diff --git a/crates/uv/src/commands/pip_install.rs b/crates/uv/src/commands/pip_install.rs index ec44703d0..8cee0bd01 100644 --- a/crates/uv/src/commands/pip_install.rs +++ b/crates/uv/src/commands/pip_install.rs @@ -42,7 +42,7 @@ use crate::requirements::{ExtrasSpecification, RequirementsSource, RequirementsS use super::Upgrade; /// Install packages into the current environment. -#[allow(clippy::too_many_arguments)] +#[allow(clippy::too_many_arguments, clippy::fn_params_excessive_bools)] pub(crate) async fn pip_install( requirements: &[RequirementsSource], constraints: &[RequirementsSource], @@ -65,6 +65,7 @@ pub(crate) async fn pip_install( exclude_newer: Option>, python: Option, system: bool, + break_system_packages: bool, cache: Cache, printer: Printer, ) -> Result { @@ -123,18 +124,22 @@ pub(crate) async fn pip_install( // If the environment is externally managed, abort. if let Some(externally_managed) = venv.interpreter().is_externally_managed() { - return if let Some(error) = externally_managed.into_error() { - Err(anyhow::anyhow!( - "The interpreter at {} is externally managed, and indicates the following:\n\n{}\n\nConsider creating a virtual environment with `uv venv`.", - venv.root().simplified_display().cyan(), - textwrap::indent(&error, " ").green(), - )) + if break_system_packages { + debug!("Ignoring externally managed environment due to `--break-system-packages`"); } else { - Err(anyhow::anyhow!( - "The interpreter at {} is externally managed. Instead, create a virtual environment with `uv venv`.", - venv.root().simplified_display().cyan() - )) - }; + return if let Some(error) = externally_managed.into_error() { + Err(anyhow::anyhow!( + "The interpreter at {} is externally managed, and indicates the following:\n\n{}\n\nConsider creating a virtual environment with `uv venv`.", + venv.root().simplified_display().cyan(), + textwrap::indent(&error, " ").green(), + )) + } else { + Err(anyhow::anyhow!( + "The interpreter at {} is externally managed. Instead, create a virtual environment with `uv venv`.", + venv.root().simplified_display().cyan() + )) + }; + } } let _lock = venv.lock()?; diff --git a/crates/uv/src/commands/pip_sync.rs b/crates/uv/src/commands/pip_sync.rs index a61551537..f18728396 100644 --- a/crates/uv/src/commands/pip_sync.rs +++ b/crates/uv/src/commands/pip_sync.rs @@ -28,7 +28,7 @@ use crate::printer::Printer; use crate::requirements::{RequirementsSource, RequirementsSpecification}; /// Install a set of locked requirements into the current Python environment. -#[allow(clippy::too_many_arguments)] +#[allow(clippy::too_many_arguments, clippy::fn_params_excessive_bools)] pub(crate) async fn pip_sync( sources: &[RequirementsSource], reinstall: &Reinstall, @@ -43,6 +43,7 @@ pub(crate) async fn pip_sync( strict: bool, python: Option, system: bool, + break_system_packages: bool, cache: Cache, printer: Printer, ) -> Result { @@ -90,18 +91,22 @@ pub(crate) async fn pip_sync( // If the environment is externally managed, abort. if let Some(externally_managed) = venv.interpreter().is_externally_managed() { - return if let Some(error) = externally_managed.into_error() { - Err(anyhow::anyhow!( - "The interpreter at {} is externally managed, and indicates the following:\n\n{}\n\nConsider creating a virtual environment with `uv venv`.", - venv.root().simplified_display().cyan(), - textwrap::indent(&error, " ").green(), - )) + if break_system_packages { + debug!("Ignoring externally managed environment due to `--break-system-packages`"); } else { - Err(anyhow::anyhow!( - "The interpreter at {} is externally managed. Instead, create a virtual environment with `uv venv`.", - venv.root().simplified_display().cyan() - )) - }; + return if let Some(error) = externally_managed.into_error() { + Err(anyhow::anyhow!( + "The interpreter at {} is externally managed, and indicates the following:\n\n{}\n\nConsider creating a virtual environment with `uv venv`.", + venv.root().simplified_display().cyan(), + textwrap::indent(&error, " ").green(), + )) + } else { + Err(anyhow::anyhow!( + "The interpreter at {} is externally managed. Instead, create a virtual environment with `uv venv`.", + venv.root().simplified_display().cyan() + )) + }; + } } let _lock = venv.lock()?; diff --git a/crates/uv/src/commands/pip_uninstall.rs b/crates/uv/src/commands/pip_uninstall.rs index 007915b56..a8c27d1bc 100644 --- a/crates/uv/src/commands/pip_uninstall.rs +++ b/crates/uv/src/commands/pip_uninstall.rs @@ -20,6 +20,7 @@ pub(crate) async fn pip_uninstall( sources: &[RequirementsSource], python: Option, system: bool, + break_system_packages: bool, cache: Cache, connectivity: Connectivity, printer: Printer, @@ -62,18 +63,22 @@ pub(crate) async fn pip_uninstall( // If the environment is externally managed, abort. if let Some(externally_managed) = venv.interpreter().is_externally_managed() { - return if let Some(error) = externally_managed.into_error() { - Err(anyhow::anyhow!( - "The interpreter at {} is externally managed, and indicates the following:\n\n{}\n\nConsider creating a virtual environment with `uv venv`.", - venv.root().simplified_display().cyan(), - textwrap::indent(&error, " ").green(), - )) + if break_system_packages { + debug!("Ignoring externally managed environment due to `--break-system-packages`"); } else { - Err(anyhow::anyhow!( - "The interpreter at {} is externally managed. Instead, create a virtual environment with `uv venv`.", - venv.root().simplified_display().cyan() - )) - }; + return if let Some(error) = externally_managed.into_error() { + Err(anyhow::anyhow!( + "The interpreter at {} is externally managed, and indicates the following:\n\n{}\n\nConsider creating a virtual environment with `uv venv`.", + venv.root().simplified_display().cyan(), + textwrap::indent(&error, " ").green(), + )) + } else { + Err(anyhow::anyhow!( + "The interpreter at {} is externally managed. Instead, create a virtual environment with `uv venv`.", + venv.root().simplified_display().cyan() + )) + }; + } } let _lock = venv.lock()?; diff --git a/crates/uv/src/main.rs b/crates/uv/src/main.rs index d0330d137..cb8c642ce 100644 --- a/crates/uv/src/main.rs +++ b/crates/uv/src/main.rs @@ -516,7 +516,13 @@ struct PipSyncArgs { /// `python3.10` on Linux and macOS. /// - `python3.10` or `python.exe` looks for a binary with the given name in `PATH`. /// - `/home/ferris/.local/bin/python3.10` uses the exact Python at the given path. - #[clap(long, short, verbatim_doc_comment, conflicts_with = "system")] + #[clap( + long, + short, + verbatim_doc_comment, + conflicts_with = "system", + group = "discovery" + )] python: Option, /// Install packages into the system Python. @@ -527,9 +533,18 @@ struct PipSyncArgs { /// /// WARNING: `--system` is intended for use in continuous integration (CI) environments and /// should be used with caution, as it can modify the system Python installation. - #[clap(long, conflicts_with = "python")] + #[clap(long, conflicts_with = "python", group = "discovery")] system: bool, + /// Allow `uv` to modify an `EXTERNALLY-MANAGED` Python installation. + /// + /// WARNING: `--break-system-packages` is intended for use in continuous integration (CI) + /// environments, when installing into Python installations that are managed by an external + /// package manager, like `apt`. It should be used with caution, as such Python installations + /// explicitly recommend against modifications by other package managers (like `uv` or `pip`). + #[clap(long, requires = "discovery")] + break_system_packages: bool, + /// Use legacy `setuptools` behavior when building source distributions without a /// `pyproject.toml`. #[clap(long)] @@ -741,7 +756,13 @@ struct PipInstallArgs { /// `python3.10` on Linux and macOS. /// - `python3.10` or `python.exe` looks for a binary with the given name in `PATH`. /// - `/home/ferris/.local/bin/python3.10` uses the exact Python at the given path. - #[clap(long, short, verbatim_doc_comment, conflicts_with = "system")] + #[clap( + long, + short, + verbatim_doc_comment, + conflicts_with = "system", + group = "discovery" + )] python: Option, /// Install packages into the system Python. @@ -752,9 +773,18 @@ struct PipInstallArgs { /// /// WARNING: `--system` is intended for use in continuous integration (CI) environments and /// should be used with caution, as it can modify the system Python installation. - #[clap(long, conflicts_with = "python")] + #[clap(long, conflicts_with = "python", group = "discovery")] system: bool, + /// Allow `uv` to modify an `EXTERNALLY-MANAGED` Python installation. + /// + /// WARNING: `--break-system-packages` is intended for use in continuous integration (CI) + /// environments, when installing into Python installations that are managed by an external + /// package manager, like `apt`. It should be used with caution, as such Python installations + /// explicitly recommend against modifications by other package managers (like `uv` or `pip`). + #[clap(long, requires = "discovery")] + break_system_packages: bool, + /// Use legacy `setuptools` behavior when building source distributions without a /// `pyproject.toml`. #[clap(long)] @@ -848,7 +878,13 @@ struct PipUninstallArgs { /// `python3.10` on Linux and macOS. /// - `python3.10` or `python.exe` looks for a binary with the given name in `PATH`. /// - `/home/ferris/.local/bin/python3.10` uses the exact Python at the given path. - #[clap(long, short, verbatim_doc_comment, conflicts_with = "system")] + #[clap( + long, + short, + verbatim_doc_comment, + conflicts_with = "system", + group = "discovery" + )] python: Option, /// Use the system Python to uninstall packages. @@ -859,9 +895,18 @@ struct PipUninstallArgs { /// /// WARNING: `--system` is intended for use in continuous integration (CI) environments and /// should be used with caution, as it can modify the system Python installation. - #[clap(long, conflicts_with = "python")] + #[clap(long, conflicts_with = "python", group = "discovery")] system: bool, + /// Allow `uv` to modify an `EXTERNALLY-MANAGED` Python installation. + /// + /// WARNING: `--break-system-packages` is intended for use in continuous integration (CI) + /// environments, when installing into Python installations that are managed by an external + /// package manager, like `apt`. It should be used with caution, as such Python installations + /// explicitly recommend against modifications by other package managers (like `uv` or `pip`). + #[clap(long, requires = "discovery")] + break_system_packages: bool, + /// Run offline, i.e., without accessing the network. #[arg(global = true, long)] offline: bool, @@ -886,7 +931,13 @@ struct PipFreezeArgs { /// `python3.10` on Linux and macOS. /// - `python3.10` or `python.exe` looks for a binary with the given name in `PATH`. /// - `/home/ferris/.local/bin/python3.10` uses the exact Python at the given path. - #[clap(long, short, verbatim_doc_comment, conflicts_with = "system")] + #[clap( + long, + short, + verbatim_doc_comment, + conflicts_with = "system", + group = "discovery" + )] python: Option, /// List packages for the system Python. @@ -898,7 +949,7 @@ struct PipFreezeArgs { /// /// WARNING: `--system` is intended for use in continuous integration (CI) environments and /// should be used with caution. - #[clap(long, conflicts_with = "python")] + #[clap(long, conflicts_with = "python", group = "discovery")] system: bool, } @@ -937,7 +988,13 @@ struct PipListArgs { /// `python3.10` on Linux and macOS. /// - `python3.10` or `python.exe` looks for a binary with the given name in `PATH`. /// - `/home/ferris/.local/bin/python3.10` uses the exact Python at the given path. - #[clap(long, short, verbatim_doc_comment, conflicts_with = "system")] + #[clap( + long, + short, + verbatim_doc_comment, + conflicts_with = "system", + group = "discovery" + )] python: Option, /// List packages for the system Python. @@ -949,7 +1006,7 @@ struct PipListArgs { /// /// WARNING: `--system` is intended for use in continuous integration (CI) environments and /// should be used with caution. - #[clap(long, conflicts_with = "python")] + #[clap(long, conflicts_with = "python", group = "discovery")] system: bool, } @@ -975,7 +1032,13 @@ struct PipShowArgs { /// `python3.10` on Linux and macOS. /// - `python3.10` or `python.exe` looks for a binary with the given name in `PATH`. /// - `/home/ferris/.local/bin/python3.10` uses the exact Python at the given path. - #[clap(long, short, verbatim_doc_comment, conflicts_with = "system")] + #[clap( + long, + short, + verbatim_doc_comment, + conflicts_with = "system", + group = "discovery" + )] python: Option, /// List packages for the system Python. @@ -987,7 +1050,7 @@ struct PipShowArgs { /// /// WARNING: `--system` is intended for use in continuous integration (CI) environments and /// should be used with caution. - #[clap(long, conflicts_with = "python")] + #[clap(long, conflicts_with = "python", group = "discovery")] system: bool, } @@ -1004,7 +1067,13 @@ struct VenvArgs { /// /// Note that this is different from `--python-version` in `pip compile`, which takes `3.10` or `3.10.13` and /// doesn't look for a Python interpreter on disk. - #[clap(long, short, verbatim_doc_comment, conflicts_with = "system")] + #[clap( + long, + short, + verbatim_doc_comment, + conflicts_with = "system", + group = "discovery" + )] python: Option, /// Use the system Python to uninstall packages. @@ -1015,7 +1084,7 @@ struct VenvArgs { /// /// WARNING: `--system` is intended for use in continuous integration (CI) environments and /// should be used with caution, as it can modify the system Python installation. - #[clap(long, conflicts_with = "python")] + #[clap(long, conflicts_with = "python", group = "discovery")] system: bool, /// Install seed packages (`pip`, `setuptools`, and `wheel`) into the virtual environment. @@ -1347,6 +1416,7 @@ async fn run() -> Result { args.strict, args.python, args.system, + args.break_system_packages, cache, printer, ) @@ -1440,6 +1510,7 @@ async fn run() -> Result { args.exclude_newer, args.python, args.system, + args.break_system_packages, cache, printer, ) @@ -1463,6 +1534,7 @@ async fn run() -> Result { &sources, args.python, args.system, + args.break_system_packages, cache, if args.offline { Connectivity::Offline