Skip to content

Commit

Permalink
Wire up to CLI
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Apr 25, 2024
1 parent 11502aa commit 6b3c5c3
Show file tree
Hide file tree
Showing 11 changed files with 145 additions and 14 deletions.
17 changes: 12 additions & 5 deletions crates/uv-interpreter/src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,19 @@ impl Interpreter {
}
}

#[must_use]
pub fn with_target(self, target: &Path) -> Self {
Self {
target: Some(target.to_path_buf()),
/// Return a new [`Interpreter`] to install into the given `--target` directory.
///
/// Initializes the `--target` directory with the expected layout.
pub fn with_target(self, target: PathBuf) -> Result<Self, Error> {
// Create the `--target` directory layout.
fs_err::create_dir_all(&target)?;
fs_err::create_dir_all(target.join("bin"))?;
fs_err::create_dir_all(target.join("include"))?;

Ok(Self {
target: Some(target),
..self
}
})
}

/// Returns the path to the Python virtual environment.
Expand Down
9 changes: 4 additions & 5 deletions crates/uv-interpreter/src/python_environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,11 @@ impl PythonEnvironment {
}

/// Create a [`PythonEnvironment`] from an existing [`Interpreter`] and `--target` directory.
#[must_use]
pub fn with_target(self, target: &Path) -> Self {
Self {
interpreter: self.interpreter.with_target(target),
pub fn with_target(self, target: PathBuf) -> Result<Self, Error> {
Ok(Self {
interpreter: self.interpreter.with_target(target)?,
..self
}
})
}

/// Returns the root (i.e., `prefix`) of the Python interpreter.
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 @@ -47,6 +47,7 @@ pub struct PipOptions {
pub python: Option<String>,
pub system: Option<bool>,
pub break_system_packages: Option<bool>,
pub target: Option<PathBuf>,
pub offline: Option<bool>,
pub index_url: Option<IndexUrl>,
pub extra_index_url: Option<Vec<IndexUrl>>,
Expand Down
15 changes: 15 additions & 0 deletions crates/uv/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,11 @@ pub(crate) struct PipSyncArgs {
#[arg(long, overrides_with("break_system_packages"))]
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)]
pub(crate) target: 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 @@ -1132,6 +1137,11 @@ pub(crate) struct PipInstallArgs {
#[arg(long, overrides_with("break_system_packages"))]
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)]
pub(crate) target: 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 @@ -1335,6 +1345,11 @@ 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)]
pub(crate) target: Option<PathBuf>,

/// Run offline, i.e., without accessing the network.
#[arg(long, overrides_with("no_offline"))]
pub(crate) offline: bool,
Expand Down
10 changes: 9 additions & 1 deletion crates/uv/src/commands/pip_install.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::borrow::Cow;
use std::fmt::Write;

use std::path::Path;
use std::path::{Path, PathBuf};

