mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 13:25:00 +00:00
Allow uv pip sync
to clear an environment with opt-in (#4517)
Closes https://github.com/astral-sh/uv/issues/4516 Open to some deliberation about the opt-in strategy here.
This commit is contained in:
parent
d9f389a58d
commit
2c0cb6e021
8 changed files with 116 additions and 7 deletions
|
@ -826,6 +826,13 @@ pub struct PipSyncArgs {
|
||||||
#[arg(long, conflicts_with = "no_build")]
|
#[arg(long, conflicts_with = "no_build")]
|
||||||
pub only_binary: Option<Vec<PackageNameSpecifier>>,
|
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.,
|
/// The minimum Python version that should be supported by the requirements (e.g.,
|
||||||
/// `3.7` or `3.7.9`).
|
/// `3.7` or `3.7.9`).
|
||||||
///
|
///
|
||||||
|
|
|
@ -165,6 +165,7 @@ pub struct PipOptions {
|
||||||
pub extra: Option<Vec<ExtraName>>,
|
pub extra: Option<Vec<ExtraName>>,
|
||||||
pub all_extras: Option<bool>,
|
pub all_extras: Option<bool>,
|
||||||
pub no_deps: Option<bool>,
|
pub no_deps: Option<bool>,
|
||||||
|
pub allow_empty_requirements: Option<bool>,
|
||||||
pub resolution: Option<ResolutionMode>,
|
pub resolution: Option<ResolutionMode>,
|
||||||
pub prerelease: Option<PreReleaseMode>,
|
pub prerelease: Option<PreReleaseMode>,
|
||||||
pub output_file: Option<PathBuf>,
|
pub output_file: Option<PathBuf>,
|
||||||
|
|
|
@ -47,6 +47,7 @@ pub(crate) async fn pip_sync(
|
||||||
index_strategy: IndexStrategy,
|
index_strategy: IndexStrategy,
|
||||||
keyring_provider: KeyringProviderType,
|
keyring_provider: KeyringProviderType,
|
||||||
setup_py: SetupPyStrategy,
|
setup_py: SetupPyStrategy,
|
||||||
|
allow_empty_requirements: bool,
|
||||||
connectivity: Connectivity,
|
connectivity: Connectivity,
|
||||||
config_settings: &ConfigSettings,
|
config_settings: &ConfigSettings,
|
||||||
no_build_isolation: bool,
|
no_build_isolation: bool,
|
||||||
|
@ -104,11 +105,13 @@ pub(crate) async fn pip_sync(
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Validate that the requirements are non-empty.
|
// Validate that the requirements are non-empty.
|
||||||
|
if !allow_empty_requirements {
|
||||||
let num_requirements = requirements.len() + source_trees.len();
|
let num_requirements = requirements.len() + source_trees.len();
|
||||||
if num_requirements == 0 {
|
if num_requirements == 0 {
|
||||||
writeln!(printer.stderr(), "No requirements found")?;
|
writeln!(printer.stderr(), "No requirements found (hint: use `--allow-empty-requirements` to clear the environment)")?;
|
||||||
return Ok(ExitStatus::Success);
|
return Ok(ExitStatus::Success);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Detect the current Python interpreter.
|
// Detect the current Python interpreter.
|
||||||
let environment = PythonEnvironment::find(
|
let environment = PythonEnvironment::find(
|
||||||
|
|
|
@ -337,6 +337,7 @@ async fn run() -> Result<ExitStatus> {
|
||||||
args.settings.index_strategy,
|
args.settings.index_strategy,
|
||||||
args.settings.keyring_provider,
|
args.settings.keyring_provider,
|
||||||
args.settings.setup_py,
|
args.settings.setup_py,
|
||||||
|
args.settings.allow_empty_requirements,
|
||||||
globals.connectivity,
|
globals.connectivity,
|
||||||
&args.settings.config_setting,
|
&args.settings.config_setting,
|
||||||
args.settings.no_build_isolation,
|
args.settings.no_build_isolation,
|
||||||
|
|
|
@ -759,6 +759,8 @@ impl PipSyncSettings {
|
||||||
no_break_system_packages,
|
no_break_system_packages,
|
||||||
target,
|
target,
|
||||||
prefix,
|
prefix,
|
||||||
|
allow_empty_requirements,
|
||||||
|
no_allow_empty_requirements,
|
||||||
legacy_setup_py,
|
legacy_setup_py,
|
||||||
no_legacy_setup_py,
|
no_legacy_setup_py,
|
||||||
no_build_isolation,
|
no_build_isolation,
|
||||||
|
@ -791,15 +793,19 @@ impl PipSyncSettings {
|
||||||
exclude_newer,
|
exclude_newer,
|
||||||
target,
|
target,
|
||||||
prefix,
|
prefix,
|
||||||
|
require_hashes: flag(require_hashes, no_require_hashes),
|
||||||
no_build: flag(no_build, build),
|
no_build: flag(no_build, build),
|
||||||
no_binary,
|
no_binary,
|
||||||
only_binary,
|
only_binary,
|
||||||
no_build_isolation: flag(no_build_isolation, build_isolation),
|
allow_empty_requirements: flag(
|
||||||
strict: flag(strict, no_strict),
|
allow_empty_requirements,
|
||||||
|
no_allow_empty_requirements,
|
||||||
|
),
|
||||||
legacy_setup_py: flag(legacy_setup_py, no_legacy_setup_py),
|
legacy_setup_py: flag(legacy_setup_py, no_legacy_setup_py),
|
||||||
|
no_build_isolation: flag(no_build_isolation, build_isolation),
|
||||||
python_version,
|
python_version,
|
||||||
python_platform,
|
python_platform,
|
||||||
require_hashes: flag(require_hashes, no_require_hashes),
|
strict: flag(strict, no_strict),
|
||||||
concurrent_builds: env(env::CONCURRENT_BUILDS),
|
concurrent_builds: env(env::CONCURRENT_BUILDS),
|
||||||
concurrent_downloads: env(env::CONCURRENT_DOWNLOADS),
|
concurrent_downloads: env(env::CONCURRENT_DOWNLOADS),
|
||||||
concurrent_installs: env(env::CONCURRENT_INSTALLS),
|
concurrent_installs: env(env::CONCURRENT_INSTALLS),
|
||||||
|
@ -1629,6 +1635,7 @@ pub(crate) struct PipSettings {
|
||||||
pub(crate) keyring_provider: KeyringProviderType,
|
pub(crate) keyring_provider: KeyringProviderType,
|
||||||
pub(crate) no_build_isolation: bool,
|
pub(crate) no_build_isolation: bool,
|
||||||
pub(crate) build_options: BuildOptions,
|
pub(crate) build_options: BuildOptions,
|
||||||
|
pub(crate) allow_empty_requirements: bool,
|
||||||
pub(crate) strict: bool,
|
pub(crate) strict: bool,
|
||||||
pub(crate) dependency_mode: DependencyMode,
|
pub(crate) dependency_mode: DependencyMode,
|
||||||
pub(crate) resolution: ResolutionMode,
|
pub(crate) resolution: ResolutionMode,
|
||||||
|
@ -1688,6 +1695,7 @@ impl PipSettings {
|
||||||
extra,
|
extra,
|
||||||
all_extras,
|
all_extras,
|
||||||
no_deps,
|
no_deps,
|
||||||
|
allow_empty_requirements,
|
||||||
resolution,
|
resolution,
|
||||||
prerelease,
|
prerelease,
|
||||||
output_file,
|
output_file,
|
||||||
|
@ -1814,6 +1822,10 @@ impl PipSettings {
|
||||||
.generate_hashes
|
.generate_hashes
|
||||||
.combine(generate_hashes)
|
.combine(generate_hashes)
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
|
allow_empty_requirements: args
|
||||||
|
.allow_empty_requirements
|
||||||
|
.combine(allow_empty_requirements)
|
||||||
|
.unwrap_or_default(),
|
||||||
setup_py: if args
|
setup_py: if args
|
||||||
.legacy_setup_py
|
.legacy_setup_py
|
||||||
.combine(legacy_setup_py)
|
.combine(legacy_setup_py)
|
||||||
|
|
|
@ -250,6 +250,68 @@ fn noop() -> Result<()> {
|
||||||
Ok(())
|
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
|
/// Install a package into a virtual environment, then install the same package into a different
|
||||||
/// virtual environment.
|
/// virtual environment.
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -124,6 +124,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
|
||||||
no_binary: None,
|
no_binary: None,
|
||||||
no_build: None,
|
no_build: None,
|
||||||
},
|
},
|
||||||
|
allow_empty_requirements: false,
|
||||||
strict: false,
|
strict: false,
|
||||||
dependency_mode: Transitive,
|
dependency_mode: Transitive,
|
||||||
resolution: LowestDirect,
|
resolution: LowestDirect,
|
||||||
|
@ -255,6 +256,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
|
||||||
no_binary: None,
|
no_binary: None,
|
||||||
no_build: None,
|
no_build: None,
|
||||||
},
|
},
|
||||||
|
allow_empty_requirements: false,
|
||||||
strict: false,
|
strict: false,
|
||||||
dependency_mode: Transitive,
|
dependency_mode: Transitive,
|
||||||
resolution: Highest,
|
resolution: Highest,
|
||||||
|
@ -387,6 +389,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
|
||||||
no_binary: None,
|
no_binary: None,
|
||||||
no_build: None,
|
no_build: None,
|
||||||
},
|
},
|
||||||
|
allow_empty_requirements: false,
|
||||||
strict: false,
|
strict: false,
|
||||||
dependency_mode: Transitive,
|
dependency_mode: Transitive,
|
||||||
resolution: Highest,
|
resolution: Highest,
|
||||||
|
@ -551,6 +554,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
|
||||||
no_binary: None,
|
no_binary: None,
|
||||||
no_build: None,
|
no_build: None,
|
||||||
},
|
},
|
||||||
|
allow_empty_requirements: false,
|
||||||
strict: false,
|
strict: false,
|
||||||
dependency_mode: Transitive,
|
dependency_mode: Transitive,
|
||||||
resolution: LowestDirect,
|
resolution: LowestDirect,
|
||||||
|
@ -661,6 +665,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
|
||||||
no_binary: None,
|
no_binary: None,
|
||||||
no_build: None,
|
no_build: None,
|
||||||
},
|
},
|
||||||
|
allow_empty_requirements: false,
|
||||||
strict: false,
|
strict: false,
|
||||||
dependency_mode: Transitive,
|
dependency_mode: Transitive,
|
||||||
resolution: Highest,
|
resolution: Highest,
|
||||||
|
@ -803,6 +808,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
|
||||||
no_binary: None,
|
no_binary: None,
|
||||||
no_build: None,
|
no_build: None,
|
||||||
},
|
},
|
||||||
|
allow_empty_requirements: false,
|
||||||
strict: false,
|
strict: false,
|
||||||
dependency_mode: Transitive,
|
dependency_mode: Transitive,
|
||||||
resolution: LowestDirect,
|
resolution: LowestDirect,
|
||||||
|
@ -982,6 +988,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
|
||||||
no_binary: None,
|
no_binary: None,
|
||||||
no_build: None,
|
no_build: None,
|
||||||
},
|
},
|
||||||
|
allow_empty_requirements: false,
|
||||||
strict: false,
|
strict: false,
|
||||||
dependency_mode: Transitive,
|
dependency_mode: Transitive,
|
||||||
resolution: Highest,
|
resolution: Highest,
|
||||||
|
@ -1160,6 +1167,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
|
||||||
no_binary: None,
|
no_binary: None,
|
||||||
no_build: None,
|
no_build: None,
|
||||||
},
|
},
|
||||||
|
allow_empty_requirements: false,
|
||||||
strict: false,
|
strict: false,
|
||||||
dependency_mode: Transitive,
|
dependency_mode: Transitive,
|
||||||
resolution: Highest,
|
resolution: Highest,
|
||||||
|
@ -1311,6 +1319,7 @@ fn resolve_find_links() -> anyhow::Result<()> {
|
||||||
no_binary: None,
|
no_binary: None,
|
||||||
no_build: None,
|
no_build: None,
|
||||||
},
|
},
|
||||||
|
allow_empty_requirements: false,
|
||||||
strict: false,
|
strict: false,
|
||||||
dependency_mode: Transitive,
|
dependency_mode: Transitive,
|
||||||
resolution: Highest,
|
resolution: Highest,
|
||||||
|
@ -1443,6 +1452,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
|
||||||
no_binary: None,
|
no_binary: None,
|
||||||
no_build: None,
|
no_build: None,
|
||||||
},
|
},
|
||||||
|
allow_empty_requirements: false,
|
||||||
strict: false,
|
strict: false,
|
||||||
dependency_mode: Transitive,
|
dependency_mode: Transitive,
|
||||||
resolution: LowestDirect,
|
resolution: LowestDirect,
|
||||||
|
@ -1613,6 +1623,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
|
||||||
no_binary: None,
|
no_binary: None,
|
||||||
no_build: None,
|
no_build: None,
|
||||||
},
|
},
|
||||||
|
allow_empty_requirements: false,
|
||||||
strict: false,
|
strict: false,
|
||||||
dependency_mode: Transitive,
|
dependency_mode: Transitive,
|
||||||
resolution: Highest,
|
resolution: Highest,
|
||||||
|
@ -1766,6 +1777,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
|
||||||
no_binary: None,
|
no_binary: None,
|
||||||
no_build: None,
|
no_build: None,
|
||||||
},
|
},
|
||||||
|
allow_empty_requirements: false,
|
||||||
strict: false,
|
strict: false,
|
||||||
dependency_mode: Transitive,
|
dependency_mode: Transitive,
|
||||||
resolution: LowestDirect,
|
resolution: LowestDirect,
|
||||||
|
@ -1898,6 +1910,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
|
||||||
no_binary: None,
|
no_binary: None,
|
||||||
no_build: None,
|
no_build: None,
|
||||||
},
|
},
|
||||||
|
allow_empty_requirements: false,
|
||||||
strict: false,
|
strict: false,
|
||||||
dependency_mode: Transitive,
|
dependency_mode: Transitive,
|
||||||
resolution: LowestDirect,
|
resolution: LowestDirect,
|
||||||
|
@ -2013,6 +2026,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
|
||||||
no_binary: None,
|
no_binary: None,
|
||||||
no_build: None,
|
no_build: None,
|
||||||
},
|
},
|
||||||
|
allow_empty_requirements: false,
|
||||||
strict: false,
|
strict: false,
|
||||||
dependency_mode: Transitive,
|
dependency_mode: Transitive,
|
||||||
resolution: LowestDirect,
|
resolution: LowestDirect,
|
||||||
|
@ -2128,6 +2142,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
|
||||||
no_binary: None,
|
no_binary: None,
|
||||||
no_build: None,
|
no_build: None,
|
||||||
},
|
},
|
||||||
|
allow_empty_requirements: false,
|
||||||
strict: false,
|
strict: false,
|
||||||
dependency_mode: Transitive,
|
dependency_mode: Transitive,
|
||||||
resolution: Highest,
|
resolution: Highest,
|
||||||
|
@ -2245,6 +2260,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
|
||||||
no_binary: None,
|
no_binary: None,
|
||||||
no_build: None,
|
no_build: None,
|
||||||
},
|
},
|
||||||
|
allow_empty_requirements: false,
|
||||||
strict: false,
|
strict: false,
|
||||||
dependency_mode: Transitive,
|
dependency_mode: Transitive,
|
||||||
resolution: LowestDirect,
|
resolution: LowestDirect,
|
||||||
|
@ -2387,6 +2403,7 @@ fn resolve_poetry_toml() -> anyhow::Result<()> {
|
||||||
no_binary: None,
|
no_binary: None,
|
||||||
no_build: None,
|
no_build: None,
|
||||||
},
|
},
|
||||||
|
allow_empty_requirements: false,
|
||||||
strict: false,
|
strict: false,
|
||||||
dependency_mode: Transitive,
|
dependency_mode: Transitive,
|
||||||
resolution: LowestDirect,
|
resolution: LowestDirect,
|
||||||
|
|
6
uv.schema.json
generated
6
uv.schema.json
generated
|
@ -449,6 +449,12 @@
|
||||||
"null"
|
"null"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"allow-empty-requirements": {
|
||||||
|
"type": [
|
||||||
|
"boolean",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
"annotation-style": {
|
"annotation-style": {
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
{
|
{
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue