Skip to content

Commit

Permalink
Add support for --prefix
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Jun 6, 2024
1 parent fa2b6a2 commit 64939e6
Show file tree
Hide file tree
Showing 13 changed files with 163 additions and 30 deletions.
44 changes: 31 additions & 13 deletions crates/uv-interpreter/src/environment.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use itertools::Either;
use std::borrow::Cow;
use std::env;
use std::path::{Path, PathBuf};
use std::sync::Arc;
Expand All @@ -12,7 +12,8 @@ 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 +160,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 +200,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>> {
// Determine the `purelib` and `platlib` directories.
let (purelib, platlib) = if let Some(target) = self.0.interpreter.target() {
let scheme = target.scheme();
(Cow::Owned(scheme.purelib), Cow::Owned(scheme.platlib))
} else if let Some(prefix) = self.0.interpreter.prefix() {
let scheme = prefix.scheme(self.0.interpreter.virtualenv());
(Cow::Owned(scheme.purelib), Cow::Owned(scheme.platlib))
} 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)
},
))
}
(Cow::Borrowed(purelib), Cow::Borrowed(platlib))
};

let platlib = if purelib == platlib || is_same_file(&purelib, &platlib).unwrap_or(false) {
None
} else {
Some(platlib)
};

std::iter::once(purelib).chain(platlib)
}

/// Returns the path to the `bin` directory inside this environment.
Expand Down
33 changes: 28 additions & 5 deletions crates/uv-interpreter/src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,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 +38,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 +70,7 @@ impl Interpreter {
stdlib: info.stdlib,
tags: OnceCell::new(),
target: None,
prefix: None,
})
}

Expand Down Expand Up @@ -100,6 +102,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 +116,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 +130,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 +177,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 +192,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 +373,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 +386,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 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
38 changes: 38 additions & 0 deletions crates/uv-interpreter/src/prefix.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
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 `--target` 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),
}
}

/// 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)
}
}
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
21 changes: 18 additions & 3 deletions crates/uv/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -732,9 +732,14 @@ pub(crate) struct PipSyncArgs {

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

/// Install packages into the specified directory, rather than into the virtual environment
/// or system Python interpreter.
#[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 @@ -1088,9 +1093,14 @@ pub(crate) struct PipInstallArgs {

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

/// Install packages into the specified directory, rather than into the virtual environment
/// or system Python interpreter.
#[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 @@ -1302,8 +1312,13 @@ pub(crate) struct PipUninstallArgs {

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

/// Uninstall packages from the specified directory, rather than from the virtual environment
/// or system Python interpreter.
#[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
12 changes: 10 additions & 2 deletions crates/uv/src/commands/pip/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use uv_dispatch::BuildDispatch;
use uv_fs::Simplified;
use uv_git::GitResolver;
use uv_installer::{SatisfiesResult, SitePackages};
use uv_interpreter::{PythonEnvironment, PythonVersion, SystemPython, Target};
use uv_interpreter::{Prefix, PythonEnvironment, PythonVersion, SystemPython, Target};
use uv_requirements::{RequirementsSource, RequirementsSpecification};
use uv_resolver::{
DependencyMode, ExcludeNewer, FlatIndex, InMemoryIndex, OptionsBuilder, PreReleaseMode,
Expand Down Expand Up @@ -68,6 +68,7 @@ pub(crate) async fn pip_install(
system: bool,
break_system_packages: bool,
target: Option<Target>,
prefix: Option<Prefix>,
concurrency: Concurrency,
native_tls: bool,
preview: PreviewMode,
Expand Down Expand Up @@ -129,14 +130,21 @@ pub(crate) async fn pip_install(
venv.python_executable().user_display().cyan()
);

// Apply any `--target` directory.
// Apply any `--target` or `--prefix` directories.
let venv = if let Some(target) = target {
debug!(
"Using `--target` directory at {}",
target.root().user_display()
);
target.init()?;
venv.with_target(target)
} else if let Some(prefix) = prefix {
debug!(
"Using `--prefix` directory at {}",
prefix.root().user_display()
);
prefix.init()?;
venv.with_prefix(prefix)
} else {
venv
};
Expand Down
12 changes: 10 additions & 2 deletions crates/uv/src/commands/pip/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use uv_dispatch::BuildDispatch;
use uv_fs::Simplified;
use uv_git::GitResolver;
use uv_installer::SitePackages;
use uv_interpreter::{PythonEnvironment, PythonVersion, SystemPython, Target};
use uv_interpreter::{Prefix, PythonEnvironment, PythonVersion, SystemPython, Target};
use uv_requirements::{RequirementsSource, RequirementsSpecification};
use uv_resolver::{
DependencyMode, ExcludeNewer, FlatIndex, InMemoryIndex, OptionsBuilder, PreReleaseMode,
Expand Down Expand Up @@ -60,6 +60,7 @@ pub(crate) async fn pip_sync(
system: bool,
break_system_packages: bool,
target: Option<Target>,
prefix: Option<Prefix>,
concurrency: Concurrency,
native_tls: bool,
preview: PreviewMode,
Expand Down Expand Up @@ -124,14 +125,21 @@ pub(crate) async fn pip_sync(
venv.python_executable().user_display().cyan()
);

// Apply any `--target` directory.
// Apply any `--target` or `--prefix` directories.
let venv = if let Some(target) = target {
debug!(
"Using `--target` directory at {}",
target.root().user_display()
);
target.init()?;
venv.with_target(target)
} else if let Some(prefix) = prefix {
debug!(
"Using `--prefix` directory at {}",
prefix.root().user_display()
);
prefix.init()?;
venv.with_prefix(prefix)
} else {
venv
};
Expand Down
Loading

0 comments on commit 64939e6

Please sign in to comment.