From b5617198f35c89a33f073c677ed9164bb661509c Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sat, 17 Feb 2024 13:53:38 -0500 Subject: [PATCH] Avoid propagating top-level options to sub-resolutions (#1607) ## Summary It's incorrect to pass the resolution and dependency mode down to the `BuildDispatch`, since it means that we'll use `--no-deps` when building source distributions. If you set resolution to `lowest`, it also means we end up using (e.g.) the lowest version of `wheel`, which also doesn't make sense. It's fine to pass `--exclude-newer`. Closes https://github.com/astral-sh/uv/issues/1355. Closes https://github.com/astral-sh/uv/issues/1563. --- crates/uv/src/commands/pip_compile.rs | 16 +++++++------- crates/uv/src/commands/pip_install.rs | 17 +++++++------- crates/uv/src/commands/venv.rs | 3 +-- crates/uv/tests/pip_compile.rs | 32 +++++++++++++++++++++++++++ crates/uv/tests/pip_install.rs | 29 ++++++++++++++++++++++++ 5 files changed, 79 insertions(+), 18 deletions(-) diff --git a/crates/uv/src/commands/pip_compile.rs b/crates/uv/src/commands/pip_compile.rs index 99e5e1464..1d699a56b 100644 --- a/crates/uv/src/commands/pip_compile.rs +++ b/crates/uv/src/commands/pip_compile.rs @@ -210,13 +210,6 @@ pub(crate) async fn pip_compile( // Track in-flight downloads, builds, etc., across resolutions. let in_flight = InFlight::default(); - let options = OptionsBuilder::new() - .resolution_mode(resolution_mode) - .prerelease_mode(prerelease_mode) - .dependency_mode(dependency_mode) - .exclude_newer(exclude_newer) - .build(); - let build_dispatch = BuildDispatch::new( &client, &cache, @@ -230,7 +223,7 @@ pub(crate) async fn pip_compile( no_build, &NoBinary::None, ) - .with_options(options); + .with_options(OptionsBuilder::new().exclude_newer(exclude_newer).build()); // Build the editables and add their requirements let editable_metadata = if editables.is_empty() { @@ -286,6 +279,13 @@ pub(crate) async fn pip_compile( editable_metadata, ); + let options = OptionsBuilder::new() + .resolution_mode(resolution_mode) + .prerelease_mode(prerelease_mode) + .dependency_mode(dependency_mode) + .exclude_newer(exclude_newer) + .build(); + // Resolve the dependencies. let resolver = Resolver::new( manifest, diff --git a/crates/uv/src/commands/pip_install.rs b/crates/uv/src/commands/pip_install.rs index d3b05ddc0..0ac77ea8a 100644 --- a/crates/uv/src/commands/pip_install.rs +++ b/crates/uv/src/commands/pip_install.rs @@ -163,13 +163,6 @@ pub(crate) async fn pip_install( // Track in-flight downloads, builds, etc., across resolutions. let in_flight = InFlight::default(); - let options = OptionsBuilder::new() - .resolution_mode(resolution_mode) - .prerelease_mode(prerelease_mode) - .dependency_mode(dependency_mode) - .exclude_newer(exclude_newer) - .build(); - let resolve_dispatch = BuildDispatch::new( &client, &cache, @@ -183,7 +176,7 @@ pub(crate) async fn pip_install( no_build, no_binary, ) - .with_options(options); + .with_options(OptionsBuilder::new().exclude_newer(exclude_newer).build()); // Build all editable distributions. The editables are shared between resolution and // installation, and should live for the duration of the command. If an editable is already @@ -205,6 +198,13 @@ pub(crate) async fn pip_install( .await? }; + let options = OptionsBuilder::new() + .resolution_mode(resolution_mode) + .prerelease_mode(prerelease_mode) + .dependency_mode(dependency_mode) + .exclude_newer(exclude_newer) + .build(); + // Resolve the requirements. let resolution = match resolve( requirements, @@ -258,6 +258,7 @@ pub(crate) async fn pip_install( no_build, no_binary, ) + .with_options(OptionsBuilder::new().exclude_newer(exclude_newer).build()) }; // Sync the environment. diff --git a/crates/uv/src/commands/venv.rs b/crates/uv/src/commands/venv.rs index 0aee85cf5..2b00e5b58 100644 --- a/crates/uv/src/commands/venv.rs +++ b/crates/uv/src/commands/venv.rs @@ -144,7 +144,6 @@ async fn venv_impl( let in_flight = InFlight::default(); // Prep the build context. - let options = OptionsBuilder::new().exclude_newer(exclude_newer).build(); let build_dispatch = BuildDispatch::new( &client, cache, @@ -158,7 +157,7 @@ async fn venv_impl( &NoBuild::All, &NoBinary::None, ) - .with_options(options); + .with_options(OptionsBuilder::new().exclude_newer(exclude_newer).build()); // Resolve the seed packages. let resolution = build_dispatch diff --git a/crates/uv/tests/pip_compile.rs b/crates/uv/tests/pip_compile.rs index 4de554e0c..f9bad50a1 100644 --- a/crates/uv/tests/pip_compile.rs +++ b/crates/uv/tests/pip_compile.rs @@ -575,6 +575,38 @@ fn compile_python_37() -> Result<()> { Ok(()) } +/// Resolve a source distribution with `--resolution=lowest-direct`, to ensure that the build +/// requirements aren't resolved at their lowest compatible version. +#[test] +fn compile_sdist_resolution_lowest() -> Result<()> { + let context = TestContext::new("3.12"); + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("anyio @ https://files.pythonhosted.org/packages/2d/b8/7333d87d5f03247215d86a86362fd3e324111788c6cdd8d2e6196a6ba833/anyio-4.2.0.tar.gz")?; + + uv_snapshot!(context.compile() + .arg("requirements.in") + .arg("--resolution=lowest-direct") + .arg("--python-version") + .arg("3.12"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv v[VERSION] via the following command: + # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2023-11-18T12:00:00Z requirements.in --resolution=lowest-direct --python-version 3.12 + anyio @ https://files.pythonhosted.org/packages/2d/b8/7333d87d5f03247215d86a86362fd3e324111788c6cdd8d2e6196a6ba833/anyio-4.2.0.tar.gz + idna==3.4 + # via anyio + sniffio==1.3.0 + # via anyio + + ----- stderr ----- + Resolved 3 packages in [TIME] + "### + ); + + Ok(()) +} + /// Resolve a specific version of Black against an invalid Python version. #[test] fn compile_python_invalid_version() -> Result<()> { diff --git a/crates/uv/tests/pip_install.rs b/crates/uv/tests/pip_install.rs index 9a7d4f78c..077277bca 100644 --- a/crates/uv/tests/pip_install.rs +++ b/crates/uv/tests/pip_install.rs @@ -1129,3 +1129,32 @@ fn install_pinned_polars_invalid_metadata() { context.assert_command("import polars").success(); } + +/// Install a source distribution with `--resolution=lowest-direct`, to ensure that the build +/// requirements aren't resolved at their lowest compatible version. +#[test] +fn install_sdist_resolution_lowest() -> Result<()> { + let context = TestContext::new("3.12"); + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("anyio @ https://files.pythonhosted.org/packages/2d/b8/7333d87d5f03247215d86a86362fd3e324111788c6cdd8d2e6196a6ba833/anyio-4.2.0.tar.gz")?; + + uv_snapshot!(command(&context) + .arg("-r") + .arg("requirements.in") + .arg("--resolution=lowest-direct"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 3 packages in [TIME] + Downloaded 3 packages in [TIME] + Installed 3 packages in [TIME] + + anyio==4.2.0 (from https://files.pythonhosted.org/packages/2d/b8/7333d87d5f03247215d86a86362fd3e324111788c6cdd8d2e6196a6ba833/anyio-4.2.0.tar.gz) + + idna==3.4 + + sniffio==1.3.0 + "### + ); + + Ok(()) +}