diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 34797ae14..c975313c0 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -2482,6 +2482,14 @@ pub struct PipFreezeArgs { #[arg(long, overrides_with("system"), hide = true)] pub no_system: bool, + /// List packages from the specified `--target` directory. + #[arg(long, conflicts_with_all = ["prefix", "paths"])] + pub target: Option, + + /// List packages from the specified `--prefix` directory. + #[arg(long, conflicts_with_all = ["target", "paths"])] + pub prefix: Option, + #[command(flatten)] pub compat_args: compat::PipGlobalCompatArgs, } @@ -2557,6 +2565,14 @@ pub struct PipListArgs { #[arg(long, overrides_with("system"), hide = true)] pub no_system: bool, + /// List packages from the specified `--target` directory. + #[arg(long, conflicts_with = "prefix")] + pub target: Option, + + /// List packages from the specified `--prefix` directory. + #[arg(long, conflicts_with = "target")] + pub prefix: Option, + #[command(flatten)] pub compat_args: compat::PipListCompatArgs, } @@ -2672,6 +2688,14 @@ pub struct PipShowArgs { #[arg(long, overrides_with("system"), hide = true)] pub no_system: bool, + /// Show a package from the specified `--target` directory. + #[arg(long, conflicts_with = "prefix")] + pub target: Option, + + /// Show a package from the specified `--prefix` directory. + #[arg(long, conflicts_with = "target")] + pub prefix: Option, + #[command(flatten)] pub compat_args: compat::PipGlobalCompatArgs, } diff --git a/crates/uv/src/commands/pip/freeze.rs b/crates/uv/src/commands/pip/freeze.rs index 7a6844427..fd8f3cb17 100644 --- a/crates/uv/src/commands/pip/freeze.rs +++ b/crates/uv/src/commands/pip/freeze.rs @@ -4,13 +4,15 @@ use std::path::PathBuf; use anyhow::Result; use itertools::Itertools; use owo_colors::OwoColorize; +use tracing::debug; use uv_cache::Cache; use uv_distribution_types::{Diagnostic, InstalledDistKind, Name}; +use uv_fs::Simplified; use uv_installer::SitePackages; use uv_preview::Preview; use uv_python::PythonPreference; -use uv_python::{EnvironmentPreference, PythonEnvironment, PythonRequest}; +use uv_python::{EnvironmentPreference, Prefix, PythonEnvironment, PythonRequest, Target}; use crate::commands::ExitStatus; use crate::commands::pip::operations::report_target_environment; @@ -22,6 +24,8 @@ pub(crate) fn pip_freeze( strict: bool, python: Option<&str>, system: bool, + target: Option, + prefix: Option, paths: Option>, cache: &Cache, printer: Printer, @@ -36,6 +40,23 @@ pub(crate) fn pip_freeze( preview, )?; + // Apply any `--target` or `--prefix` directories. + let environment = if let Some(target) = target { + debug!( + "Using `--target` directory at {}", + target.root().user_display() + ); + environment.with_target(target)? + } else if let Some(prefix) = prefix { + debug!( + "Using `--prefix` directory at {}", + prefix.root().user_display() + ); + environment.with_prefix(prefix)? + } else { + environment + }; + report_target_environment(&environment, cache, printer)?; // Collect all the `site-packages` directories. diff --git a/crates/uv/src/commands/pip/list.rs b/crates/uv/src/commands/pip/list.rs index 908cacbf0..0c7879d18 100644 --- a/crates/uv/src/commands/pip/list.rs +++ b/crates/uv/src/commands/pip/list.rs @@ -8,6 +8,7 @@ use owo_colors::OwoColorize; use rustc_hash::FxHashMap; use serde::Serialize; use tokio::sync::Semaphore; +use tracing::debug; use unicode_width::UnicodeWidthStr; use uv_cache::{Cache, Refresh}; @@ -25,7 +26,7 @@ use uv_normalize::PackageName; use uv_pep440::Version; use uv_preview::Preview; use uv_python::PythonRequest; -use uv_python::{EnvironmentPreference, PythonEnvironment, PythonPreference}; +use uv_python::{EnvironmentPreference, Prefix, PythonEnvironment, PythonPreference, Target}; use uv_resolver::{ExcludeNewer, PrereleaseMode}; use crate::commands::ExitStatus; @@ -51,6 +52,8 @@ pub(crate) async fn pip_list( exclude_newer: ExcludeNewer, python: Option<&str>, system: bool, + target: Option, + prefix: Option, cache: &Cache, printer: Printer, preview: Preview, @@ -69,6 +72,23 @@ pub(crate) async fn pip_list( preview, )?; + // Apply any `--target` or `--prefix` directories. + let environment = if let Some(target) = target { + debug!( + "Using `--target` directory at {}", + target.root().user_display() + ); + environment.with_target(target)? + } else if let Some(prefix) = prefix { + debug!( + "Using `--prefix` directory at {}", + prefix.root().user_display() + ); + environment.with_prefix(prefix)? + } else { + environment + }; + report_target_environment(&environment, cache, printer)?; // Build the installed index. diff --git a/crates/uv/src/commands/pip/show.rs b/crates/uv/src/commands/pip/show.rs index 5a906547f..15a4b30ee 100644 --- a/crates/uv/src/commands/pip/show.rs +++ b/crates/uv/src/commands/pip/show.rs @@ -5,6 +5,7 @@ use fs_err::File; use itertools::{Either, Itertools}; use owo_colors::OwoColorize; use rustc_hash::FxHashMap; +use tracing::debug; use uv_cache::Cache; use uv_distribution_types::{Diagnostic, Name}; @@ -13,7 +14,9 @@ use uv_install_wheel::read_record_file; use uv_installer::SitePackages; use uv_normalize::PackageName; use uv_preview::Preview; -use uv_python::{EnvironmentPreference, PythonEnvironment, PythonPreference, PythonRequest}; +use uv_python::{ + EnvironmentPreference, Prefix, PythonEnvironment, PythonPreference, PythonRequest, Target, +}; use crate::commands::ExitStatus; use crate::commands::pip::operations::report_target_environment; @@ -25,6 +28,8 @@ pub(crate) fn pip_show( strict: bool, python: Option<&str>, system: bool, + target: Option, + prefix: Option, files: bool, cache: &Cache, printer: Printer, @@ -52,6 +57,23 @@ pub(crate) fn pip_show( preview, )?; + // Apply any `--target` or `--prefix` directories. + let environment = if let Some(target) = target { + debug!( + "Using `--target` directory at {}", + target.root().user_display() + ); + environment.with_target(target)? + } else if let Some(prefix) = prefix { + debug!( + "Using `--prefix` directory at {}", + prefix.root().user_display() + ); + environment.with_prefix(prefix)? + } else { + environment + }; + report_target_environment(&environment, cache, printer)?; // Build the installed index. diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 9a2d4d42b..7ab3d1279 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -941,6 +941,8 @@ async fn run(mut cli: Cli) -> Result { args.settings.strict, args.settings.python.as_deref(), args.settings.system, + args.settings.target, + args.settings.prefix, args.paths, &cache, printer, @@ -974,6 +976,8 @@ async fn run(mut cli: Cli) -> Result { args.settings.exclude_newer, args.settings.python.as_deref(), args.settings.system, + args.settings.target, + args.settings.prefix, &cache, printer, globals.preview, @@ -995,6 +999,8 @@ async fn run(mut cli: Cli) -> Result { args.settings.strict, args.settings.python.as_deref(), args.settings.system, + args.settings.target, + args.settings.prefix, args.files, &cache, printer, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 9e623f712..7f80ddb86 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -2771,6 +2771,8 @@ impl PipFreezeSettings { paths, system, no_system, + target, + prefix, compat_args: _, } = args; @@ -2782,6 +2784,8 @@ impl PipFreezeSettings { python: python.and_then(Maybe::into_option), system: flag(system, no_system, "system"), strict: flag(strict, no_strict, "strict"), + target, + prefix, ..PipOptions::default() }, filesystem, @@ -2821,6 +2825,8 @@ impl PipListSettings { python, system, no_system, + target, + prefix, compat_args: _, } = args; @@ -2834,6 +2840,8 @@ impl PipListSettings { python: python.and_then(Maybe::into_option), system: flag(system, no_system, "system"), strict: flag(strict, no_strict, "strict"), + target, + prefix, ..PipOptions::from(fetch) }, filesystem, @@ -2866,6 +2874,8 @@ impl PipShowSettings { python, system, no_system, + target, + prefix, compat_args: _, } = args; @@ -2877,6 +2887,8 @@ impl PipShowSettings { python: python.and_then(Maybe::into_option), system: flag(system, no_system, "system"), strict: flag(strict, no_strict, "strict"), + target, + prefix, ..PipOptions::default() }, filesystem, diff --git a/crates/uv/tests/it/pip_freeze.rs b/crates/uv/tests/it/pip_freeze.rs index e1bb5e59f..eb7f90e97 100644 --- a/crates/uv/tests/it/pip_freeze.rs +++ b/crates/uv/tests/it/pip_freeze.rs @@ -482,3 +482,95 @@ fn freeze_with_quiet_flag() -> Result<()> { Ok(()) } + +#[test] +fn freeze_target() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str("MarkupSafe==2.1.3\ntomli==2.0.1")?; + + let target = context.temp_dir.child("target"); + + // Install packages to a target directory. + context + .pip_install() + .arg("-r") + .arg("requirements.txt") + .arg("--target") + .arg(target.path()) + .assert() + .success(); + + // Freeze packages in the target directory. + uv_snapshot!(context.filters(), context.pip_freeze() + .arg("--target") + .arg(target.path()), @r###" + success: true + exit_code: 0 + ----- stdout ----- + markupsafe==2.1.3 + tomli==2.0.1 + + ----- stderr ----- + "### + ); + + // Without --target, the packages should not be visible. + uv_snapshot!(context.pip_freeze(), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + "### + ); + + Ok(()) +} + +#[test] +fn freeze_prefix() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str("MarkupSafe==2.1.3\ntomli==2.0.1")?; + + let prefix = context.temp_dir.child("prefix"); + + // Install packages to a prefix directory. + context + .pip_install() + .arg("-r") + .arg("requirements.txt") + .arg("--prefix") + .arg(prefix.path()) + .assert() + .success(); + + // Freeze packages in the prefix directory. + uv_snapshot!(context.filters(), context.pip_freeze() + .arg("--prefix") + .arg(prefix.path()), @r###" + success: true + exit_code: 0 + ----- stdout ----- + markupsafe==2.1.3 + tomli==2.0.1 + + ----- stderr ----- + "### + ); + + // Without --prefix, the packages should not be visible. + uv_snapshot!(context.pip_freeze(), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + "### + ); + + Ok(()) +} diff --git a/crates/uv/tests/it/pip_list.rs b/crates/uv/tests/it/pip_list.rs index ec1f288e6..73b3461aa 100644 --- a/crates/uv/tests/it/pip_list.rs +++ b/crates/uv/tests/it/pip_list.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use assert_cmd::prelude::*; use assert_fs::fixture::ChildPath; use assert_fs::fixture::FileWriteStr; use assert_fs::fixture::PathChild; @@ -788,3 +789,99 @@ fn list_ignores_quiet_flag_format_freeze() { "### ); } + +#[test] +fn list_target() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str("MarkupSafe==2.1.3\ntomli==2.0.1")?; + + let target = context.temp_dir.child("target"); + + // Install packages to a target directory. + context + .pip_install() + .arg("-r") + .arg("requirements.txt") + .arg("--target") + .arg(target.path()) + .assert() + .success(); + + // List packages in the target directory. + uv_snapshot!(context.filters(), context.pip_list() + .arg("--target") + .arg(target.path()), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Package Version + ---------- ------- + markupsafe 2.1.3 + tomli 2.0.1 + + ----- stderr ----- + "### + ); + + // Without --target, the packages should not be visible. + uv_snapshot!(context.pip_list(), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + "### + ); + + Ok(()) +} + +#[test] +fn list_prefix() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str("MarkupSafe==2.1.3\ntomli==2.0.1")?; + + let prefix = context.temp_dir.child("prefix"); + + // Install packages to a prefix directory. + context + .pip_install() + .arg("-r") + .arg("requirements.txt") + .arg("--prefix") + .arg(prefix.path()) + .assert() + .success(); + + // List packages in the prefix directory. + uv_snapshot!(context.filters(), context.pip_list() + .arg("--prefix") + .arg(prefix.path()), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Package Version + ---------- ------- + markupsafe 2.1.3 + tomli 2.0.1 + + ----- stderr ----- + "### + ); + + // Without --prefix, the packages should not be visible. + uv_snapshot!(context.pip_list(), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + "### + ); + + Ok(()) +} diff --git a/crates/uv/tests/it/pip_show.rs b/crates/uv/tests/it/pip_show.rs index a097e128b..7ed3919a0 100644 --- a/crates/uv/tests/it/pip_show.rs +++ b/crates/uv/tests/it/pip_show.rs @@ -534,3 +534,105 @@ fn show_files() { ----- stderr ----- "#); } + +#[test] +fn show_target() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str("MarkupSafe==2.1.3")?; + + let target = context.temp_dir.child("target"); + + // Install packages to a target directory. + context + .pip_install() + .arg("-r") + .arg("requirements.txt") + .arg("--target") + .arg(target.path()) + .assert() + .success(); + + // Show package in the target directory. + uv_snapshot!(context.filters(), context.pip_show() + .arg("markupsafe") + .arg("--target") + .arg(target.path()), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Name: markupsafe + Version: 2.1.3 + Location: [TEMP_DIR]/target + Requires: + Required-by: + + ----- stderr ----- + "### + ); + + // Without --target, the package should not be found. + uv_snapshot!(context.pip_show().arg("markupsafe"), @r###" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + warning: Package(s) not found for: markupsafe + "### + ); + + Ok(()) +} + +#[test] +fn show_prefix() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str("MarkupSafe==2.1.3")?; + + let prefix = context.temp_dir.child("prefix"); + + // Install packages to a prefix directory. + context + .pip_install() + .arg("-r") + .arg("requirements.txt") + .arg("--prefix") + .arg(prefix.path()) + .assert() + .success(); + + // Show package in the prefix directory. + uv_snapshot!(context.filters(), context.pip_show() + .arg("markupsafe") + .arg("--prefix") + .arg(prefix.path()), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Name: markupsafe + Version: 2.1.3 + Location: [TEMP_DIR]/prefix/[PYTHON-LIB]/site-packages + Requires: + Required-by: + + ----- stderr ----- + "### + ); + + // Without --prefix, the package should not be found. + uv_snapshot!(context.pip_show().arg("markupsafe"), @r###" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + warning: Package(s) not found for: markupsafe + "### + ); + + Ok(()) +} diff --git a/docs/reference/cli.md b/docs/reference/cli.md index de2bdc312..d5a1b0fe0 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -5062,6 +5062,7 @@ uv pip freeze [OPTIONS]
--offline

Disable network access.

When disabled, uv will only use locally cached data and locally available files.

May also be set with the UV_OFFLINE environment variable.

--path paths

Restrict to the specified installation path for listing packages (can be used multiple times)

+
--prefix prefix

List packages from the specified --prefix directory

--project project

Run the command within the given project directory.

All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project's virtual environment (.venv).

Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

@@ -5077,7 +5078,8 @@ Python environment if no virtual environment is found.

--system

List packages in the system Python environment.

Disables discovery of virtual environments.

See uv python for details on Python discovery.

-

May also be set with the UV_SYSTEM_PYTHON environment variable.

--verbose, -v

Use verbose output.

+

May also be set with the UV_SYSTEM_PYTHON environment variable.

--target target

List packages from the specified --target directory

+
--verbose, -v

Use verbose output.

You can configure fine-grained logging using the RUST_LOG environment variable. (https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives)

@@ -5172,6 +5174,7 @@ uv pip list [OPTIONS]

When disabled, uv will only use locally cached data and locally available files.

May also be set with the UV_OFFLINE environment variable.

--outdated

List outdated packages.

The latest version of each package will be shown alongside the installed version. Up-to-date packages will be omitted from the output.

+
--prefix prefix

List packages from the specified --prefix directory

--project project

Run the command within the given project directory.

All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project's virtual environment (.venv).

Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

@@ -5187,7 +5190,8 @@ Python environment if no virtual environment is found.

--system

List packages in the system Python environment.

Disables discovery of virtual environments.

See uv python for details on Python discovery.

-

May also be set with the UV_SYSTEM_PYTHON environment variable.

--verbose, -v

Use verbose output.

+

May also be set with the UV_SYSTEM_PYTHON environment variable.

--target target

List packages from the specified --target directory

+
--verbose, -v

Use verbose output.

You can configure fine-grained logging using the RUST_LOG environment variable. (https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives)

@@ -5244,7 +5248,8 @@ uv pip show [OPTIONS] [PACKAGE]...

May also be set with the UV_NO_PROGRESS environment variable.

--no-python-downloads

Disable automatic downloads of Python.

--offline

Disable network access.

When disabled, uv will only use locally cached data and locally available files.

-

May also be set with the UV_OFFLINE environment variable.

--project project

Run the command within the given project directory.

+

May also be set with the UV_OFFLINE environment variable.

--prefix prefix

Show a package from the specified --prefix directory

+
--project project

Run the command within the given project directory.

All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project's virtual environment (.venv).

Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

See --directory to change the working directory entirely.

@@ -5259,7 +5264,8 @@ system Python environment if no virtual environment is found.

--system

Show a package in the system Python environment.

Disables discovery of virtual environments.

See uv python for details on Python discovery.

-

May also be set with the UV_SYSTEM_PYTHON environment variable.

--verbose, -v

Use verbose output.

+

May also be set with the UV_SYSTEM_PYTHON environment variable.

--target target

Show a package from the specified --target directory

+
--verbose, -v

Use verbose output.

You can configure fine-grained logging using the RUST_LOG environment variable. (https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives)