Support `pip install --exact` (#8044)

## Summary

Resolves #8041 

## Test Plan

`cargo test`
This commit is contained in:
Ahmed Ilyas 2024-10-09 15:31:28 +02:00 committed by GitHub
parent 1e6c64074d
commit 1764a95d39
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 87 additions and 3 deletions

View File

@ -1637,6 +1637,18 @@ pub struct PipInstallArgs {
#[arg(long)]
pub python_platform: Option<TargetTriple>,
/// Do not remove extraneous packages present in the environment.
#[arg(long, overrides_with("exact"), alias = "no-exact", hide = true)]
pub inexact: bool,
/// Perform an exact sync, removing extraneous packages.
///
/// By default, installing will make the minimum necessary changes to satisfy the requirements.
/// When enabled, uv will update the environment to exactly match the requirements, removing
/// packages that are not included in the requirements.
#[arg(long, overrides_with("inexact"))]
pub exact: bool,
/// Validate the Python environment after completing the installation, to detect and with
/// missing dependencies or other issues.
#[arg(long, overrides_with("no_strict"))]

View File

@ -67,6 +67,7 @@ pub(crate) async fn pip_install(
no_build_isolation: bool,
no_build_isolation_package: Vec<PackageName>,
build_options: BuildOptions,
modifications: Modifications,
python_version: Option<PythonVersion>,
python_platform: Option<TargetTriple>,
strict: bool,
@ -408,7 +409,7 @@ pub(crate) async fn pip_install(
operations::install(
&resolution,
site_packages,
Modifications::Sufficient,
modifications,
&reinstall,
&build_options,
link_mode,

View File

@ -494,6 +494,7 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
args.settings.no_build_isolation,
args.settings.no_build_isolation_package,
args.settings.build_options,
args.modifications,
args.settings.python_version,
args.settings.python_platform,
args.settings.strict,

View File

@ -1307,6 +1307,7 @@ pub(crate) struct PipInstallSettings {
pub(crate) dry_run: bool,
pub(crate) constraints_from_workspace: Vec<Requirement>,
pub(crate) overrides_from_workspace: Vec<Requirement>,
pub(crate) modifications: Modifications,
pub(crate) refresh: Refresh,
pub(crate) settings: PipSettings,
}
@ -1320,16 +1321,16 @@ impl PipInstallSettings {
editable,
constraint,
r#override,
build_constraint,
extra,
all_extras,
no_all_extras,
build_constraint,
installer,
refresh,
no_deps,
deps,
require_hashes,
no_require_hashes,
installer,
verify_hashes,
no_verify_hashes,
python,
@ -1345,6 +1346,8 @@ impl PipInstallSettings {
only_binary,
python_version,
python_platform,
inexact,
exact,
strict,
no_strict,
dry_run,
@ -1398,6 +1401,11 @@ impl PipInstallSettings {
dry_run,
constraints_from_workspace,
overrides_from_workspace,
modifications: if flag(exact, inexact).unwrap_or(false) {
Modifications::Exact
} else {
Modifications::Sufficient
},
refresh: Refresh::from(refresh),
settings: PipSettings::combine(
PipOptions {

View File

@ -748,6 +748,64 @@ fn reinstall_incomplete() -> Result<()> {
Ok(())
}
#[test]
fn exact_install_removes_extraneous_packages() -> Result<()> {
let context = TestContext::new("3.12").with_filtered_counts();
// Install flask
uv_snapshot!(context.filters(), context.pip_install()
.arg("flask"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved [N] packages in [TIME]
Prepared [N] packages in [TIME]
Installed [N] packages in [TIME]
+ blinker==1.7.0
+ click==8.1.7
+ flask==3.0.2
+ itsdangerous==2.1.2
+ jinja2==3.1.3
+ markupsafe==2.1.5
+ werkzeug==3.0.1
"###
);
// Install anyio with exact flag removes flask and flask dependencies.
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("anyio==3.7.0")?;
uv_snapshot!(context.filters(), context.pip_install()
.arg("--exact")
.arg("-r")
.arg("requirements.txt"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved [N] packages in [TIME]
Prepared [N] packages in [TIME]
Uninstalled [N] packages in [TIME]
Installed [N] packages in [TIME]
+ anyio==3.7.0
- blinker==1.7.0
- click==8.1.7
- flask==3.0.2
+ idna==3.6
- itsdangerous==2.1.2
- jinja2==3.1.3
- markupsafe==2.1.5
+ sniffio==1.3.1
- werkzeug==3.0.1
"###
);
Ok(())
}
/// Like `pip`, we (unfortunately) allow incompatible environments.
#[test]
fn allow_incompatibilities() -> Result<()> {

View File

@ -5444,6 +5444,10 @@ uv pip install [OPTIONS] <PACKAGE|--requirement <REQUIREMENT>|--editable <EDITAB
</dd><dt><code>--editable</code>, <code>-e</code> <i>editable</i></dt><dd><p>Install the editable package based on the provided local file path</p>
</dd><dt><code>--exact</code></dt><dd><p>Perform an exact sync, removing extraneous packages.</p>
<p>By default, installing will make the minimum necessary changes to satisfy the requirements. When enabled, uv will update the environment to exactly match the requirements, removing packages that are not included in the requirements.</p>
</dd><dt><code>--exclude-newer</code> <i>exclude-newer</i></dt><dd><p>Limit candidate packages to those that were uploaded prior to the given date.</p>
<p>Accepts both RFC 3339 timestamps (e.g., <code>2006-12-02T02:07:43Z</code>) and local dates in the same format (e.g., <code>2006-12-02</code>) in your system&#8217;s configured time zone.</p>