diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 6befc34b95950..f135870a054de 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -2533,6 +2533,10 @@ pub struct PipFreezeArgs { #[arg(long)] pub exclude_editable: bool, + /// Exclude the specified package(s) from the output. + #[arg(long)] + pub r#exclude: Vec, + /// Validate the Python environment, to detect packages with missing dependencies and other /// issues. #[arg(long, overrides_with("no_strict"))] diff --git a/crates/uv/src/commands/pip/freeze.rs b/crates/uv/src/commands/pip/freeze.rs index fd8f3cb177349..0d5f3d7054f5d 100644 --- a/crates/uv/src/commands/pip/freeze.rs +++ b/crates/uv/src/commands/pip/freeze.rs @@ -1,3 +1,4 @@ +use std::collections::HashSet; use std::fmt::Write; use std::path::PathBuf; @@ -10,6 +11,7 @@ use uv_cache::Cache; use uv_distribution_types::{Diagnostic, InstalledDistKind, Name}; use uv_fs::Simplified; use uv_installer::SitePackages; +use uv_normalize::PackageName; use uv_preview::Preview; use uv_python::PythonPreference; use uv_python::{EnvironmentPreference, Prefix, PythonEnvironment, PythonRequest, Target}; @@ -21,6 +23,7 @@ use crate::printer::Printer; /// Enumerate the installed packages in the current environment. pub(crate) fn pip_freeze( exclude_editable: bool, + exclude: &HashSet, strict: bool, python: Option<&str>, system: bool, @@ -80,7 +83,15 @@ pub(crate) fn pip_freeze( site_packages .iter() .flat_map(uv_installer::SitePackages::iter) - .filter(|dist| !(exclude_editable && dist.is_editable())) + .filter(|dist| { + if exclude_editable && dist.is_editable() { + return false; + } + if exclude.contains(dist.name()) { + return false; + } + true + }) .sorted_unstable_by(|a, b| a.name().cmp(b.name()).then(a.version().cmp(b.version()))) .map(|dist| match &dist.kind { InstalledDistKind::Registry(dist) => { diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 3560faa27412d..4a37963efddbf 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -976,6 +976,7 @@ async fn run(mut cli: Cli) -> Result { commands::pip_freeze( args.exclude_editable, + &args.exclude, args.settings.strict, args.settings.python.as_deref(), args.settings.system, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 4a3da83f478af..ab7d288dcc67b 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -1,3 +1,4 @@ +use std::collections::HashSet; use std::env::VarError; use std::num::NonZeroUsize; use std::path::PathBuf; @@ -3024,6 +3025,7 @@ impl PipUninstallSettings { #[derive(Debug, Clone)] pub(crate) struct PipFreezeSettings { pub(crate) exclude_editable: bool, + pub(crate) exclude: HashSet, pub(crate) paths: Option>, pub(crate) settings: PipSettings, } @@ -3037,6 +3039,7 @@ impl PipFreezeSettings { ) -> Self { let PipFreezeArgs { exclude_editable, + exclude, strict, no_strict, python, @@ -3050,6 +3053,7 @@ impl PipFreezeSettings { Self { exclude_editable, + exclude: exclude.into_iter().collect(), paths, settings: PipSettings::combine( PipOptions { diff --git a/crates/uv/tests/it/pip_freeze.rs b/crates/uv/tests/it/pip_freeze.rs index ce1ef8b1f6cd2..d4f4b9fc0ce76 100644 --- a/crates/uv/tests/it/pip_freeze.rs +++ b/crates/uv/tests/it/pip_freeze.rs @@ -574,3 +574,41 @@ fn freeze_prefix() -> Result<()> { Ok(()) } + +#[test] +fn freeze_exclude() { + let context = TestContext::new("3.12"); + + let prefix = context.temp_dir.child("prefix"); + + // Install packages to a prefix directory. + context + .pip_install() + .arg("MarkupSafe") + .arg("tomli") + .arg("--prefix") + .arg(prefix.path()) + .assert() + .success(); + + // Run `pip freeze --exclude MarkupSafe`. + uv_snapshot!(context.filters(), context.pip_freeze().arg("--exclude").arg("MarkupSafe").arg("--prefix").arg(prefix.path()), @r###" + success: true + exit_code: 0 + ----- stdout ----- + tomli==2.0.1 + + ----- stderr ----- + "### + ); + + // Run `pip freeze --exclude MarkupSafe --exclude tomli`. + uv_snapshot!(context.filters(), context.pip_freeze().arg("--exclude").arg("MarkupSafe").arg("--exclude").arg("tomli").arg("--prefix").arg(prefix.path()), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + "### + ); +}