Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for --prefix #4085

Merged
merged 2 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 34 additions & 18 deletions crates/uv-interpreter/src/environment.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
use itertools::Either;
use std::borrow::Cow;
use std::env;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use uv_configuration::PreviewMode;

use same_file::is_same_file;

use uv_cache::Cache;
use uv_configuration::PreviewMode;
use uv_fs::{LockedFile, Simplified};

use crate::discovery::{InterpreterRequest, SourceSelector, SystemPython};
use crate::virtualenv::{virtualenv_python_executable, PyVenvConfiguration};
use crate::{
find_default_interpreter, find_interpreter, Error, Interpreter, InterpreterSource, Target,
find_default_interpreter, find_interpreter, Error, Interpreter, InterpreterSource, Prefix,
Target,
};

/// A Python environment, consisting of a Python [`Interpreter`] and its associated paths.
Expand Down Expand Up @@ -159,6 +158,16 @@ impl PythonEnvironment {
}))
}

/// Create a [`PythonEnvironment`] from an existing [`Interpreter`] and `--prefix` directory.
#[must_use]
pub fn with_prefix(self, prefix: Prefix) -> Self {
let inner = Arc::unwrap_or_clone(self.0);
Self(Arc::new(PythonEnvironmentShared {
interpreter: inner.interpreter.with_prefix(prefix),
..inner
}))
}

/// Returns the root (i.e., `prefix`) of the Python interpreter.
pub fn root(&self) -> &Path {
&self.0.root
Expand Down Expand Up @@ -189,20 +198,27 @@ impl PythonEnvironment {
///
/// Some distributions also create symbolic links from `purelib` to `platlib`; in such cases, we
/// still deduplicate the entries, returning a single path.
pub fn site_packages(&self) -> impl Iterator<Item = &Path> {
if let Some(target) = self.0.interpreter.target() {
Either::Left(std::iter::once(target.root()))
pub fn site_packages(&self) -> impl Iterator<Item = Cow<Path>> {
let target = self.0.interpreter.target().map(Target::site_packages);

let prefix = self
.0
.interpreter
.prefix()
.map(|prefix| prefix.site_packages(self.0.interpreter.virtualenv()));

let interpreter = if target.is_none() && prefix.is_none() {
Some(self.0.interpreter.site_packages())
} else {
let purelib = self.0.interpreter.purelib();
let platlib = self.0.interpreter.platlib();
Either::Right(std::iter::once(purelib).chain(
if purelib == platlib || is_same_file(purelib, platlib).unwrap_or(false) {
None
} else {
Some(platlib)
},
))
}
None
};

target
.into_iter()
.flatten()
.map(Cow::Borrowed)
.chain(prefix.into_iter().flatten().map(Cow::Owned))
.chain(interpreter.into_iter().flatten().map(Cow::Borrowed))
}

/// Returns the path to the `bin` directory inside this environment.
Expand Down
47 changes: 42 additions & 5 deletions crates/uv-interpreter/src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::process::{Command, ExitStatus};
use configparser::ini::Ini;
use fs_err as fs;
use once_cell::sync::OnceCell;
use same_file::is_same_file;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use tracing::{trace, warn};
Expand All @@ -20,7 +21,7 @@ use uv_cache::{Cache, CacheBucket, CachedByTimestamp, Freshness, Timestamp};
use uv_fs::{write_atomic_sync, PythonExt, Simplified};

use crate::pointer_size::PointerSize;
use crate::{PythonVersion, Target, VirtualEnvironment};
use crate::{Prefix, PythonVersion, Target, VirtualEnvironment};

/// A Python executable and its associated platform markers.
#[derive(Debug, Clone)]
Expand All @@ -38,6 +39,7 @@ pub struct Interpreter {
stdlib: PathBuf,
tags: OnceCell<Tags>,
target: Option<Target>,
prefix: Option<Prefix>,
pointer_size: PointerSize,
gil_disabled: bool,
}
Expand Down Expand Up @@ -69,6 +71,7 @@ impl Interpreter {
stdlib: info.stdlib,
tags: OnceCell::new(),
target: None,
prefix: None,
})
}

Expand Down Expand Up @@ -100,6 +103,7 @@ impl Interpreter {
stdlib: PathBuf::from("/dev/null"),
tags: OnceCell::new(),
target: None,
prefix: None,
pointer_size: PointerSize::_64,
gil_disabled: false,
}
Expand All @@ -113,13 +117,12 @@ impl Interpreter {
sys_executable: virtualenv.executable,
sys_prefix: virtualenv.root,
target: None,
prefix: None,
..self
}
}

/// Return a new [`Interpreter`] to install into the given `--target` directory.
///
/// Initializes the `--target` directory with the expected layout.
#[must_use]
pub fn with_target(self, target: Target) -> Self {
Self {
Expand All @@ -128,6 +131,15 @@ impl Interpreter {
}
}

/// Return a new [`Interpreter`] to install into the given `--prefix` directory.
#[must_use]
pub fn with_prefix(self, prefix: Prefix) -> Self {
Self {
prefix: Some(prefix),
..self
}
}

/// Returns the path to the Python virtual environment.
#[inline]
pub fn platform(&self) -> &Platform {
Expand Down Expand Up @@ -166,6 +178,11 @@ impl Interpreter {
self.target.is_some()
}

/// Returns `true` if the environment is a `--prefix` environment.
pub fn is_prefix(&self) -> bool {
self.prefix.is_some()
}

/// Returns `Some` if the environment is externally managed, optionally including an error
/// message from the `EXTERNALLY-MANAGED` file.
///
Expand All @@ -176,8 +193,8 @@ impl Interpreter {
return None;
}

// If we're installing into a target directory, it's never externally managed.
if self.is_target() {
// If we're installing into a target or prefix directory, it's never externally managed.
if self.is_target() || self.is_prefix() {
return None;
}

Expand Down Expand Up @@ -357,6 +374,11 @@ impl Interpreter {
self.target.as_ref()
}

/// Return the `--prefix` directory for this interpreter, if any.
pub fn prefix(&self) -> Option<&Prefix> {
self.prefix.as_ref()
}

/// Return the [`Layout`] environment used to install wheels into this interpreter.
pub fn layout(&self) -> Layout {
Layout {
Expand All @@ -365,6 +387,8 @@ impl Interpreter {
os_name: self.markers.os_name().to_string(),
scheme: if let Some(target) = self.target.as_ref() {
target.scheme()
} else if let Some(prefix) = self.prefix.as_ref() {
prefix.scheme(&self.virtualenv)
} else {
Scheme {
purelib: self.purelib().to_path_buf(),
Expand All @@ -387,6 +411,19 @@ impl Interpreter {
}
}

/// Return an iterator over the `site-packages` directories inside the environment.
pub fn site_packages(&self) -> impl Iterator<Item = &Path> {
let purelib = self.purelib();
let platlib = self.platlib();
std::iter::once(purelib).chain(
if purelib == platlib || is_same_file(purelib, platlib).unwrap_or(false) {
None
} else {
Some(platlib)
},
)
}

/// Check if the interpreter matches the given Python version.
///
/// If a patch version is present, we will require an exact match.
Expand Down
2 changes: 2 additions & 0 deletions crates/uv-interpreter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub use crate::discovery::{
pub use crate::environment::PythonEnvironment;
pub use crate::interpreter::Interpreter;
pub use crate::pointer_size::PointerSize;
pub use crate::prefix::Prefix;
pub use crate::python_version::PythonVersion;
pub use crate::target::Target;
pub use crate::virtualenv::{Error as VirtualEnvError, PyVenvConfiguration, VirtualEnvironment};
Expand All @@ -21,6 +22,7 @@ mod interpreter;
pub mod managed;
pub mod platform;
mod pointer_size;
mod prefix;
mod py_launcher;
mod python_version;
mod target;
Expand Down
43 changes: 43 additions & 0 deletions crates/uv-interpreter/src/prefix.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use std::path::{Path, PathBuf};

use pypi_types::Scheme;

/// A `--prefix` directory into which packages can be installed, separate from a virtual environment
/// or system Python interpreter.
#[derive(Debug, Clone)]
pub struct Prefix(PathBuf);

impl Prefix {
/// Return the [`Scheme`] for the `--prefix` directory.
pub fn scheme(&self, virtualenv: &Scheme) -> Scheme {
Scheme {
purelib: self.0.join(&virtualenv.purelib),
platlib: self.0.join(&virtualenv.platlib),
scripts: self.0.join(&virtualenv.scripts),
data: self.0.join(&virtualenv.data),
include: self.0.join(&virtualenv.include),
}
}

/// Return an iterator over the `site-packages` directories inside the environment.
pub fn site_packages(&self, virtualenv: &Scheme) -> impl Iterator<Item = PathBuf> {
std::iter::once(self.0.join(&virtualenv.purelib))
}

/// Initialize the `--prefix` directory.
pub fn init(&self) -> std::io::Result<()> {
fs_err::create_dir_all(&self.0)?;
Ok(())
}

/// Return the path to the `--prefix` directory.
pub fn root(&self) -> &Path {
&self.0
}
}

impl From<PathBuf> for Prefix {
fn from(path: PathBuf) -> Self {
Self(path)
}
}
5 changes: 5 additions & 0 deletions crates/uv-interpreter/src/target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ impl Target {
}
}

/// Return an iterator over the `site-packages` directories inside the environment.
pub fn site_packages(&self) -> impl Iterator<Item = &Path> {
std::iter::once(self.0.as_path())
}

/// Initialize the `--target` directory.
pub fn init(&self) -> std::io::Result<()> {
fs_err::create_dir_all(&self.0)?;
Expand Down
1 change: 1 addition & 0 deletions crates/uv-workspace/src/combine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ impl Combine for PipOptions {
.break_system_packages
.combine(other.break_system_packages),
target: self.target.combine(other.target),
prefix: self.prefix.combine(other.prefix),
index_url: self.index_url.combine(other.index_url),
extra_index_url: self.extra_index_url.combine(other.extra_index_url),
no_index: self.no_index.combine(other.no_index),
Expand Down
1 change: 1 addition & 0 deletions crates/uv-workspace/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ pub struct PipOptions {
pub system: Option<bool>,
pub break_system_packages: Option<bool>,
pub target: Option<PathBuf>,
pub prefix: Option<PathBuf>,
pub index_url: Option<IndexUrl>,
pub extra_index_url: Option<Vec<IndexUrl>>,
pub no_index: Option<bool>,
Expand Down
39 changes: 32 additions & 7 deletions crates/uv/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -731,10 +731,21 @@ pub(crate) struct PipSyncArgs {
pub(crate) no_break_system_packages: bool,

/// Install packages into the specified directory, rather than into the virtual environment
/// or system Python interpreter.
#[arg(long)]
/// or system Python interpreter. The packages will be installed at the top-level of the
/// directory
#[arg(long, conflicts_with = "prefix")]
pub(crate) target: Option<PathBuf>,

/// Install packages into `lib`, `bin`, and other top-level folders under the specified
/// directory, as if a virtual environment were created at the specified location.
///
/// In general, prefer the use of `--python` to install into an alternate environment, as
/// scripts and other artifacts installed via `--prefix` will reference the installing
/// interpreter, rather than any interpreter added to the `--prefix` directory, rendering them
/// non-portable.
#[arg(long, conflicts_with = "target")]
pub(crate) prefix: Option<PathBuf>,

/// Use legacy `setuptools` behavior when building source distributions without a
/// `pyproject.toml`.
#[arg(long, overrides_with("no_legacy_setup_py"))]
Expand Down Expand Up @@ -1087,10 +1098,21 @@ pub(crate) struct PipInstallArgs {
pub(crate) no_break_system_packages: bool,

/// Install packages into the specified directory, rather than into the virtual environment
/// or system Python interpreter.
#[arg(long)]
/// or system Python interpreter. The packages will be installed at the top-level of the
/// directory
#[arg(long, conflicts_with = "prefix")]
pub(crate) target: Option<PathBuf>,

/// Install packages into `lib`, `bin`, and other top-level folders under the specified
/// directory, as if a virtual environment were created at the specified location.
///
/// In general, prefer the use of `--python` to install into an alternate environment, as
/// scripts and other artifacts installed via `--prefix` will reference the installing
/// interpreter, rather than any interpreter added to the `--prefix` directory, rendering them
/// non-portable.
#[arg(long, conflicts_with = "target")]
pub(crate) prefix: Option<PathBuf>,

/// Use legacy `setuptools` behavior when building source distributions without a
/// `pyproject.toml`.
#[arg(long, overrides_with("no_legacy_setup_py"))]
Expand Down Expand Up @@ -1300,10 +1322,13 @@ pub(crate) struct PipUninstallArgs {
#[arg(long, overrides_with("break_system_packages"))]
pub(crate) no_break_system_packages: bool,

/// Uninstall packages from the specified directory, rather than from the virtual environment
/// or system Python interpreter.
#[arg(long)]
/// Uninstall packages from the specified `--target` directory.
#[arg(long, conflicts_with = "prefix")]
pub(crate) target: Option<PathBuf>,

/// Uninstall packages from the specified `--prefix` directory.
#[arg(long, conflicts_with = "target")]
pub(crate) prefix: Option<PathBuf>,
}

#[derive(Args)]
Expand Down
2 changes: 1 addition & 1 deletion crates/uv/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ pub(super) async fn compile_bytecode(
let start = std::time::Instant::now();
let mut files = 0;
for site_packages in venv.site_packages() {
files += compile_tree(site_packages, venv.python_executable(), cache.root())
files += compile_tree(&site_packages, venv.python_executable(), cache.root())
.await
.with_context(|| {
format!(
Expand Down
Loading
Loading