Avoid un-strict syncing by-default for build isolation (#6606)

## Summary

Closes https://github.com/astral-sh/uv/issues/6580.

Closes https://github.com/astral-sh/uv/issues/6599.
This commit is contained in:
Charlie Marsh 2024-08-26 14:04:58 -04:00 committed by GitHub
parent a7850d2a1c
commit 023acbe4b0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 130 additions and 18 deletions

View File

@ -2261,10 +2261,7 @@ pub struct SyncArgs {
/// Do not remove extraneous packages present in the environment.
///
/// When enabled, uv will make the minimum necessary changes to satisfy the requirements.
///
/// By default, syncing will remove any extraneous packages from the environment, unless
/// `--no-build-isolation` is enabled, in which case extra packages are considered necessary for
/// builds.
/// By default, syncing will remove any extraneous packages from the environment
#[arg(long, overrides_with("exact"), alias = "no-exact")]
pub inexact: bool,

View File

@ -621,6 +621,7 @@ impl PythonPinSettings {
}
}
}
/// The resolved settings to use for a `sync` invocation.
#[allow(clippy::struct_excessive_bools, dead_code)]
#[derive(Debug, Clone)]
@ -668,16 +669,6 @@ impl SyncSettings {
filesystem,
);
let exact = flag(exact, inexact).unwrap_or(true);
// By default, sync with exact semantics, unless the user set `--no-build-isolation`;
// otherwise, we'll end up removing build dependencies.
let modifications = if !exact || settings.no_build_isolation {
Modifications::Sufficient
} else {
Modifications::Exact
};
Self {
locked,
frozen,
@ -689,7 +680,11 @@ impl SyncSettings {
no_install_project,
no_install_workspace,
no_install_package,
modifications,
modifications: if flag(exact, inexact).unwrap_or(true) {
Modifications::Exact
} else {
Modifications::Sufficient
},
package,
python,
refresh: Refresh::from(refresh),

View File

@ -428,6 +428,7 @@ fn virtual_workspace_dev_dependencies() -> Result<()> {
Ok(())
}
/// Use a `pip install` step to pre-install build dependencies for `--no-build-isolation`.
#[test]
fn sync_build_isolation() -> Result<()> {
let context = TestContext::new("3.12");
@ -479,9 +480,17 @@ fn sync_build_isolation() -> Result<()> {
----- stderr -----
Resolved 2 packages in [TIME]
Prepared 2 packages in [TIME]
Uninstalled 7 packages in [TIME]
Installed 2 packages in [TIME]
- hatchling==1.22.4
- packaging==24.0
- pathspec==0.12.1
- pluggy==1.4.0
+ project==0.1.0 (from file://[TEMP_DIR]/)
- setuptools==69.2.0
+ source-distribution==0.0.1 (from https://files.pythonhosted.org/packages/10/1f/57aa4cce1b1abf6b433106676e15f9fa2c92ed2bd4cf77c3b50a9e9ac773/source_distribution-0.0.1.tar.gz)
- trove-classifiers==2024.3.3
- wheel==0.43.0
"###);
assert!(context.temp_dir.child("uv.lock").exists());
@ -489,6 +498,7 @@ fn sync_build_isolation() -> Result<()> {
Ok(())
}
/// Use a `pip install` step to pre-install build dependencies for `--no-build-isolation-package`.
#[test]
fn sync_build_isolation_package() -> Result<()> {
let context = TestContext::new("3.12");
@ -575,6 +585,118 @@ fn sync_build_isolation_package() -> Result<()> {
Ok(())
}
/// Use dedicated extra groups to install dependencies for `--no-build-isolation-package`.
#[test]
fn sync_build_isolation_extra() -> Result<()> {
let context = TestContext::new("3.12");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[project.optional-dependencies]
build = ["hatchling"]
compile = ["source-distribution @ https://files.pythonhosted.org/packages/10/1f/57aa4cce1b1abf6b433106676e15f9fa2c92ed2bd4cf77c3b50a9e9ac773/source_distribution-0.0.1.tar.gz"]
[build-system]
requires = ["setuptools >= 40.9.0"]
build-backend = "setuptools.build_meta"
[tool.uv]
no-build-isolation-package = ["source-distribution"]
"#,
)?;
// Running `uv sync` should fail for the `compile` extra.
let filters = std::iter::once((r"exit code: 1", "exit status: 1"))
.chain(context.filters())
.collect::<Vec<_>>();
uv_snapshot!(&filters, context.sync().arg("--extra").arg("compile"), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
Resolved 7 packages in [TIME]
error: Failed to prepare distributions
Caused by: Failed to fetch wheel: source-distribution @ https://files.pythonhosted.org/packages/10/1f/57aa4cce1b1abf6b433106676e15f9fa2c92ed2bd4cf77c3b50a9e9ac773/source_distribution-0.0.1.tar.gz
Caused by: Build backend failed to build wheel through `build_wheel()` with exit status: 1
--- stdout:
--- stderr:
Traceback (most recent call last):
File "<string>", line 8, in <module>
ModuleNotFoundError: No module named 'hatchling'
---
"###);
// Running `uv sync` with `--all-extras` should also fail.
uv_snapshot!(&filters, context.sync().arg("--all-extras"), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
Resolved 7 packages in [TIME]
error: Failed to prepare distributions
Caused by: Failed to fetch wheel: source-distribution @ https://files.pythonhosted.org/packages/10/1f/57aa4cce1b1abf6b433106676e15f9fa2c92ed2bd4cf77c3b50a9e9ac773/source_distribution-0.0.1.tar.gz
Caused by: Build backend failed to build wheel through `build_wheel()` with exit status: 1
--- stdout:
--- stderr:
Traceback (most recent call last):
File "<string>", line 8, in <module>
ModuleNotFoundError: No module named 'hatchling'
---
"###);
// Install the build dependencies.
uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("build"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 7 packages in [TIME]
Prepared 6 packages in [TIME]
Installed 6 packages in [TIME]
+ hatchling==1.22.4
+ packaging==24.0
+ pathspec==0.12.1
+ pluggy==1.4.0
+ project==0.1.0 (from file://[TEMP_DIR]/)
+ trove-classifiers==2024.3.3
"###);
// Running `uv sync` for the `compile` extra should succeed, and remove the build dependencies.
uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("compile"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 7 packages in [TIME]
Prepared 1 package in [TIME]
Uninstalled 5 packages in [TIME]
Installed 1 package in [TIME]
- hatchling==1.22.4
- packaging==24.0
- pathspec==0.12.1
- pluggy==1.4.0
+ source-distribution==0.0.1 (from https://files.pythonhosted.org/packages/10/1f/57aa4cce1b1abf6b433106676e15f9fa2c92ed2bd4cf77c3b50a9e9ac773/source_distribution-0.0.1.tar.gz)
- trove-classifiers==2024.3.3
"###);
assert!(context.temp_dir.child("uv.lock").exists());
Ok(())
}
/// Avoid using incompatible versions for build dependencies that are also part of the resolved
/// environment. This is a very subtle issue, but: when locking, we don't enforce platform
/// compatibility. So, if we reuse the resolver state to install, and the install itself has to

View File

@ -1098,9 +1098,7 @@ uv sync [OPTIONS]
</dd><dt><code>--inexact</code></dt><dd><p>Do not remove extraneous packages present in the environment.</p>
<p>When enabled, uv will make the minimum necessary changes to satisfy the requirements.</p>
<p>By default, syncing will remove any extraneous packages from the environment, unless <code>--no-build-isolation</code> is enabled, in which case extra packages are considered necessary for builds.</p>
<p>When enabled, uv will make the minimum necessary changes to satisfy the requirements. By default, syncing will remove any extraneous packages from the environment</p>
</dd><dt><code>--keyring-provider</code> <i>keyring-provider</i></dt><dd><p>Attempt to use <code>keyring</code> for authentication for index URLs.</p>