Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow uv pip sync to clear an environment with opt-in #4517

Merged
merged 5 commits into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

Loading