Skip to content

Commit

Permalink
Allow uv pip sync to clear an environment with opt-in (#4517)
Browse files Browse the repository at this point in the history
Closes #4516

Open to some deliberation about the opt-in strategy here.
  • Loading branch information
zanieb authored Jul 2, 2024
1 parent d9f389a commit 2c0cb6e
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 7 deletions.
7 changes: 7 additions & 0 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,13 @@ pub struct PipSyncArgs {
#[arg(long, conflicts_with = "no_build")]
pub only_binary: Option<Vec<PackageNameSpecifier>>,

/// Allow sync of empty requirements, which will clear the environment of all packages.
#[arg(long, overrides_with("no_allow_empty_requirements"))]
pub allow_empty_requirements: bool,

#[arg(long, overrides_with("allow_empty_requirements"))]
pub no_allow_empty_requirements: bool,

/// The minimum Python version that should be supported by the requirements (e.g.,
/// `3.7` or `3.7.9`).
///
Expand Down
1 change: 1 addition & 0 deletions crates/uv-settings/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ pub struct PipOptions {
pub extra: Option<Vec<ExtraName>>,
pub all_extras: Option<bool>,
pub no_deps: Option<bool>,
pub allow_empty_requirements: Option<bool>,
pub resolution: Option<ResolutionMode>,
pub prerelease: Option<PreReleaseMode>,
pub output_file: Option<PathBuf>,
Expand Down
11 changes: 7 additions & 4 deletions crates/uv/src/commands/pip/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ pub(crate) async fn pip_sync(
index_strategy: IndexStrategy,
keyring_provider: KeyringProviderType,
setup_py: SetupPyStrategy,
allow_empty_requirements: bool,
connectivity: Connectivity,
config_settings: &ConfigSettings,
no_build_isolation: bool,
Expand Down Expand Up @@ -104,10 +105,12 @@ pub(crate) async fn pip_sync(
.await?;

// Validate that the requirements are non-empty.
let num_requirements = requirements.len() + source_trees.len();
if num_requirements == 0 {
writeln!(printer.stderr(), "No requirements found")?;
return Ok(ExitStatus::Success);
if !allow_empty_requirements {
let num_requirements = requirements.len() + source_trees.len();
if num_requirements == 0 {
writeln!(printer.stderr(), "No requirements found (hint: use `--allow-empty-requirements` to clear the environment)")?;
return Ok(ExitStatus::Success);
}
}

// Detect the current Python interpreter.
Expand Down
1 change: 1 addition & 0 deletions crates/uv/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ async fn run() -> Result<ExitStatus> {
args.settings.index_strategy,
args.settings.keyring_provider,
args.settings.setup_py,
args.settings.allow_empty_requirements,
globals.connectivity,
&args.settings.config_setting,
args.settings.no_build_isolation,
Expand Down
18 changes: 15 additions & 3 deletions crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,8 @@ impl PipSyncSettings {
no_break_system_packages,
target,
prefix,
allow_empty_requirements,
no_allow_empty_requirements,
legacy_setup_py,
no_legacy_setup_py,
no_build_isolation,
Expand Down Expand Up @@ -791,15 +793,19 @@ impl PipSyncSettings {
exclude_newer,
target,
prefix,
require_hashes: flag(require_hashes, no_require_hashes),
no_build: flag(no_build, build),
no_binary,
only_binary,
no_build_isolation: flag(no_build_isolation, build_isolation),
strict: flag(strict, no_strict),
allow_empty_requirements: flag(
allow_empty_requirements,
no_allow_empty_requirements,
),
legacy_setup_py: flag(legacy_setup_py, no_legacy_setup_py),
no_build_isolation: flag(no_build_isolation, build_isolation),
python_version,
python_platform,
require_hashes: flag(require_hashes, no_require_hashes),
strict: flag(strict, no_strict),
concurrent_builds: env(env::CONCURRENT_BUILDS),
concurrent_downloads: env(env::CONCURRENT_DOWNLOADS),
concurrent_installs: env(env::CONCURRENT_INSTALLS),
Expand Down Expand Up @@ -1629,6 +1635,7 @@ pub(crate) struct PipSettings {
pub(crate) keyring_provider: KeyringProviderType,
pub(crate) no_build_isolation: bool,
pub(crate) build_options: BuildOptions,
pub(crate) allow_empty_requirements: bool,
pub(crate) strict: bool,
pub(crate) dependency_mode: DependencyMode,
pub(crate) resolution: ResolutionMode,
Expand Down Expand Up @@ -1688,6 +1695,7 @@ impl PipSettings {
extra,
all_extras,
no_deps,
allow_empty_requirements,
resolution,
prerelease,
output_file,
Expand Down Expand Up @@ -1814,6 +1822,10 @@ impl PipSettings {
.generate_hashes
.combine(generate_hashes)
.unwrap_or_default(),
allow_empty_requirements: args
.allow_empty_requirements
.combine(allow_empty_requirements)
.unwrap_or_default(),
setup_py: if args
.legacy_setup_py
.combine(legacy_setup_py)
Expand Down
62 changes: 62 additions & 0 deletions crates/uv/tests/pip_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,68 @@ fn noop() -> Result<()> {
Ok(())
}

/// Attempt to sync an empty set of requirements.
#[test]
fn pip_sync_empty() -> Result<()> {
let context = TestContext::new("3.12");

let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.touch()?;

uv_snapshot!(context.pip_sync()
.arg("requirements.txt"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
warning: Requirements file requirements.txt does not contain any dependencies
No requirements found (hint: use `--allow-empty-requirements` to clear the environment)
"###
);

uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--allow-empty-requirements"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
warning: Requirements file requirements.txt does not contain any dependencies
Resolved 0 packages in [TIME]
Audited 0 packages in [TIME]
"###
);

// Install a package.
requirements_txt.write_str("iniconfig==2.0.0")?;
context
.pip_sync()
.arg("requirements.txt")
.assert()
.success();

// Now, syncing should remove the package.
requirements_txt.write_str("")?;
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--allow-empty-requirements"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
warning: Requirements file requirements.txt does not contain any dependencies
Resolved 0 packages in [TIME]
Uninstalled 1 package in [TIME]
- iniconfig==2.0.0
"###
);

Ok(())
}

/// Install a package into a virtual environment, then install the same package into a different
/// virtual environment.
#[test]
Expand Down
17 changes: 17 additions & 0 deletions crates/uv/tests/show_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
no_binary: None,
no_build: None,
},
allow_empty_requirements: false,
strict: false,
dependency_mode: Transitive,
resolution: LowestDirect,
Expand Down Expand Up @@ -255,6 +256,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
no_binary: None,
no_build: None,
},
allow_empty_requirements: false,
strict: false,
dependency_mode: Transitive,
resolution: Highest,
Expand Down Expand Up @@ -387,6 +389,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
no_binary: None,
no_build: None,
},
allow_empty_requirements: false,
strict: false,
dependency_mode: Transitive,
resolution: Highest,
Expand Down Expand Up @@ -551,6 +554,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
no_binary: None,
no_build: None,
},
allow_empty_requirements: false,
strict: false,
dependency_mode: Transitive,
resolution: LowestDirect,
Expand Down Expand Up @@ -661,6 +665,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
no_binary: None,
no_build: None,
},
allow_empty_requirements: false,
strict: false,
dependency_mode: Transitive,
resolution: Highest,
Expand Down Expand Up @@ -803,6 +808,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
no_binary: None,
no_build: None,
},
allow_empty_requirements: false,
strict: false,
dependency_mode: Transitive,
resolution: LowestDirect,
Expand Down Expand Up @@ -982,6 +988,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
no_binary: None,
no_build: None,
},
allow_empty_requirements: false,
strict: false,
dependency_mode: Transitive,
resolution: Highest,
Expand Down Expand Up @@ -1160,6 +1167,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
no_binary: None,
no_build: None,
},
allow_empty_requirements: false,
strict: false,
dependency_mode: Transitive,
resolution: Highest,
Expand Down Expand Up @@ -1311,6 +1319,7 @@ fn resolve_find_links() -> anyhow::Result<()> {
no_binary: None,
no_build: None,
},
allow_empty_requirements: false,
strict: false,
dependency_mode: Transitive,
resolution: Highest,
Expand Down Expand Up @@ -1443,6 +1452,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
no_binary: None,
no_build: None,
},
allow_empty_requirements: false,
strict: false,
dependency_mode: Transitive,
resolution: LowestDirect,
Expand Down Expand Up @@ -1613,6 +1623,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
no_binary: None,
no_build: None,
},
allow_empty_requirements: false,
strict: false,
dependency_mode: Transitive,
resolution: Highest,
Expand Down Expand Up @@ -1766,6 +1777,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
no_binary: None,
no_build: None,
},
allow_empty_requirements: false,
strict: false,
dependency_mode: Transitive,
resolution: LowestDirect,
Expand Down Expand Up @@ -1898,6 +1910,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
no_binary: None,
no_build: None,
},
allow_empty_requirements: false,
strict: false,
dependency_mode: Transitive,
resolution: LowestDirect,
Expand Down Expand Up @@ -2013,6 +2026,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
no_binary: None,
no_build: None,
},
allow_empty_requirements: false,
strict: false,
dependency_mode: Transitive,
resolution: LowestDirect,
Expand Down Expand Up @@ -2128,6 +2142,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
no_binary: None,
no_build: None,
},
allow_empty_requirements: false,
strict: false,
dependency_mode: Transitive,
resolution: Highest,
Expand Down Expand Up @@ -2245,6 +2260,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
no_binary: None,
no_build: None,
},
allow_empty_requirements: false,
strict: false,
dependency_mode: Transitive,
resolution: LowestDirect,
Expand Down Expand Up @@ -2387,6 +2403,7 @@ fn resolve_poetry_toml() -> anyhow::Result<()> {
no_binary: None,
no_build: None,
},
allow_empty_requirements: false,
strict: false,
dependency_mode: Transitive,
resolution: LowestDirect,
Expand Down
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 2c0cb6e

Please sign in to comment.