use anstream::eprint;
use anyhow::{anyhow, Context, Result};
Expand Down Expand Up @@ -84,6 +84,7 @@ pub(crate) async fn pip_install(
python: Option<String>,
system: bool,
break_system_packages: bool,
target: Option<PathBuf>,
native_tls: bool,
cache: Cache,
dry_run: bool,
Expand Down Expand Up @@ -134,6 +135,13 @@ pub(crate) async fn pip_install(
venv.python_executable().user_display().cyan()
);

// Apply any `--target` directory.
let venv = if let Some(target) = target {
venv.with_target(target)?
} else {
venv
};

// If the environment is externally managed, abort.
if let Some(externally_managed) = venv.interpreter().is_externally_managed() {
if break_system_packages {
Expand Down
11 changes: 8 additions & 3 deletions crates/uv/src/commands/pip_sync.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::borrow::Cow;
use std::fmt::Write;
use std::path::Path;
use std::path::PathBuf;

use anstream::eprint;
use anyhow::{anyhow, Context, Result};
Expand All @@ -12,7 +12,6 @@ use distribution_types::{
IndexLocations, InstalledMetadata, LocalDist, LocalEditable, LocalEditables, Name, ResolvedDist,
};
use install_wheel_rs::linker::LinkMode;

use platform_tags::Tags;
use pypi_types::Yanked;
use requirements_txt::EditableRequirement;
Expand Down Expand Up @@ -65,6 +64,7 @@ pub(crate) async fn pip_sync(
python: Option<String>,
system: bool,
break_system_packages: bool,
target: Option<PathBuf>,
native_tls: bool,
cache: Cache,
printer: Printer,
Expand Down Expand Up @@ -114,7 +114,12 @@ pub(crate) async fn pip_sync(
venv.python_executable().user_display().cyan()
);

let venv = venv.with_target(Path::new("/Users/crmarsh/workspace/uv/foo"));
// Apply any `--target` directory.
let venv = if let Some(target) = target {
venv.with_target(target)?
} else {
venv
};

// If the environment is externally managed, abort.
if let Some(externally_managed) = venv.interpreter().is_externally_managed() {
Expand Down
9 changes: 9 additions & 0 deletions crates/uv/src/commands/pip_uninstall.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::fmt::Write;
use std::path::PathBuf;

use anyhow::Result;
use itertools::{Either, Itertools};
Expand All @@ -25,6 +26,7 @@ pub(crate) async fn pip_uninstall(
python: Option<String>,
system: bool,
break_system_packages: bool,
target: Option<PathBuf>,
cache: Cache,
connectivity: Connectivity,
native_tls: bool,
Expand Down Expand Up @@ -54,6 +56,13 @@ pub(crate) async fn pip_uninstall(
venv.python_executable().user_display().cyan(),
);

// Apply any `--target` directory.
let venv = if let Some(target) = target {
venv.with_target(target)?
} else {
venv
};

// If the environment is externally managed, abort.
if let Some(externally_managed) = venv.interpreter().is_externally_managed() {
if break_system_packages {
Expand Down
3 changes: 3 additions & 0 deletions crates/uv/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ async fn run() -> Result<ExitStatus> {
args.shared.python,
args.shared.system,
args.shared.break_system_packages,
args.shared.target,
globals.native_tls,
cache,
printer,
Expand Down Expand Up @@ -334,6 +335,7 @@ async fn run() -> Result<ExitStatus> {
args.shared.python,
args.shared.system,
args.shared.break_system_packages,
args.shared.target,
globals.native_tls,
cache,
args.dry_run,
Expand Down Expand Up @@ -362,6 +364,7 @@ async fn run() -> Result<ExitStatus> {
args.shared.python,
args.shared.system,
args.shared.break_system_packages,
args.shared.target,
cache,
args.shared.connectivity,
globals.native_tls,
Expand Down
9 changes: 9 additions & 0 deletions crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ impl PipSyncSettings {
no_system,
break_system_packages,
no_break_system_packages,
target,
legacy_setup_py,
no_legacy_setup_py,
no_build_isolation,
Expand Down Expand Up @@ -332,6 +333,7 @@ impl PipSyncSettings {
python,
system: flag(system, no_system),
break_system_packages: flag(break_system_packages, no_break_system_packages),
target,
offline: flag(offline, no_offline),
index_url: index_url.and_then(Maybe::into_option),
extra_index_url: extra_index_url.map(|extra_index_urls| {
Expand Down Expand Up @@ -423,6 +425,7 @@ impl PipInstallSettings {
no_system,
break_system_packages,
no_break_system_packages,
target,
legacy_setup_py,
no_legacy_setup_py,
no_build_isolation,
Expand Down Expand Up @@ -463,6 +466,7 @@ impl PipInstallSettings {
python,
system: flag(system, no_system),
break_system_packages: flag(break_system_packages, no_break_system_packages),
target,
offline: flag(offline, no_offline),
index_url: index_url.and_then(Maybe::into_option),
extra_index_url: extra_index_url.map(|extra_index_urls| {
Expand Down Expand Up @@ -530,6 +534,7 @@ impl PipUninstallSettings {
no_system,
break_system_packages,
no_break_system_packages,
target,
offline,
no_offline,
} = args;
Expand All @@ -545,6 +550,7 @@ impl PipUninstallSettings {
python,
system: flag(system, no_system),
break_system_packages: flag(break_system_packages, no_break_system_packages),
target,
offline: flag(offline, no_offline),
keyring_provider,
..PipOptions::default()
Expand Down Expand Up @@ -801,6 +807,7 @@ pub(crate) struct PipSharedSettings {
pub(crate) system: bool,
pub(crate) extras: ExtrasSpecification,
pub(crate) break_system_packages: bool,
pub(crate) target: Option<PathBuf>,
pub(crate) connectivity: Connectivity,
pub(crate) index_strategy: IndexStrategy,
pub(crate) keyring_provider: KeyringProviderType,
Expand Down Expand Up @@ -840,6 +847,7 @@ impl PipSharedSettings {
python,
system,
break_system_packages,
target,
offline,
index_url,
extra_index_url,
Expand Down Expand Up @@ -955,6 +963,7 @@ impl PipSharedSettings {
.break_system_packages
.or(break_system_packages)
.unwrap_or_default(),
target: args.target.or(target),
no_binary: NoBinary::from_args(args.no_binary.or(no_binary).unwrap_or_default()),
compile_bytecode: args
.compile_bytecode
Expand Down
69 changes: 69 additions & 0 deletions crates/uv/tests/pip_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4814,3 +4814,72 @@ fn require_hashes_registry_invalid_hash() -> Result<()> {

Ok(())
}

/// Sync to a `--target` directory.
#[test]
fn target() -> Result<()> {
let context = TestContext::new("3.12");

// Install `anyio` to the target directory.
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str("anyio==4.1.0")?;

uv_snapshot!(command(&context)
.arg("requirements.in")
.arg("--target")
.arg("target"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Downloaded 1 package in [TIME]
Installed 1 package in [TIME]
+ anyio==4.1.0
"###);

// Upgrade it.
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str("anyio==4.2.0")?;

uv_snapshot!(command(&context)
.arg("requirements.in")
.arg("--target")
.arg("target"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Downloaded 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
- anyio==4.1.0
+ anyio==4.2.0
"###);

// Remove it, and replace with `flask`, which includes a binary.
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str("flask")?;

uv_snapshot!(command(&context)
.arg("requirements.in")
.arg("--target")
.arg("target"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Downloaded 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
- anyio==4.2.0
+ flask==3.0.3
"###);

Ok(())
}
6 changes: 6 additions & 0 deletions uv.schema.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 6b3c5c3

Please sign in to comment.