From 091e808397c16d3904c2da9caead50d12cedcf52 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 15 Apr 2026 16:47:00 -0500 Subject: [PATCH 01/70] Silence warnings on empty `SSL_CERT_DIR` directory (#19018) Closes #19013 --------- Co-authored-by: Claude --- crates/uv-client/src/tls.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/uv-client/src/tls.rs b/crates/uv-client/src/tls.rs index eec176e4f5042..d61528dbf9ac6 100644 --- a/crates/uv-client/src/tls.rs +++ b/crates/uv-client/src/tls.rs @@ -355,13 +355,14 @@ impl Certificates { } if certs.0.is_empty() { - warn_user_once!( + // Unlike `SSL_CERT_FILE`, it's plausible for this to be intentionally set to an + // empty directory that a user _could_ put certificates in. + warn!( "Ignoring `SSL_CERT_DIR`. No valid certificates found in: {}.", existing .iter() .map(Simplified::simplified_display) .join(", ") - .cyan() ); return None; } From afd114f574262216aa8263bb31a983b84f48d6a3 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 15 Apr 2026 16:47:25 -0500 Subject: [PATCH 02/70] Skip Pyodide pre-release builds during version sync (#19010) Co-authored-by: Claude --- crates/uv-python/fetch-download-metadata.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/uv-python/fetch-download-metadata.py b/crates/uv-python/fetch-download-metadata.py index f54acf9c29b33..822869c5a5959 100755 --- a/crates/uv-python/fetch-download-metadata.py +++ b/crates/uv-python/fetch-download-metadata.py @@ -525,6 +525,11 @@ async def _fetch_downloads(self) -> list[PythonDownload]: results = {} for release in releases: + # Skip prereleases + # https://github.com/astral-sh/uv/pull/18958#discussion_r3082735525 + if release.get("prerelease"): + continue + pyodide_version = release["tag_name"] meta = metadata.get(pyodide_version, None) if meta is None: From 8736c7b5b43b127edce555da29fe5bceec4acacc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 04:28:47 +0000 Subject: [PATCH 03/70] Sync latest Python releases (#18958) Automated update for Python releases. Co-authored-by: zanieb <2586601+zanieb@users.noreply.github.com> --- crates/uv-dev/src/generate_sysconfig_mappings.rs | 4 ++-- crates/uv-python/src/sysconfig/generated_mappings.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/uv-dev/src/generate_sysconfig_mappings.rs b/crates/uv-dev/src/generate_sysconfig_mappings.rs index 3cb7b6eaed414..d93643b64d253 100644 --- a/crates/uv-dev/src/generate_sysconfig_mappings.rs +++ b/crates/uv-dev/src/generate_sysconfig_mappings.rs @@ -11,7 +11,7 @@ use crate::ROOT_DIR; use crate::generate_all::Mode; /// Contains current supported targets -const TARGETS_YML_URL: &str = "https://raw.githubusercontent.com/astral-sh/python-build-standalone/refs/tags/20260408/cpython-unix/targets.yml"; +const TARGETS_YML_URL: &str = "https://raw.githubusercontent.com/astral-sh/python-build-standalone/refs/tags/20260414/cpython-unix/targets.yml"; #[derive(clap::Args)] pub(crate) struct Args { @@ -130,7 +130,7 @@ async fn generate() -> Result { output.push_str("//! DO NOT EDIT\n"); output.push_str("//!\n"); output.push_str("//! Generated with `cargo run dev generate-sysconfig-metadata`\n"); - output.push_str("//! Targets from \n"); + output.push_str("//! Targets from \n"); output.push_str("//!\n"); // Disable clippy/fmt diff --git a/crates/uv-python/src/sysconfig/generated_mappings.rs b/crates/uv-python/src/sysconfig/generated_mappings.rs index 94e4ea9b2312b..edf93bd5983cd 100644 --- a/crates/uv-python/src/sysconfig/generated_mappings.rs +++ b/crates/uv-python/src/sysconfig/generated_mappings.rs @@ -1,7 +1,7 @@ //! DO NOT EDIT //! //! Generated with `cargo run dev generate-sysconfig-metadata` -//! Targets from +//! Targets from //! #![allow(clippy::all)] #![cfg_attr(any(), rustfmt::skip)] From 9c0eec664d58281c532e91318a9ed5935b4b7602 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 16 Apr 2026 09:05:05 -0500 Subject: [PATCH 04/70] Add test case for lock when `exclude-newer` is missing but `exclude-newer-span` is present (#19021) Co-authored-by: Claude --- .../tests/it/lock_exclude_newer_relative.rs | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/crates/uv/tests/it/lock_exclude_newer_relative.rs b/crates/uv/tests/it/lock_exclude_newer_relative.rs index 9317a6192cc06..0863874302249 100644 --- a/crates/uv/tests/it/lock_exclude_newer_relative.rs +++ b/crates/uv/tests/it/lock_exclude_newer_relative.rs @@ -1198,6 +1198,121 @@ fn lock_exclude_newer_relative_values() -> Result<()> { Ok(()) } +#[test] +fn lock_exclude_newer_relative_no_timestamp_in_lockfile() -> Result<()> { + let context = uv_test::test_context!("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 = ["idna"] + + [tool.uv] + exclude-newer = "3 weeks" + "#, + )?; + + let current_timestamp = "2024-05-01T00:00:00Z"; + uv_snapshot!(context.filters(), context + .lock() + .env_remove(EnvVars::UV_EXCLUDE_NEWER) + .env(EnvVars::UV_TEST_CURRENT_TIMESTAMP, current_timestamp), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + "); + + let lock = context.read("uv.lock"); + assert_snapshot!(lock, @r#" + version = 1 + revision = 3 + requires-python = ">=3.12" + + [options] + exclude-newer = "2024-04-10T00:00:00Z" + exclude-newer-span = "P3W" + + [[package]] + name = "idna" + version = "3.6" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", size = 175426, upload-time = "2023-11-25T15:40:54.902Z" } + wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f", size = 61567, upload-time = "2023-11-25T15:40:52.604Z" }, + ] + + [[package]] + name = "project" + version = "0.1.0" + source = { virtual = "." } + dependencies = [ + { name = "idna" }, + ] + + [package.metadata] + requires-dist = [{ name = "idna" }] + "#); + + // Manually remove the exclude-newer timestamp from the lockfile, leaving the span. + let lock = lock.replace("exclude-newer = \"2024-04-10T00:00:00Z\"\n", ""); + context.temp_dir.child("uv.lock").write_str(&lock)?; + + // The lockfile now has no exclude-newer, but `pyproject.toml` still configures one, + // so the resolver detects "addition of global exclude newer" and re-resolves. + uv_snapshot!(context.filters(), context + .lock() + .env_remove(EnvVars::UV_EXCLUDE_NEWER) + .env(EnvVars::UV_TEST_CURRENT_TIMESTAMP, current_timestamp), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolving despite existing lockfile due to addition of global exclude newer 2024-04-10T00:00:00Z + Resolved 2 packages in [TIME] + "); + + // The lockfile should have exclude-newer restored. + let lock = context.read("uv.lock"); + assert_snapshot!(lock, @r#" + version = 1 + revision = 3 + requires-python = ">=3.12" + + [options] + exclude-newer = "2024-04-10T00:00:00Z" + exclude-newer-span = "P3W" + + [[package]] + name = "idna" + version = "3.6" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", size = 175426, upload-time = "2023-11-25T15:40:54.902Z" } + wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f", size = 61567, upload-time = "2023-11-25T15:40:52.604Z" }, + ] + + [[package]] + name = "project" + version = "0.1.0" + source = { virtual = "." } + dependencies = [ + { name = "idna" }, + ] + + [package.metadata] + requires-dist = [{ name = "idna" }] + "#); + + Ok(()) +} + /// Lock with various relative exclude newer value formats in a `pyproject.toml`. #[test] fn lock_exclude_newer_relative_values_pyproject() -> Result<()> { From 58be01e8dff4735d4ed4dafafabc1f7f8206be64 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Fri, 17 Apr 2026 08:10:43 -0500 Subject: [PATCH 05/70] Add a lock equality assertion for `uv lock --check` with a relative exclude newer (#19031) --- crates/uv/tests/it/lock_exclude_newer_relative.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/uv/tests/it/lock_exclude_newer_relative.rs b/crates/uv/tests/it/lock_exclude_newer_relative.rs index 0863874302249..1de52f286db53 100644 --- a/crates/uv/tests/it/lock_exclude_newer_relative.rs +++ b/crates/uv/tests/it/lock_exclude_newer_relative.rs @@ -89,6 +89,8 @@ fn lock_exclude_newer_relative() -> Result<()> { Resolved 2 packages in [TIME] "); + assert_eq!(context.read("uv.lock"), lock); + // Changing the span to 2 weeks should cause a new resolution. // 2 weeks before 2024-05-01 is 2024-04-17, which is after idna 3.7 (released 2024-04-11). uv_snapshot!(context.filters(), context From d8fb331e0573ed42cfea46aa4e3ad212ae48db3d Mon Sep 17 00:00:00 2001 From: Kevin Stillhammer Date: Fri, 17 Apr 2026 15:55:45 +0200 Subject: [PATCH 06/70] Bump astral-sh/setup-uv version in docs (#19030) ## Summary As the title says ## Test Plan N/A --- docs/guides/integration/github.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/guides/integration/github.md b/docs/guides/integration/github.md index c54332b2ab076..9266e49848b20 100644 --- a/docs/guides/integration/github.md +++ b/docs/guides/integration/github.md @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@v6 - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 ``` It is considered best practice to pin to a specific uv version, e.g., with: @@ -44,7 +44,7 @@ jobs: - uses: actions/checkout@v6 - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: # Install a specific version of uv. version: "0.11.7" @@ -66,7 +66,7 @@ jobs: - uses: actions/checkout@v6 - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 - name: Set up Python run: uv python install @@ -98,7 +98,7 @@ jobs: python-version-file: ".python-version" - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 ``` Or, specify the `pyproject.toml` file to ignore the pin and use the latest version compatible with @@ -121,7 +121,7 @@ jobs: python-version-file: "pyproject.toml" - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 ``` ## Multiple Python versions @@ -146,7 +146,7 @@ jobs: - uses: actions/checkout@v6 - name: Install uv and set the Python version - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: python-version: ${{ matrix.python-version }} ``` @@ -187,7 +187,7 @@ jobs: - uses: actions/checkout@v6 - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 - name: Install the project run: uv sync --locked --all-extras --dev @@ -212,7 +212,7 @@ persisting the cache: ```yaml title="example.yml" - name: Enable caching - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: enable-cache: true ``` @@ -377,7 +377,7 @@ jobs: - name: Checkout uses: actions/checkout@v6 - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 - name: Install Python 3.13 run: uv python install 3.13 - name: Build From 69e1f5f1b8d2f184e1f518cf8f433ae2e998b4fa Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Fri, 17 Apr 2026 11:24:49 -0400 Subject: [PATCH 07/70] Fix Python variant tagging in the Windows registry (#19012) ## Summary This adds a regression test and fix for #18795. I ran the test and confirmed reproduction before implementing the fix. The underlying bug here happens only on Windows, and only when exercising the PEP 514 Python installation registration pathway (which the integration tests disable by default, since it involves global mutable state that leaks between tests). The bug itself is just an imprecision in how we compute the "tag" for the Python entry -- we weren't including the variant (the `t` in `3.14t`), so two distinct installs (`3.14` and `3.14t`) would end up with the same registry tag. For an end user, this surfaces as Python installation entries missing when running `uv python list`. One thing to note about the test here is that it _does_ exercise the Windows registry pathway, which means that it intentionally bypasses the guardrail around global mutations in the integration tests. This is "fine" in the sense that there are on other tests observing that state at the moment, but I think it's a risk in terms of isolation (in the sense that devs who run our integration tests will actually observe global changes to their Python installations, plus any failure in the test means we won't clean up our global changes). Two options there: - I could try and harden/isolate the registry mutation pathways a bit more, e.g. we could add `UV_DEV_WINDOWS_REGISTRY_COMPANY_KEY` or something like that to do some more test-level isolation of HKCU writes. This still modifies global state, but at least it'll be more namespaced. - I could remove the integration test entirely, now that we've confirmed that the fix itself works. This leaves us without coverage, but given that the fix itself is ~2 lines that might be acceptable. Fixes #18795. ## Test Plan This PR includes a regression test. --------- Signed-off-by: William Woodruff --- .github/workflows/test.yml | 2 +- crates/uv-python/src/windows_registry.rs | 13 +++- crates/uv/Cargo.toml | 4 ++ crates/uv/tests/it/python_install.rs | 88 ++++++++++++++++++++++++ 4 files changed, 105 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c9edc18de8870..803cb8731faad 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -250,7 +250,7 @@ jobs: cargo nextest run \ --cargo-profile fast-build \ --no-default-features \ - --features test-python,test-pypi,test-python-managed,native-auth,windows-native \ + --features test-python,test-pypi,test-python-managed,test-windows-registry,native-auth,windows-native \ --workspace \ --profile ci-windows \ --partition hash:${{ matrix.partition }}/3 diff --git a/crates/uv-python/src/windows_registry.rs b/crates/uv-python/src/windows_registry.rs index 5aa09a16278a9..72714e569f5ae 100644 --- a/crates/uv-python/src/windows_registry.rs +++ b/crates/uv-python/src/windows_registry.rs @@ -200,7 +200,18 @@ fn write_registry_entry( } fn registry_python_tag(key: &PythonInstallationKey) -> String { - format!("{}{}", key.implementation().pretty(), key.version()) + // Include the variant's executable suffix (e.g., "t" for freethreaded) in the + // registry tag so that variant (freethreaded, debug, etc.) installations of the same version + // get distinct registry entries. This suffix can be empty. + // + // See: https://github.com/astral-sh/uv/issues/18795 + let variant_suffix = key.variant().executable_suffix(); + format!( + "{}{}{}", + key.implementation().pretty(), + key.version(), + variant_suffix, + ) } /// Remove requested Python entries from the Windows Registry (PEP 514). diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml index 177dbc3740eab..4941be12de25d 100644 --- a/crates/uv/Cargo.toml +++ b/crates/uv/Cargo.toml @@ -208,6 +208,10 @@ test-python-managed = [] test-slow = [] # Includes test cases that require ecosystem packages test-ecosystem = [] +# Includes test cases that write to the Windows registry. These tests mutate +# global state (the Windows registry). +# We don't run these tests by default locally; the CI for Windows enables them. +test-windows-registry = [] # Build uvw binary on Windows windows-gui-bin = [] diff --git a/crates/uv/tests/it/python_install.rs b/crates/uv/tests/it/python_install.rs index a46f5ae1bcb98..dbbfac16e6549 100644 --- a/crates/uv/tests/it/python_install.rs +++ b/crates/uv/tests/it/python_install.rs @@ -1226,6 +1226,94 @@ fn python_install_freethreaded() { "); } +/// Test that installing both GIL and free-threaded variants of the same Python version +/// doesn't cause managed installation entries to disappear from `uv python list` +/// on Windows when registry discovery is enabled. +/// +/// Regression test for . +/// +/// IMPORTANT: this test writes to the shared `HKCU` registry. The trailing uninstall is +/// best-effort cleanup; panics will leak entries. This is fine for now since this is the only +/// test exercising the registry pathway, but adding more will probably require isolation. +#[cfg(all(windows, feature = "test-windows-registry"))] +#[test] +fn python_install_freethreaded_and_gil_list() { + use assert_cmd::assert::OutputAssertExt; + + let context = uv_test::test_context_with_versions!(&[]) + .with_filtered_python_keys() + .with_filtered_latest_python_versions() + .with_managed_python_dirs() + .with_python_download_cache() + .with_filtered_python_install_bin() + .with_filtered_python_names() + .with_filtered_exe_suffix() + .with_collapsed_whitespace(); + + // Install both the GIL and free-threaded versions, with registry enabled + context + .python_install() + .arg("3.13") + .env(EnvVars::UV_PYTHON_INSTALL_REGISTRY, "1") + .assert() + .success(); + context + .python_install() + .arg("--preview") + .arg("3.13t") + .env(EnvVars::UV_PYTHON_INSTALL_REGISTRY, "1") + .assert() + .success(); + + // List installed versions with registry discovery enabled. + // We remove UV_TEST_PYTHON_PATH to enable registry discovery (it's skipped when set), + // and use `--managed-python --only-installed` to exclude unrelated system Pythons. + // + // Both the GIL and freethreaded variants should show entries from: + // - The registry (patch-versioned managed directory path) + // - The search path (bin trampoline) + // - Managed discovery (minor-version junction path) + uv_snapshot!(context.filters(), context.python_list() + .arg("3.13") + .arg("--only-installed") + .arg("--managed-python") + .env_remove(EnvVars::UV_TEST_PYTHON_PATH) + .env(EnvVars::UV_PYTHON_INSTALL_REGISTRY, "1"), @" + success: true + exit_code: 0 + ----- stdout ----- + cpython-3.13.[LATEST]-[PLATFORM] managed/cpython-3.13.[LATEST]-[PLATFORM]/[INSTALL-BIN]/[PYTHON] + cpython-3.13.[LATEST]-[PLATFORM] [BIN]/[INSTALL-BIN]/[PYTHON] + cpython-3.13.[LATEST]-[PLATFORM] managed/cpython-3.13-[PLATFORM]/[INSTALL-BIN]/[PYTHON] + + ----- stderr ----- + "); + + uv_snapshot!(context.filters(), context.python_list() + .arg("3.13t") + .arg("--only-installed") + .arg("--managed-python") + .env_remove(EnvVars::UV_TEST_PYTHON_PATH) + .env(EnvVars::UV_PYTHON_INSTALL_REGISTRY, "1"), @" + success: true + exit_code: 0 + ----- stdout ----- + cpython-3.13.[LATEST]+freethreaded-[PLATFORM] managed/cpython-3.13.[LATEST]+freethreaded-[PLATFORM]/[INSTALL-BIN]/[PYTHON] + cpython-3.13.[LATEST]+freethreaded-[PLATFORM] [BIN]/python3.13t + cpython-3.13.[LATEST]+freethreaded-[PLATFORM] managed/cpython-3.13+freethreaded-[PLATFORM]/[INSTALL-BIN]/[PYTHON] + + ----- stderr ----- + "); + + // Clean up registry entries + context + .python_uninstall() + .arg("--all") + .env(EnvVars::UV_PYTHON_INSTALL_REGISTRY, "1") + .assert() + .success(); +} + #[test] fn python_upgrade_not_allowed() { let context = uv_test::test_context_with_versions!(&[]) From 91a51cbb1390b50dc827e0bbd7c8e56f6f502dd7 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Fri, 17 Apr 2026 11:42:01 -0500 Subject: [PATCH 08/70] Only show the version number in `uv self version --short` (#19019) Simplify the `--short` output format for `uv self version` to display only the version string (e.g., "0.5.1") without commit info or target triple. See https://github.com/astral-sh/uv/pull/18520#issuecomment-4237301352 Co-authored-by: Claude --- crates/uv-cli/src/version.rs | 7 +++++++ crates/uv/src/commands/project/version.rs | 2 +- crates/uv/tests/it/version.rs | 7 ++----- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/crates/uv-cli/src/version.rs b/crates/uv-cli/src/version.rs index d3954ba1395ae..26c80b8d315ff 100644 --- a/crates/uv-cli/src/version.rs +++ b/crates/uv-cli/src/version.rs @@ -56,6 +56,13 @@ impl ProjectVersionInfo { } } +impl SelfVersionInfo { + /// Returns just the version string (e.g., "0.5.1"), without commit info or target triple. + pub fn version(&self) -> &str { + &self.version + } +} + impl fmt::Display for SelfVersionInfo { /// Formatted version information: "[+] ([ ])" /// diff --git a/crates/uv/src/commands/project/version.rs b/crates/uv/src/commands/project/version.rs index 41f441917ce37..97e9919ac0d08 100644 --- a/crates/uv/src/commands/project/version.rs +++ b/crates/uv/src/commands/project/version.rs @@ -50,7 +50,7 @@ pub(crate) fn self_version( match output_format { VersionFormat::Text => { if short { - writeln!(printer.stdout(), "{}", version_info.cyan())?; + writeln!(printer.stdout(), "{}", version_info.version().cyan())?; } else { writeln!(printer.stdout(), "uv {}", version_info.cyan())?; } diff --git a/crates/uv/tests/it/version.rs b/crates/uv/tests/it/version.rs index 0ba78161120a0..49278aeeafcba 100644 --- a/crates/uv/tests/it/version.rs +++ b/crates/uv/tests/it/version.rs @@ -2289,17 +2289,14 @@ fn self_version_short() -> Result<()> { let filters = context .filters() .into_iter() - .chain([( - r"\d+\.\d+\.\d+(-alpha\.\d+)?(\+\d+)?( \(.*\))?", - r"[VERSION] ([COMMIT] DATE)", - )]) + .chain([(r"\d+\.\d+\.\d+(-alpha\.\d+)?(\+\d+)?", r"[VERSION]")]) .collect::>(); uv_snapshot!(filters, context.self_version() .arg("--short"), @" success: true exit_code: 0 ----- stdout ----- - [VERSION] ([COMMIT] DATE) + [VERSION] ----- stderr ----- "); From 07f61551b23586862b367dc3d71424edaf637fef Mon Sep 17 00:00:00 2001 From: Zsolt Dollenstein Date: Fri, 17 Apr 2026 18:40:30 +0100 Subject: [PATCH 09/70] Fetch uv from Astral mirror during self-update (#18682) --- Cargo.lock | 1 + crates/uv/Cargo.toml | 1 + crates/uv/src/commands/self_update.rs | 702 +++++++++++++++++++++++++- crates/uv/tests/it/self_update.rs | 96 ++++ 4 files changed, 790 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 304cbd2185c54..78308f631be70 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5773,6 +5773,7 @@ dependencies = [ "dotenvy", "dunce", "embed-manifest", + "etcetera", "filetime", "flate2", "fs-err", diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml index 4941be12de25d..8b32021571a4b 100644 --- a/crates/uv/Cargo.toml +++ b/crates/uv/Cargo.toml @@ -79,6 +79,7 @@ ctrlc = { workspace = true } diskus = { workspace = true } dotenvy = { workspace = true } dunce = { workspace = true } +etcetera = { workspace = true } fs-err = { workspace = true, features = ["tokio"] } futures = { workspace = true } http = { workspace = true } diff --git a/crates/uv/src/commands/self_update.rs b/crates/uv/src/commands/self_update.rs index 30517602b0c47..998b26ee551e4 100644 --- a/crates/uv/src/commands/self_update.rs +++ b/crates/uv/src/commands/self_update.rs @@ -1,19 +1,37 @@ use std::fmt::Write; +use std::path::{Path, PathBuf}; use std::str::FromStr; +use std::time::{Duration, SystemTimeError}; use anyhow::{Context, Result}; -use axoupdater::{AxoUpdater, AxoupdateError, ReleaseSource, ReleaseSourceType, UpdateRequest}; +use axoupdater::{ + AxoUpdater, AxoupdateError, ReleaseSource, ReleaseSourceType, UpdateRequest, + app_name_to_env_var, +}; use owo_colors::OwoColorize; +use serde::Deserialize; +use tempfile::TempDir; +use thiserror::Error; +use tokio::process::Command; use tracing::{debug, warn}; +use url::Url; use uv_bin_install::{Binary, find_matching_version}; -use uv_client::BaseClientBuilder; +use uv_client::{BaseClientBuilder, RetriableError, WrappedReqwestError, fetch_with_url_fallback}; use uv_fs::Simplified; use uv_pep440::{Version as Pep440Version, VersionSpecifier, VersionSpecifiers}; +use uv_redacted::DisplaySafeUrl; use uv_static::EnvVars; use crate::commands::ExitStatus; use crate::printer::Printer; +const UV_GITHUB_RELEASES_DOWNLOAD_PREFIX: &str = + "https://github.com/astral-sh/uv/releases/download/"; +const UV_MIRROR_RELEASES_DOWNLOAD_PREFIX: &str = + "https://releases.astral.sh/github/uv/releases/download/"; +const AXOUPDATER_CONFIG_PATH: &str = "AXOUPDATER_CONFIG_PATH"; +const AXOUPDATER_CONFIG_WORKING_DIR: &str = "AXOUPDATER_CONFIG_WORKING_DIR"; + /// Attempt to update the uv binary. pub(crate) async fn self_update( version: Option, @@ -114,7 +132,7 @@ pub(crate) async fn self_update( debug!("Using official public self-update path"); let retry_policy = client_builder.retry_policy(); - let client = client_builder.retries(0).build()?; + let client = client_builder.clone().retries(0).build()?; let constraints = official_target_version_specifiers(version.as_deref())?; let resolved = find_matching_version( @@ -163,12 +181,18 @@ pub(crate) async fn self_update( return Ok(ExitStatus::Success); } - updater - .configure_version_specifier(UpdateRequest::SpecificTag(resolved.version.to_string())); - return run_updater(updater, printer, token.is_some()).await; + return run_official_updater( + updater, + ¤t_version, + &resolved.version, + printer, + client_builder, + token.as_deref(), + ) + .await; } - debug!("Using legacy self-update path"); + debug!("Using custom self-update path"); let update_request = if let Some(version) = version { UpdateRequest::SpecificTag(version) @@ -209,7 +233,7 @@ pub(crate) async fn self_update( return Ok(ExitStatus::Success); } - run_updater(updater, printer, token.is_some()).await + run_custom_updater(updater, printer, token.is_some()).await } /// Returns `true` if the `source` is the official GitHub repository for uv, or @@ -246,7 +270,7 @@ fn is_official_public_uv_install_with_overrides( /// Parse an explicit `uv self update` target version for the official public case. /// -/// To preserve legacy tag-based behavior, only exact `major.minor.patch` release versions are +/// To preserve existing tag-based behavior, only exact `major.minor.patch` release versions are /// accepted. Inputs that normalize to a different version string, such as `0.10` or `v0.10.0`, /// are rejected instead of being silently rewritten. fn official_target_version_specifiers( @@ -284,7 +308,297 @@ fn is_update_needed( } } -async fn run_updater( +/// Given the current and target versions, fetch the installer from Astral's official release +/// artifacts and run it. +async fn run_official_updater( + updater: &AxoUpdater, + current_version: &Pep440Version, + target_version: &Pep440Version, + printer: Printer, + client_builder: BaseClientBuilder<'_>, + github_token: Option<&str>, +) -> Result { + let installer_urls = official_installer_urls(target_version)?; + let temp_dir = TempDir::new()?; + let installer_path = temp_dir.path().join(installer_filename()); + let install_prefix = PathBuf::from(updater.install_prefix_root()?.as_str()); + // If we can't determine the previous PATH behavior, abort rather than potentially changing the + // user's shell configuration unexpectedly. + let modify_path = load_receipt_modify_path("uv") + .context("Failed to determine whether the existing standalone install modified PATH")?; + + download_installer_from_urls( + &installer_urls, + &installer_path, + client_builder, + github_token, + ) + .await?; + + execute_official_installer(&installer_path, &install_prefix, modify_path).await?; + + let direction = if current_version > target_version { + "Downgraded" + } else { + "Upgraded" + }; + writeln!( + printer.stderr(), + "{}", + format_args!( + "{}{} {direction} uv from {} to {}! {}", + "success".green().bold(), + ":".bold(), + format!("v{current_version}").bold().cyan(), + format!("v{target_version}").bold().cyan(), + format!("https://github.com/astral-sh/uv/releases/tag/{target_version}").cyan(), + ) + )?; + + Ok(ExitStatus::Success) +} + +/// Return the platform-specific standalone installer filename. +fn installer_filename() -> &'static str { + if cfg!(windows) { + "uv-installer.ps1" + } else { + "uv-installer.sh" + } +} + +/// Build the mirror-first URL list for the official standalone installer. +fn official_installer_urls(version: &Pep440Version) -> Result> { + let filename = installer_filename(); + let mirror = format!("{UV_MIRROR_RELEASES_DOWNLOAD_PREFIX}{version}/{filename}"); + let canonical = format!("{UV_GITHUB_RELEASES_DOWNLOAD_PREFIX}{version}/{filename}"); + + Ok(vec![ + DisplaySafeUrl::parse(&mirror).with_context(|| format!("Failed to parse `{mirror}`"))?, + DisplaySafeUrl::parse(&canonical) + .with_context(|| format!("Failed to parse `{canonical}`"))?, + ]) +} + +/// Download the official installer from the mirror first, then fall back to GitHub. +async fn download_installer_from_urls( + urls: &[DisplaySafeUrl], + installer_path: &Path, + client_builder: BaseClientBuilder<'_>, + github_token: Option<&str>, +) -> Result<()> { + let retry_policy = client_builder.retry_policy(); + // Disable the client's built-in retries here because `fetch_with_url_fallback` already owns + // the retry budget across the mirror-first URL list. + let client = client_builder + .retries(0) + .build() + .context("Failed to build HTTP client for self-update")?; + + fetch_with_url_fallback(urls, retry_policy, "official uv installer", |url| async { + let mut request = client.for_host(&url).get(Url::from(url.clone())); + if let Some(github_token) = installer_download_github_token(&url, github_token) { + request = request.header("Authorization", format!("Bearer {github_token}")); + } + + let response = request + .send() + .await + .map_err(|source| InstallerDownloadError::Download { + url: url.clone(), + source: source.into(), + })?; + + let response = + response + .error_for_status() + .map_err(|source| InstallerDownloadError::Download { + url: url.clone(), + source: source.into(), + })?; + + let bytes = response + .bytes() + .await + .map_err(|source| InstallerDownloadError::Download { + url, + source: source.into(), + })?; + + fs_err::tokio::write(installer_path, &bytes) + .await + .map_err(|source| InstallerDownloadError::Write { + path: installer_path.to_path_buf(), + source, + })?; + + Ok::<(), InstallerDownloadError>(()) + }) + .await?; + + #[cfg(unix)] + { + use std::fs::Permissions; + use std::os::unix::fs::PermissionsExt; + + fs_err::tokio::set_permissions(installer_path, Permissions::from_mode(0o744)).await?; + } + + Ok(()) +} + +fn installer_download_github_token<'a>( + url: &DisplaySafeUrl, + github_token: Option<&'a str>, +) -> Option<&'a str> { + match url.host_str() { + Some("github.com") => github_token, + _ => None, + } +} + +/// Execute the standalone installer while preserving the existing install location and PATH +/// behavior. +async fn execute_official_installer( + installer_path: &Path, + install_prefix: &Path, + modify_path: bool, +) -> Result<(), AxoupdateError> { + let mut command = if cfg!(windows) { + let mut command = Command::new("powershell"); + command.arg("-ExecutionPolicy").arg("ByPass"); + command.arg(installer_path); + command + } else { + Command::new(installer_path) + }; + + let to_restore = if cfg!(windows) { + let old_path = std::env::current_exe()?; + let mut previous_path = old_path.as_os_str().to_os_string(); + previous_path.push(".previous.exe"); + let previous_path = PathBuf::from(previous_path); + fs_err::rename(&old_path, &previous_path)?; + Some((previous_path, old_path)) + } else { + None + }; + + command.env_remove(EnvVars::PS_MODULE_PATH); + command.env("CARGO_DIST_FORCE_INSTALL_DIR", install_prefix); + command.env(EnvVars::UV_INSTALL_DIR, install_prefix); + if !modify_path { + let app_name_env_var = app_name_to_env_var("uv"); + command.env(format!("{app_name_env_var}_NO_MODIFY_PATH"), "1"); + } + + let result = command.output().await; + let failed = result + .as_ref() + .map(|output| !output.status.success()) + .unwrap_or(true); + + if let Some((previous_path, old_path)) = to_restore.as_ref() { + if failed { + fs_err::rename(previous_path, old_path)?; + } else { + #[cfg(windows)] + self_replace::self_delete_at(previous_path) + .map_err(|_| AxoupdateError::CleanupFailed {})?; + } + } + + let output = result?; + if output.status.success() { + return Ok(()); + } + + let stdout = + (!output.stdout.is_empty()).then(|| String::from_utf8_lossy(&output.stdout).to_string()); + let stderr = + (!output.stderr.is_empty()).then(|| String::from_utf8_lossy(&output.stderr).to_string()); + Err(AxoupdateError::InstallFailed { + status: output.status.code(), + stdout, + stderr, + }) +} + +/// Read whether the existing standalone install opted out of PATH modification. +/// +/// Older receipts that lack this field default to `true` for modifying PATH. +fn load_receipt_modify_path(app_name: &str) -> Result { + let Some(receipt_path) = find_receipt_path(app_name)? else { + anyhow::bail!("Failed to locate the standalone install receipt for `{app_name}`"); + }; + + // Axoupdater does not expose `modify_path`, so we re-read the already-validated receipt. + let receipt = fs_err::read(&receipt_path).with_context(|| { + format!( + "Failed to read install receipt at `{}`", + receipt_path.display() + ) + })?; + let receipt: StandaloneInstallReceipt = + serde_json::from_slice(&receipt).with_context(|| { + format!( + "Failed to parse install receipt at `{}`", + receipt_path.display() + ) + })?; + Ok(receipt.modify_path) +} + +/// Find the receipt path for the given app name. Returns `Ok(None)` if the receipt +/// definitely doesn't exist. +fn find_receipt_path(app_name: &str) -> Result> { + for prefix in receipt_prefixes(app_name)? { + let receipt_path = prefix.join(format!("{app_name}-receipt.json")); + if receipt_path.exists() { + return Ok(Some(receipt_path)); + } + } + Ok(None) +} + +/// List all possible locations for the receipt file for a given app name, +/// taking into account axoupdater-specific environment variable overrides. +fn receipt_prefixes(app_name: &str) -> Result> { + if std::env::var_os(AXOUPDATER_CONFIG_WORKING_DIR).is_some() { + return Ok(vec![std::env::current_dir()?]); + } + + if let Some(path) = std::env::var_os(AXOUPDATER_CONFIG_PATH) { + return Ok(vec![PathBuf::from(path)]); + } + + let mut prefixes = Vec::new(); + + if let Some(path) = std::env::var_os("XDG_CONFIG_HOME") { + let path = PathBuf::from(path).join(app_name); + if path.exists() { + prefixes.push(path); + } + } + + #[cfg(windows)] + if let Some(path) = std::env::var_os("LOCALAPPDATA") { + prefixes.push(PathBuf::from(path).join(app_name)); + } + + #[cfg(not(windows))] + if let Ok(path) = etcetera::home_dir() { + prefixes.push(path.join(".config").join(app_name)); + } + + Ok(prefixes) +} + +/// Runs the regular axoupdater-based update flow, printing the results to the console. +/// +/// This is used when the Astral-provided official releases are disabled by the user. +/// See [`is_official_public_uv_install`] for the condition that enables this. +async fn run_custom_updater( updater: &mut AxoUpdater, printer: Printer, has_token: bool, @@ -365,10 +679,171 @@ async fn run_updater( Ok(ExitStatus::Success) } +#[derive(Debug, Deserialize)] +struct StandaloneInstallReceipt { + #[serde(default = "default_modify_path")] + modify_path: bool, +} + +const fn default_modify_path() -> bool { + true +} + +#[derive(Debug, Error)] +enum InstallerDownloadError { + #[error("Failed to download installer from: {url}")] + Download { + url: DisplaySafeUrl, + #[source] + source: WrappedReqwestError, + }, + + #[error("Failed to write installer to: {path}")] + Write { + path: PathBuf, + #[source] + source: std::io::Error, + }, + + #[error( + "Request failed after {retries} {subject} in {duration:.1}s", + subject = if *retries > 1 { "retries" } else { "retry" }, + duration = duration.as_secs_f32() + )] + RetriedError { + #[source] + err: Box, + retries: u32, + duration: Duration, + }, + + #[error(transparent)] + Io(#[from] std::io::Error), + + #[error(transparent)] + SystemTime(#[from] SystemTimeError), +} + +impl RetriableError for InstallerDownloadError { + fn should_try_next_url(&self) -> bool { + match self { + Self::Download { source, .. } => should_try_next_installer_url(source), + Self::RetriedError { err, .. } => err.should_try_next_url(), + Self::Write { .. } | Self::Io(..) | Self::SystemTime(..) => false, + } + } + + fn retries(&self) -> u32 { + if let Self::RetriedError { retries, .. } = self { + *retries + } else { + 0 + } + } + + fn into_retried(self, retries: u32, duration: Duration) -> Self { + Self::RetriedError { + err: Box::new(self), + retries, + duration, + } + } +} + +fn should_try_next_installer_url(error: &WrappedReqwestError) -> bool { + if let Some(error) = error.inner() + && (error.status().is_some() + || error.is_timeout() + || error.is_connect() + || error.is_request() + || error.is_body() + || error.is_decode()) + { + return true; + } + + let mut source: Option<&(dyn std::error::Error + 'static)> = Some(error); + while let Some(error) = source { + if let Some(io_error) = error.downcast_ref::() + && matches!( + io_error.kind(), + std::io::ErrorKind::BrokenPipe + | std::io::ErrorKind::ConnectionAborted + | std::io::ErrorKind::ConnectionReset + | std::io::ErrorKind::InvalidData + | std::io::ErrorKind::TimedOut + | std::io::ErrorKind::UnexpectedEof + ) + { + return true; + } + source = error.source(); + } + + false +} + #[cfg(test)] mod tests { + use std::io::{Read, Write}; + use std::net::TcpListener; + use std::sync::Arc; + use std::sync::atomic::{AtomicUsize, Ordering}; + use std::sync::mpsc::{self, Sender}; + use std::thread::JoinHandle; + use std::time::Duration; + use super::*; + fn spawn_http_server( + response: String, + ) -> (DisplaySafeUrl, Arc, Sender<()>, JoinHandle<()>) { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + listener.set_nonblocking(true).unwrap(); + let addr = listener.local_addr().unwrap(); + let requests = Arc::new(AtomicUsize::new(0)); + let requests_clone = Arc::clone(&requests); + let (shutdown_tx, shutdown_rx) = mpsc::channel(); + let handle = std::thread::spawn(move || { + loop { + if shutdown_rx.try_recv().is_ok() { + return; + } + + match listener.accept() { + Ok((mut stream, _)) => { + requests_clone.fetch_add(1, Ordering::SeqCst); + let mut buf = [0u8; 4096]; + let _ = stream.read(&mut buf); + stream.write_all(response.as_bytes()).unwrap(); + return; + } + Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => { + std::thread::sleep(Duration::from_millis(10)); + } + Err(err) => panic!("failed to accept connection: {err}"), + } + } + }); + ( + DisplaySafeUrl::parse(&format!("http://{addr}/uv-installer.sh")).unwrap(), + requests, + shutdown_tx, + handle, + ) + } + + fn installer_response(body: &str) -> String { + format!( + "HTTP/1.1 200 OK\r\nContent-Length: {}\r\nContent-Type: text/plain\r\n\r\n{body}", + body.len() + ) + } + + fn not_found_response() -> String { + "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n".to_string() + } + #[test] fn test_is_official_public_uv_install() { let source = ReleaseSource { @@ -451,4 +926,211 @@ mod tests { true, )); } + + #[test] + fn test_official_installer_urls() { + let urls = official_installer_urls(&Pep440Version::new([1, 2, 3])) + .unwrap() + .into_iter() + .map(|url| url.to_string()) + .collect::>(); + assert_eq!( + urls, + vec![ + format!( + "https://releases.astral.sh/github/uv/releases/download/1.2.3/{}", + installer_filename() + ), + format!( + "https://github.com/astral-sh/uv/releases/download/1.2.3/{}", + installer_filename() + ), + ] + ); + } + + #[test] + fn test_installer_download_github_token() { + let mirror = DisplaySafeUrl::parse( + "https://releases.astral.sh/github/uv/releases/download/1.2.3/uv-installer.sh", + ) + .unwrap(); + let github = DisplaySafeUrl::parse( + "https://github.com/astral-sh/uv/releases/download/1.2.3/uv-installer.sh", + ) + .unwrap(); + + assert_eq!( + installer_download_github_token(&mirror, Some("token")), + None + ); + assert_eq!( + installer_download_github_token(&github, Some("token")), + Some("token") + ); + assert_eq!(installer_download_github_token(&github, None), None); + } + + #[tokio::test] + async fn test_download_installer_falls_back_to_canonical_url() { + let (mirror_url, mirror_requests, mirror_shutdown, mirror_handle) = + spawn_http_server(not_found_response()); + let (canonical_url, canonical_requests, canonical_shutdown, canonical_handle) = + spawn_http_server(installer_response("echo canonical installer\n")); + let temp_dir = TempDir::new().unwrap(); + let installer_path = temp_dir.path().join("installer.sh"); + + download_installer_from_urls( + &[mirror_url, canonical_url], + &installer_path, + BaseClientBuilder::default(), + None, + ) + .await + .expect("404 from mirror should fall back to canonical installer URL"); + + let _ = mirror_shutdown.send(()); + let _ = canonical_shutdown.send(()); + mirror_handle.join().unwrap(); + canonical_handle.join().unwrap(); + + assert_eq!(mirror_requests.load(Ordering::SeqCst), 1); + assert_eq!(canonical_requests.load(Ordering::SeqCst), 1); + assert_eq!( + fs_err::read_to_string(&installer_path).unwrap(), + "echo canonical installer\n" + ); + } + + #[test] + fn test_standalone_install_receipt_defaults_modify_path_to_true() { + let receipt: StandaloneInstallReceipt = + serde_json::from_str("{}\n").expect("receipt without modify_path should parse"); + assert!(receipt.modify_path); + + let receipt: StandaloneInstallReceipt = serde_json::from_str("{\"modify_path\":false}\n") + .expect("receipt with explicit modify_path should parse"); + assert!(!receipt.modify_path); + } + + #[cfg(unix)] + #[tokio::test] + async fn test_download_installer_sets_executable_bit() { + use std::os::unix::fs::PermissionsExt; + + let (url, _requests, shutdown_tx, handle) = + spawn_http_server(installer_response("echo installer\n")); + let temp_dir = TempDir::new().unwrap(); + let installer_path = temp_dir.path().join("installer.sh"); + + download_installer_from_urls(&[url], &installer_path, BaseClientBuilder::default(), None) + .await + .expect("installer download should succeed"); + + let _ = shutdown_tx.send(()); + handle.join().unwrap(); + + let mode = fs_err::metadata(&installer_path) + .unwrap() + .permissions() + .mode(); + assert_eq!(mode & 0o100, 0o100, "installer should be owner-executable"); + } + + #[cfg(unix)] + #[tokio::test] + async fn test_execute_official_installer_reports_failure() { + use std::os::unix::fs::PermissionsExt; + + let temp_dir = TempDir::new().unwrap(); + let installer_path = temp_dir.path().join("installer.sh"); + let install_prefix = temp_dir.path().join("install-prefix"); + + fs_err::write( + &installer_path, + "#!/bin/sh\nprintf 'hello from stdout\\n'\nprintf 'hello from stderr\\n' >&2\nexit 23\n", + ) + .unwrap(); + fs_err::set_permissions(&installer_path, std::fs::Permissions::from_mode(0o744)).unwrap(); + + let err = execute_official_installer(&installer_path, &install_prefix, true) + .await + .expect_err("failing installer should return an error"); + let AxoupdateError::InstallFailed { + status, + stdout, + stderr, + } = err + else { + panic!("expected InstallFailed error"); + }; + + assert_eq!(status, Some(23)); + assert_eq!(stdout.as_deref(), Some("hello from stdout\n")); + assert_eq!(stderr.as_deref(), Some("hello from stderr\n")); + } + + #[cfg(unix)] + #[tokio::test] + async fn test_execute_official_installer_sets_install_env_vars() { + use std::os::unix::fs::PermissionsExt; + + let temp_dir = TempDir::new().unwrap(); + let output_path = temp_dir.path().join("env.txt"); + let installer_path = temp_dir.path().join("installer.sh"); + let install_prefix = temp_dir.path().join("install-prefix"); + + fs_err::write( + &installer_path, + format!( + "#!/bin/sh\nset -eu\n{{\nprintf 'CARGO_DIST_FORCE_INSTALL_DIR=%s\\n' \"$CARGO_DIST_FORCE_INSTALL_DIR\"\nprintf 'UV_INSTALL_DIR=%s\\n' \"$UV_INSTALL_DIR\"\nprintf 'UV_NO_MODIFY_PATH=%s\\n' \"${{UV_NO_MODIFY_PATH-}}\"\n}} > \"{}\"\n", + output_path.display() + ), + ) + .unwrap(); + fs_err::set_permissions(&installer_path, std::fs::Permissions::from_mode(0o744)).unwrap(); + + execute_official_installer(&installer_path, &install_prefix, false) + .await + .unwrap(); + + assert_eq!( + fs_err::read_to_string(&output_path).unwrap(), + format!( + "CARGO_DIST_FORCE_INSTALL_DIR={}\nUV_INSTALL_DIR={}\nUV_NO_MODIFY_PATH=1\n", + install_prefix.display(), + install_prefix.display(), + ) + ); + } + + #[cfg(unix)] + #[tokio::test] + async fn test_execute_official_installer_preserves_modify_path_default() { + use std::os::unix::fs::PermissionsExt; + + let temp_dir = TempDir::new().unwrap(); + let output_path = temp_dir.path().join("env.txt"); + let installer_path = temp_dir.path().join("installer.sh"); + let install_prefix = temp_dir.path().join("install-prefix"); + + fs_err::write( + &installer_path, + format!( + "#!/bin/sh\nset -eu\n{{\nprintf 'UV_NO_MODIFY_PATH=%s\\n' \"${{UV_NO_MODIFY_PATH-}}\"\n}} > \"{}\"\n", + output_path.display() + ), + ) + .unwrap(); + fs_err::set_permissions(&installer_path, std::fs::Permissions::from_mode(0o744)).unwrap(); + + execute_official_installer(&installer_path, &install_prefix, true) + .await + .unwrap(); + + assert_eq!( + fs_err::read_to_string(&output_path).unwrap(), + "UV_NO_MODIFY_PATH=\n" + ); + } } diff --git a/crates/uv/tests/it/self_update.rs b/crates/uv/tests/it/self_update.rs index 4316704e5a3e0..24493d662ad30 100644 --- a/crates/uv/tests/it/self_update.rs +++ b/crates/uv/tests/it/self_update.rs @@ -6,6 +6,7 @@ use axoupdater::{ ReleaseSourceType, test::helpers::{RuntestArgs, perform_runtest}, }; +use regex::escape; use serde_json::json; use wiremock::matchers::{method, path}; use wiremock::{Mock, MockServer, ResponseTemplate}; @@ -153,6 +154,101 @@ async fn setup_mock_update( Ok((receipt_dir.to_path_buf(), server)) } +#[test] +fn test_self_update_help() { + let context = uv_test::test_context_with_versions!(&[]); + + let output = context + .help() + .arg("self") + .arg("update") + .output() + .expect("`uv help self update` should succeed"); + assert!(output.status.success()); + + let stdout = String::from_utf8_lossy(&output.stdout); + assert!(stdout.contains("A GitHub token for authentication.")); + assert!(stdout.contains("A token is not required but can be used to reduce the")); + assert!(stdout.contains("chance of encountering rate limits")); +} + +#[tokio::test] +async fn test_self_update_uses_custom_path_with_ghe_override() -> Result<()> { + let context = uv_test::test_context!("3.12").with_filter(( + escape(&format!("v{}", env!("CARGO_PKG_VERSION"))), + "v[CURRENT_VERSION]", + )); + + let receipt_dir = context.temp_dir.child("receipt"); + receipt_dir.create_dir_all()?; + + let install_prefix = std::path::absolute( + get_bin!() + .parent() + .expect("uv binary should have a parent directory"), + )?; + receipt_dir + .child("uv-receipt.json") + .write_str(&serde_json::to_string_pretty(&json!({ + "install_prefix": install_prefix, + "binaries": ["uv"], + "cdylibs": [], + "source": { + "release_type": "github", + "owner": "astral-sh", + "name": "uv", + "app_name": "uv", + }, + "version": env!("CARGO_PKG_VERSION"), + "provider": { + "source": "cargo-dist", + "version": "0.31.0", + }, + "modify_path": true, + }))?)?; + + let server = MockServer::start().await; + let target_version = "9.9.9"; + let installer_name = if cfg!(windows) { + "uv-installer.ps1" + } else { + "uv-installer.sh" + }; + Mock::given(method("GET")) + .and(path(format!( + "/api/v3/repos/astral-sh/uv/releases/tags/{target_version}" + ))) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "tag_name": target_version, + "name": target_version, + "url": format!("{}/repos/astral-sh/uv/releases/tags/{target_version}", server.uri()), + "assets": [{ + "url": format!("{}/assets/{installer_name}", server.uri()), + "browser_download_url": format!("{}/downloads/{installer_name}", server.uri()), + "name": installer_name, + }], + "prerelease": false, + }))) + .mount(&server) + .await; + + uv_snapshot!(context.filters(), context.self_update() + .arg(target_version) + .arg("--dry-run") + .env("AXOUPDATER_CONFIG_PATH", receipt_dir.as_os_str()) + .env(EnvVars::UV_INSTALLER_GHE_BASE_URL, server.uri()), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + info: Checking for updates... + Would update uv from v[CURRENT_VERSION] to v9.9.9 + "); + + Ok(()) +} + #[tokio::test] async fn test_self_update_uses_legacy_path_with_ghe_override() -> Result<()> { let context = uv_test::test_context!("3.12").with_filtered_current_version(); From 23ac4996d9ac20df26512b1b2592bcc89745d845 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 17 Apr 2026 15:19:16 -0500 Subject: [PATCH 10/70] Update cgr.dev/chainguard/python:latest-dev Docker digest to c2ac411 (#18981) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | cgr.dev/chainguard/python | container | digest | `f475abd` → `c2ac411` | --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/astral-sh/uv). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/test-system.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-system.yml b/.github/workflows/test-system.yml index 066db454feda4..f9094a2a0a2cd 100644 --- a/.github/workflows/test-system.yml +++ b/.github/workflows/test-system.yml @@ -321,7 +321,7 @@ jobs: name: "python on chainguard-dev" runs-on: ubuntu-latest container: - image: cgr.dev/chainguard/python:latest-dev@sha256:f475abdba3927df9fe2b87ca886b6b2a112d0a1f196617bd85af07928df6d773 + image: cgr.dev/chainguard/python:latest-dev@sha256:c2ac4118658f7f5a07fcdf31dafb7adca7e7edaf3ad5121585b5f83b79bd85ed options: --user root --entrypoint /bin/sh steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 From 1f4fce9692ebc51b3419ef81550ade8125d90195 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Fri, 17 Apr 2026 16:01:29 -0500 Subject: [PATCH 11/70] Refactor `update_availability_range` to use iterator and collect (#19039) Addresses the TODO about using a repeated union now that there's `FromIterator` implementation upstream. Co-authored-by: Claude --- crates/uv-resolver/src/pubgrub/report.rs | 96 +++++++++++------------- 1 file changed, 43 insertions(+), 53 deletions(-) diff --git a/crates/uv-resolver/src/pubgrub/report.rs b/crates/uv-resolver/src/pubgrub/report.rs index 5dcd95ba53caf..b8e4624723037 100644 --- a/crates/uv-resolver/src/pubgrub/report.rs +++ b/crates/uv-resolver/src/pubgrub/report.rs @@ -2244,8 +2244,6 @@ fn update_availability_range( versions.contains(&version) } - let mut new_range = Range::empty(); - // Construct an available range to help guide simplification. Note this is not strictly correct, // as the available range should have many holes in it. However, for this use-case it should be // okay — we just may avoid simplifying some segments _inside_ the available range. @@ -2270,61 +2268,53 @@ fn update_availability_range( (None, None) => return Range::empty(), }; - for segment in range.iter() { - let (lower, upper) = segment; - let segment_range = Range::from_range_bounds((lower.clone(), upper.clone())); - - // Drop the segment if it's disjoint with the available range, e.g., if the segment is - // `foo>999`, and the available versions are all `<10` it's useless to show. - if segment_range.is_disjoint(&available_range) { - continue; - } - - // Replace the segment if it's captured by the available range, e.g., if the segment is - // `foo<1000` and the available versions are all `<10` we can simplify to `foo<10`. - if available_range.subset_of(&segment_range) { - // If the segment only has a lower or upper bound, only take the relevant part of the - // available range. This avoids replacing `foo<100` with `foo>1,<2`, instead using - // `foo<2` to avoid extra noise. - if matches!(lower, Bound::Unbounded) { - new_range = new_range.union(&Range::from_range_bounds(( - Bound::Unbounded, - Bound::Included(last_available.clone()), - ))); - } else if matches!(upper, Bound::Unbounded) { - new_range = new_range.union(&Range::from_range_bounds(( + range + .iter() + .filter_map(|(lower, upper)| { + let segment_range = Range::from_range_bounds((lower.clone(), upper.clone())); + + // Drop the segment if it's disjoint with the available range, e.g., if the segment is + // `foo>999`, and the available versions are all `<10` it's useless to show. + if segment_range.is_disjoint(&available_range) { + return None; + } + + // Replace the segment if it's captured by the available range, e.g., if the segment is + // `foo<1000` and the available versions are all `<10` we can simplify to `foo<10`. + if available_range.subset_of(&segment_range) { + // If the segment only has a lower or upper bound, only take the relevant part of + // the available range. This avoids replacing `foo<100` with `foo>1,<2`, instead + // using `foo<2` to avoid extra noise. + if matches!(lower, Bound::Unbounded) { + return Some((Bound::Unbounded, Bound::Included(last_available.clone()))); + } else if matches!(upper, Bound::Unbounded) { + return Some((Bound::Included(first_available.clone()), Bound::Unbounded)); + } + return Some(( Bound::Included(first_available.clone()), - Bound::Unbounded, - ))); - } else { - new_range = new_range.union(&available_range); - } - continue; - } - - // If the bound is inclusive, and the version is _not_ available, change it to an exclusive - // bound to avoid confusion, e.g., if the segment is `foo<=10` and the available versions - // do not include `foo 10`, we should instead say `foo<10`. - let lower = match lower { - Bound::Included(version) if !version_contained_in(version, available_versions) => { - Bound::Excluded(version.clone()) - } - _ => (*lower).clone(), - }; - let upper = match upper { - Bound::Included(version) if !version_contained_in(version, available_versions) => { - Bound::Excluded(version.clone()) + Bound::Included(last_available.clone()), + )); } - _ => (*upper).clone(), - }; - // Note this repeated-union construction is not particularly efficient, but there's not - // better API exposed by PubGrub. Since we're just generating an error message, it's - // probably okay, but we should investigate a better upstream API. - new_range = new_range.union(&Range::from_range_bounds((lower, upper))); - } + // If the bound is inclusive, and the version is _not_ available, change it to an + // exclusive bound to avoid confusion, e.g., if the segment is `foo<=10` and the + // available versions do not include `foo 10`, we should instead say `foo<10`. + let lower = match lower { + Bound::Included(version) if !version_contained_in(version, available_versions) => { + Bound::Excluded(version.clone()) + } + _ => (*lower).clone(), + }; + let upper = match upper { + Bound::Included(version) if !version_contained_in(version, available_versions) => { + Bound::Excluded(version.clone()) + } + _ => (*upper).clone(), + }; - new_range + Some((lower, upper)) + }) + .collect() } impl std::fmt::Display for PackageRange<'_> { From 0ec181539947b32bc69463b18c58a477fc7d61c0 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Fri, 17 Apr 2026 16:25:53 -0500 Subject: [PATCH 12/70] Only update `test-system.yml` dependencies monthly (#19040) These are not important to update frequently --------- Co-authored-by: Claude --- .github/renovate.json5 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index a7e32b6b53c2c..e9844d088d106 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -80,6 +80,11 @@ description: "Disable Python version updates in test-system.yml", enabled: false, }, + { + matchFileNames: [".github/workflows/test-system.yml"], + extends: ["schedule:monthly"], + description: "Monthly update of test-system.yml dependencies", + }, { groupName: "pre-commit dependencies", matchManagers: ["pre-commit"], From 754c5d944cc8427c770744b52279fedbff9031b8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 19 Apr 2026 00:21:24 +0200 Subject: [PATCH 13/70] Update Rust crate similar to v3 (#18991) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [similar](https://redirect.github.com/mitsuhiko/similar) | workspace.dependencies | major | `2.6.0` → `3.0.0` | --- ### Release Notes
mitsuhiko/similar (similar) ### [`v3.1.0`](https://redirect.github.com/mitsuhiko/similar/blob/HEAD/CHANGELOG.md#310) [Compare Source](https://redirect.github.com/mitsuhiko/similar/compare/3.0.0...3.1.0) - Added `capture_diff_slices_by_key` and `capture_diff_slices_by_key_deadline` as convenience helpers for diffing slices by derived keys. - Fixed `Compact` emitting inconsistent `DiffOp` cursor positions after compaction, which could leave `Delete`/`Insert` operations with stale `new_index`/`old_index` values. - Added explicit lifetime capture (`+ use<...>`) on iterator-returning APIs to improve compatibility with Rust 2024 lifetime capture behavior. [#​93](https://redirect.github.com/mitsuhiko/similar/issues/93) ### [`v3.0.0`](https://redirect.github.com/mitsuhiko/similar/blob/HEAD/CHANGELOG.md#300) [Compare Source](https://redirect.github.com/mitsuhiko/similar/compare/2.7.0...3.0.0) - Added a Git-style Histogram diff implementation exposed as `Algorithm::Histogram`, including deadline-aware Myers fallback and comprehensive regression/behavior tests. - Raised MSRV to Rust 1.85 and moved the crate to Rust 2024 edition. - Added a Hunt-style diff implementation exposed as `Algorithm::Hunt`. - Added configurable inline refinement via `InlineChangeOptions` and `InlineChangeMode`, including semantic cleanup and new `TextDiff::iter_inline_changes_with_options*` methods. [#​92](https://redirect.github.com/mitsuhiko/similar/issues/92) - Added a global disjoint-input fast path in `algorithms::diff_deadline` to avoid pathological runtimes on large, fully distinct inputs. - Improved `Algorithm::Myers` performance on heavily unbalanced diffs to avoid pathological slowdowns. - Added `diff_deadline_raw` entrypoints in the algorithm modules to bypass shared heuristics and keep minimal intrinsic trait bounds where needed. - Added test files in `examples/diffs` that can be used with the some of the examples as input pairs. - Added `CachedLookup`, a helper for adapting virtual or computed sequences by materializing items on first access and then serving borrowed values through normal indexing. The `owned-lookup` example demonstrates this approach for issue [#​33](https://redirect.github.com/mitsuhiko/similar/issues/33). - Fixed ranged indexing in the classic LCS table algorithm. - Improved diff compaction to merge adjacent delete hunks across equal runs. - Excluded development scripts from published crate contents. [#​87](https://redirect.github.com/mitsuhiko/similar/issues/87) - `TextDiff::from_*` and `TextDiffConfig::diff_*` now accept owned inputs (`String`, `Vec`, `Cow`) in addition to borrowed inputs. This allows returning text diffs from functions without external owner lifetimes. [#​65](https://redirect.github.com/mitsuhiko/similar/issues/65) - `TextDiff` no longer exposes `old_slices` / `new_slices`. Use `old_len`, `new_len`, `old_slice`, `new_slice`, `iter_old_slices`, `iter_new_slices`, `old_lookup`, and `new_lookup` instead. - `TextDiff::iter_changes` now panics on invalid out-of-bounds `DiffOp` ranges instead of silently truncating iteration. - `utils::diff_lines_inline` now takes `&TextDiff` and options rather than `(Algorithm, old, new, options)`. - `utils::diff_lines` now avoids a second line-tokenization pass. - Renamed `get_diff_ratio` to `diff_ratio`. - Added first-class `no_std + alloc` support with an explicit default `std` feature. - Added optional `hashbrown` backend for `no_std` map storage (`default-features = false, features = ["hashbrown"]`), while the default `no_std` backend uses `alloc::collections::BTreeMap`. - Made core constructors const-ready (`Capture::new`, `Replace::new`, `NoFinishHook::new`, `InlineChangeOptions::new`, `TextDiff::configure`).
--- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/astral-sh/uv). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 43 ++++++++++++++++++++++++++----------------- Cargo.toml | 2 +- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 78308f631be70..a73feacab52ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -90,7 +90,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -101,7 +101,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1318,7 +1318,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1497,7 +1497,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -2328,7 +2328,7 @@ dependencies = [ "pest_derive", "regex", "serde", - "similar", + "similar 2.7.0", "tempfile", ] @@ -2365,7 +2365,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -2426,7 +2426,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde_core", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -2543,7 +2543,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cfc352a66ba903c23239ef51e809508b6fc2b0f90e3476ac7a9ff47e863ae95" dependencies = [ "scopeguard", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2814,7 +2814,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "536bfad37a309d62069485248eeaba1e8d9853aaf951caaeaed0585a95346f08" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2902,7 +2902,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4188,7 +4188,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4245,7 +4245,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4663,6 +4663,15 @@ version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" +[[package]] +name = "similar" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04d93e861ede2e497b47833469b8ec9d5c07fa4c78ce7a00f6eb7dd8168b4b3f" +dependencies = [ + "bstr", +] + [[package]] name = "simple_asn1" version = "0.6.4" @@ -4724,7 +4733,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4952,7 +4961,7 @@ dependencies = [ "getrandom 0.4.1", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -5569,7 +5578,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ "memoffset", "tempfile", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -7279,7 +7288,7 @@ dependencies = [ "predicates", "regex", "reqwest", - "similar", + "similar 3.1.0", "tempfile", "tokio", "uv-cache", @@ -7745,7 +7754,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 4c393eee5425f..f7318f557d016 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -333,7 +333,7 @@ rcgen = { version = "0.14.5", features = [ "ring", ], default-features = false } rustls = { version = "0.23.36", default-features = false } -similar = { version = "2.6.0" } +similar = { version = "3.0.0" } webpki = { package = "rustls-webpki", version = "0.103.10" } x509-parser = { version = "0.18.0" } temp-env = { version = "0.3.6", features = ["async_closure"] } From d5223f6a213861e9e8eb7f000b970c44ea482676 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sun, 19 Apr 2026 19:19:52 -0400 Subject: [PATCH 14/70] Add an environment variable for `UV_NO_PROJECT` (#19052) Closes https://github.com/astral-sh/uv/issues/19020. --- crates/uv-cli/src/lib.rs | 35 +++++++++++++++++++++++++++----- crates/uv-static/src/env_vars.rs | 4 ++++ 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 07b02e940ffad..490464508a4a3 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -3115,7 +3115,12 @@ pub struct VenvArgs { /// By default, uv searches for projects in the current directory or any parent directory to /// determine the default path of the virtual environment and check for Python version /// constraints, if any. - #[arg(long, alias = "no-workspace")] + #[arg( + long, + alias = "no-workspace", + env = EnvVars::UV_NO_PROJECT, + value_parser = clap::builder::BoolishValueParser::new() + )] pub no_project: bool, /// Install seed packages (one or more of: `pip`, `setuptools`, and `wheel`) into the virtual @@ -3751,7 +3756,13 @@ pub struct RunArgs { /// /// If a virtual environment is active or found in a current or parent directory, it will be /// used as if there was no project or workspace. - #[arg(long, alias = "no_workspace", conflicts_with = "package")] + #[arg( + long, + alias = "no_workspace", + env = EnvVars::UV_NO_PROJECT, + value_parser = clap::builder::BoolishValueParser::new(), + conflicts_with = "package" + )] pub no_project: bool, /// The Python interpreter to use for the run environment. @@ -5133,7 +5144,11 @@ pub struct FormatArgs { /// Instead of running the formatter in the context of the current project, run it in the /// context of the current directory. This is useful when the current directory is not a /// project. - #[arg(long)] + #[arg( + long, + env = EnvVars::UV_NO_PROJECT, + value_parser = clap::builder::BoolishValueParser::new() + )] pub no_project: bool, /// Display the version of Ruff that will be used for formatting. @@ -6599,7 +6614,12 @@ pub struct PythonFindArgs { /// /// Otherwise, when no request is provided, the Python requirement of a project in the current /// directory or parent directories will be used. - #[arg(long, alias = "no_workspace")] + #[arg( + long, + alias = "no_workspace", + env = EnvVars::UV_NO_PROJECT, + value_parser = clap::builder::BoolishValueParser::new() + )] pub no_project: bool, /// Only find system Python interpreters. @@ -6677,7 +6697,12 @@ pub struct PythonPinArgs { /// By default, a project or workspace is discovered in the current directory or any parent /// directory. If a workspace is found, the Python pin is validated against the workspace's /// `requires-python` constraint. - #[arg(long, alias = "no-workspace")] + #[arg( + long, + alias = "no-workspace", + env = EnvVars::UV_NO_PROJECT, + value_parser = clap::builder::BoolishValueParser::new() + )] pub no_project: bool, /// Update the global Python version pin. diff --git a/crates/uv-static/src/env_vars.rs b/crates/uv-static/src/env_vars.rs index a66561d794af9..35c327dbb9ba8 100644 --- a/crates/uv-static/src/env_vars.rs +++ b/crates/uv-static/src/env_vars.rs @@ -1271,6 +1271,10 @@ impl EnvVars { #[attr_added_in("0.4.4")] pub const UV_PROJECT: &'static str = "UV_PROJECT"; + /// Equivalent to the `--no-project` command-line argument. + #[attr_added_in("next")] + pub const UV_NO_PROJECT: &'static str = "UV_NO_PROJECT"; + /// Equivalent to the `--directory` command-line argument. `UV_WORKING_DIRECTORY` (added in /// v0.9.1) is also supported for backwards compatibility. #[attr_added_in("0.9.14")] From c9c1eb8d83cfc12895e12058bc2f4993a0a80df0 Mon Sep 17 00:00:00 2001 From: konsti Date: Mon, 20 Apr 2026 12:30:20 +0200 Subject: [PATCH 15/70] Move retry logic into its own file (#19073) The retry logic has enough complexity for its own module. --- crates/uv-client/src/base_client.rs | 355 +------------------------- crates/uv-client/src/cached_client.rs | 10 +- crates/uv-client/src/lib.rs | 7 +- crates/uv-client/src/retry.rs | 352 +++++++++++++++++++++++++ 4 files changed, 368 insertions(+), 356 deletions(-) create mode 100644 crates/uv-client/src/retry.rs diff --git a/crates/uv-client/src/base_client.rs b/crates/uv-client/src/base_client.rs index 59582a84952a2..c2da4e177cf1b 100644 --- a/crates/uv-client/src/base_client.rs +++ b/crates/uv-client/src/base_client.rs @@ -1,41 +1,30 @@ -use std::error::Error; +use std::env; use std::fmt::Debug; use std::fmt::Write; use std::num::ParseIntError; use std::sync::Arc; -use std::time::{Duration, SystemTime, SystemTimeError}; -use std::{env, io, iter}; +use std::time::{Duration, SystemTimeError}; use anyhow::anyhow; - -use http::{ - HeaderMap, HeaderName, HeaderValue, Method, StatusCode, - header::{ - AUTHORIZATION, CONTENT_ENCODING, CONTENT_LENGTH, CONTENT_TYPE, COOKIE, LOCATION, - PROXY_AUTHORIZATION, REFERER, TRANSFER_ENCODING, WWW_AUTHENTICATE, - }, +use http::header::{ + AUTHORIZATION, CONTENT_ENCODING, CONTENT_LENGTH, CONTENT_TYPE, COOKIE, LOCATION, + PROXY_AUTHORIZATION, REFERER, TRANSFER_ENCODING, WWW_AUTHENTICATE, }; -use itertools::Itertools; +use http::{HeaderMap, HeaderName, HeaderValue, Method, StatusCode}; use reqwest::{ Certificate, Client, ClientBuilder, IntoUrl, NoProxy, Proxy, Request, Response, multipart, }; use reqwest_middleware::{ClientWithMiddleware, Middleware}; use reqwest_retry::policies::ExponentialBackoff; -use reqwest_retry::{ - Jitter, RetryPolicy, RetryTransientMiddleware, Retryable, RetryableStrategy, - default_on_request_error, default_on_request_success, -}; - +use reqwest_retry::{Jitter, RetryTransientMiddleware}; use thiserror::Error; - -use tracing::{debug, trace, warn}; +use tracing::{debug, warn}; use url::ParseError; use url::Url; use uv_auth::{AuthMiddleware, Credentials, CredentialsCache, Indexes, PyxTokenStore}; use uv_configuration::ProxyUrlKind; use uv_configuration::{KeyringProviderType, ProxyUrl, TrustedHost}; - use uv_pep508::MarkerEnvironment; use uv_platform_tags::Platform; use uv_preview::Preview; @@ -48,7 +37,7 @@ use uv_warnings::warn_user_once; use crate::linehaul::LineHaul; use crate::middleware::OfflineMiddleware; use crate::tls::{Certificates, read_identity}; -use crate::{Connectivity, WrappedReqwestError}; +use crate::{Connectivity, RetriableError, RetryState, UvRetryableStrategy}; pub const DEFAULT_RETRIES: u32 = 3; @@ -1100,233 +1089,6 @@ fn retry_policy(retries: u32, no_retry_delay: bool) -> ExponentialBackoff { builder.build_with_max_retries(retries) } -/// An extension over [`DefaultRetryableStrategy`] that logs transient request failures and -/// adds additional retry cases. -pub struct UvRetryableStrategy; - -impl RetryableStrategy for UvRetryableStrategy { - fn handle(&self, res: &Result) -> Option { - let retryable = match res { - Ok(success) => default_on_request_success(success), - Err(err) => retryable_on_request_failure(err), - }; - - // Log on transient errors - if retryable == Some(Retryable::Transient) { - match res { - Ok(response) => { - debug!("Transient request failure for: {}", response.url()); - } - Err(err) => { - let context = iter::successors(err.source(), |&err| err.source()) - .map(|err| format!(" Caused by: {err}")) - .join("\n"); - debug!( - "Transient request failure for {}, retrying: {err}\n{context}", - err.url().map(Url::as_str).unwrap_or("unknown URL") - ); - } - } - } - retryable - } -} - -/// Whether the error looks like a network error that should be retried. -/// -/// This is an extension over [`reqwest_middleware::default_on_request_failure`], which is missing -/// a number of cases: -/// * Inside the reqwest or reqwest-middleware error is an `io::Error` such as a broken pipe -/// * When streaming a response, a reqwest error may be hidden several layers behind errors -/// of different crates processing the stream, including `io::Error` layers -/// * Any `h2` error -pub fn retryable_on_request_failure(err: &(dyn Error + 'static)) -> Option { - // First, try to show a nice trace log - if let Some((Some(status), Some(url))) = find_source::(&err) - .map(|request_err| (request_err.status(), request_err.url())) - { - trace!( - "Considering retry of response HTTP {status} for {url}", - url = DisplaySafeUrl::from_url(url.clone()) - ); - } else { - trace!("Considering retry of error: {err:?}"); - } - - let mut has_known_error = false; - // IO Errors or reqwest errors may be nested through custom IO errors or stream processing - // crates - let mut current_source = Some(err); - while let Some(source) = current_source { - // Handle different kinds of reqwest error nesting not accessible by downcast. - let reqwest_err = if let Some(reqwest_err) = source.downcast_ref::() { - Some(reqwest_err) - } else if let Some(reqwest_err) = source - .downcast_ref::() - .and_then(|err| err.inner()) - { - Some(reqwest_err) - } else if let Some(reqwest_middleware::Error::Reqwest(reqwest_err)) = - source.downcast_ref::() - { - Some(reqwest_err) - } else { - None - }; - - if let Some(reqwest_err) = reqwest_err { - has_known_error = true; - // Ignore the default retry strategy returning fatal. - if default_on_request_error(reqwest_err) == Some(Retryable::Transient) { - trace!("Transient nested reqwest error"); - return Some(Retryable::Transient); - } - if is_retryable_status_error(reqwest_err) { - trace!("Transient nested reqwest status code error"); - return Some(Retryable::Transient); - } - - trace!("Fatal nested reqwest error"); - } else if source.downcast_ref::().is_some() { - // All h2 errors look like errors that should be retried - // https://github.com/astral-sh/uv/issues/15916 - trace!("Transient nested h2 error"); - return Some(Retryable::Transient); - } else if let Some(io_err) = source.downcast_ref::() { - has_known_error = true; - let retryable_io_err_kinds = [ - // https://github.com/astral-sh/uv/issues/12054 - io::ErrorKind::BrokenPipe, - // From reqwest-middleware - io::ErrorKind::ConnectionAborted, - // https://github.com/astral-sh/uv/issues/3514 - io::ErrorKind::ConnectionReset, - // https://github.com/astral-sh/uv/issues/14699 - io::ErrorKind::InvalidData, - // https://github.com/astral-sh/uv/issues/17697#issuecomment-3817060484 - io::ErrorKind::TimedOut, - // https://github.com/astral-sh/uv/issues/9246 - io::ErrorKind::UnexpectedEof, - ]; - if retryable_io_err_kinds.contains(&io_err.kind()) { - trace!("Transient IO error: `{}`", io_err.kind()); - return Some(Retryable::Transient); - } - - trace!( - "Fatal IO error `{}`, not a transient IO error kind", - io_err.kind() - ); - } - - current_source = source.source(); - } - - if !has_known_error { - trace!("Cannot retry error: neither an IO error nor a reqwest error"); - } - - None -} - -/// Per-request retry state and policy. -pub struct RetryState { - retry_policy: ExponentialBackoff, - start_time: SystemTime, - total_retries: u32, - url: DisplaySafeUrl, -} - -impl RetryState { - /// Initialize the [`RetryState`] and record the start time for the retry policy. - pub fn start(retry_policy: ExponentialBackoff, url: impl Into) -> Self { - Self { - retry_policy, - start_time: SystemTime::now(), - total_retries: 0, - url: url.into(), - } - } - - /// The number of retries across all requests. - /// - /// After a failed retryable request, this equals the maximum number of retries. - pub fn total_retries(&self) -> u32 { - self.total_retries - } - - /// The total duration from the first request to the (failure) of the last request. - pub fn duration(&self) -> Result { - self.start_time.elapsed() - } - - /// Determines whether request should be retried. - /// - /// Takes the number of retries from nested layers associated with the specific `err` type as - /// `error_retries`. - /// - /// Returns the backoff duration if the request should be retried. - #[must_use] - pub fn should_retry( - &mut self, - err: &(dyn Error + 'static), - error_retries: u32, - ) -> Option { - // If the middleware performed any retries, consider them in our budget. - self.total_retries += error_retries; - match retryable_on_request_failure(err) { - Some(Retryable::Transient) => { - // Capture `now` before calling the policy so that `execute_after` - // (computed from a `SystemTime::now()` inside the library) is always - // >= `now`, making `duration_since` reliable. - let now = SystemTime::now(); - let retry_decision = self - .retry_policy - .should_retry(self.start_time, self.total_retries); - if let reqwest_retry::RetryDecision::Retry { execute_after } = retry_decision { - let duration = execute_after - .duration_since(now) - .unwrap_or_else(|_| Duration::default()); - - self.total_retries += 1; - return Some(duration); - } - - None - } - Some(Retryable::Fatal) | None => None, - } - } - - /// Wait before retrying the request. - pub async fn sleep_backoff(&self, duration: Duration) { - debug!( - "Transient failure while handling response from {}; retrying after {:.1}s...", - self.url, - duration.as_secs_f32(), - ); - // TODO(konsti): Should we show a spinner plus a message in the CLI while - // waiting? - tokio::time::sleep(duration).await; - } -} - -/// An error type that supports URL-fallback and exponential-backoff retry logic. -/// -/// Used by [`fetch_with_url_fallback`] to drive the retry loop without knowing the concrete error -/// type. -pub trait RetriableError: std::error::Error + Sized + 'static { - /// Returns `true` if an alternative URL should be tried immediately (without backoff). - fn should_try_next_url(&self) -> bool; - - /// Returns the number of inner retries already recorded in this error. - fn retries(&self) -> u32; - - /// Wrap the error to indicate that the operation was retried `retries` times before failing. - #[must_use] - fn into_retried(self, retries: u32, duration: Duration) -> Self; -} - /// Try a fallible async operation against each URL in order, with exponential backoff. /// /// URLs are tried in sequence without any backoff between them. Backoff is only applied after all @@ -1379,32 +1141,6 @@ where } } -/// Whether the error is a status code error that is retryable. -/// -/// Port of `reqwest_retry::default_on_request_success`. -fn is_retryable_status_error(reqwest_err: &reqwest::Error) -> bool { - let Some(status) = reqwest_err.status() else { - return false; - }; - status.is_server_error() - || status == StatusCode::REQUEST_TIMEOUT - || status == StatusCode::TOO_MANY_REQUESTS -} - -/// Find the first source error of a specific type. -/// -/// See -fn find_source(orig: &dyn Error) -> Option<&E> { - let mut cause = orig.source(); - while let Some(err) = cause { - if let Some(typed) = err.downcast_ref() { - return Some(typed); - } - cause = err.source(); - } - None -} - // TODO(konsti): Remove once we find a native home for `retries_from_env` #[derive(Debug, Error)] pub enum RetryParsingError { @@ -1417,13 +1153,10 @@ mod tests { use super::*; use anyhow::Result; - use insta::assert_debug_snapshot; use reqwest::{Client, Method}; - use wiremock::matchers::{method, path}; + use wiremock::matchers::method; use wiremock::{Mock, MockServer, ResponseTemplate}; - use crate::base_client::request_into_redirect; - #[tokio::test] async fn test_redirect_preserves_authorization_header_on_same_origin() -> Result<()> { for status in &[301, 302, 303, 307, 308] { @@ -1612,72 +1345,4 @@ mod tests { Ok(()) } - - /// Enumerate which status codes we are retrying. - #[tokio::test] - async fn retried_status_codes() -> Result<()> { - let server = MockServer::start().await; - let client = Client::default(); - let middleware_client = ClientWithMiddleware::default(); - let mut retried = Vec::new(); - for status in 100..599 { - // Test all standard status codes and an example for a non-RFC code used in the wild. - if StatusCode::from_u16(status)?.canonical_reason().is_none() && status != 420 { - continue; - } - - Mock::given(path(format!("/{status}"))) - .respond_with(ResponseTemplate::new(status)) - .mount(&server) - .await; - - let response = middleware_client - .get(format!("{}/{}", server.uri(), status)) - .send() - .await; - - let middleware_retry = - UvRetryableStrategy.handle(&response) == Some(Retryable::Transient); - - let response = client - .get(format!("{}/{}", server.uri(), status)) - .send() - .await?; - - let uv_retry = match response.error_for_status() { - Ok(_) => false, - Err(err) => retryable_on_request_failure(&err) == Some(Retryable::Transient), - }; - - // Ensure we're retrying the same status code as the reqwest_retry crate. We may choose - // to deviate from this later. - assert_eq!(middleware_retry, uv_retry); - if uv_retry { - retried.push(status); - } - } - - assert_debug_snapshot!(retried, @" - [ - 100, - 102, - 103, - 408, - 429, - 500, - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 510, - 511, - ] - "); - - Ok(()) - } } diff --git a/crates/uv-client/src/cached_client.rs b/crates/uv-client/src/cached_client.rs index 8ad6d502274d2..eda7c7fc6782f 100644 --- a/crates/uv-client/src/cached_client.rs +++ b/crates/uv-client/src/cached_client.rs @@ -12,14 +12,8 @@ use uv_cache::{CacheEntry, Freshness}; use uv_fs::write_atomic; use uv_redacted::DisplaySafeUrl; -use crate::BaseClient; -use crate::base_client::RetryState; -use crate::error::ProblemDetails; -use crate::{ - Error, ErrorKind, - httpcache::{AfterResponse, BeforeRequest, CachePolicy, CachePolicyBuilder}, - rkyvutil::OwnedArchive, -}; +use crate::httpcache::{AfterResponse, BeforeRequest, CachePolicy, CachePolicyBuilder}; +use crate::{BaseClient, Error, ErrorKind, OwnedArchive, ProblemDetails, RetryState}; /// A trait the generalizes (de)serialization at a high level. /// diff --git a/crates/uv-client/src/lib.rs b/crates/uv-client/src/lib.rs index 7482a90a48140..82887d1b437b4 100644 --- a/crates/uv-client/src/lib.rs +++ b/crates/uv-client/src/lib.rs @@ -1,9 +1,8 @@ pub use base_client::{ AuthIntegration, BaseClient, BaseClientBuilder, ClientBuildError, DEFAULT_CONNECT_TIMEOUT, DEFAULT_MAX_REDIRECTS, DEFAULT_READ_TIMEOUT, DEFAULT_READ_TIMEOUT_UPLOAD, DEFAULT_RETRIES, - ExtraMiddleware, RedirectClientWithMiddleware, RedirectPolicy, RequestBuilder, RetriableError, - RetryParsingError, RetryState, UvRetryableStrategy, fetch_with_url_fallback, - retryable_on_request_failure, + ExtraMiddleware, RedirectClientWithMiddleware, RedirectPolicy, RequestBuilder, + RetryParsingError, fetch_with_url_fallback, }; pub use cached_client::{CacheControl, CachedClient, CachedClientError, DataWithCachePolicy}; pub use error::{Error, ErrorKind, ProblemDetails, WrappedReqwestError}; @@ -13,6 +12,7 @@ pub use registry_client::{ Connectivity, MetadataFormat, RegistryClient, RegistryClientBuilder, SimpleDetailMetadata, SimpleDetailMetadatum, SimpleIndexMetadata, VersionFiles, }; +pub use retry::{RetriableError, RetryState, UvRetryableStrategy, retryable_on_request_failure}; pub use rkyvutil::{Deserializer, OwnedArchive, Serializer, Validator}; mod base_client; @@ -25,5 +25,6 @@ mod linehaul; mod middleware; mod registry_client; mod remote_metadata; +mod retry; mod rkyvutil; mod tls; diff --git a/crates/uv-client/src/retry.rs b/crates/uv-client/src/retry.rs new file mode 100644 index 0000000000000..a5e8dac8bed99 --- /dev/null +++ b/crates/uv-client/src/retry.rs @@ -0,0 +1,352 @@ +use std::error::Error; +use std::time::{Duration, SystemTime, SystemTimeError}; +use std::{io, iter}; + +use http::status::StatusCode; +use itertools::Itertools; +use reqwest::Response; +use reqwest_retry::policies::ExponentialBackoff; +use reqwest_retry::{ + RetryPolicy, Retryable, RetryableStrategy, default_on_request_error, default_on_request_success, +}; +use tracing::{debug, trace}; +use url::Url; + +use uv_redacted::DisplaySafeUrl; + +use crate::WrappedReqwestError; + +/// An extension over [`DefaultRetryableStrategy`] that logs transient request failures and +/// adds additional retry cases. +pub struct UvRetryableStrategy; + +impl RetryableStrategy for UvRetryableStrategy { + fn handle(&self, res: &Result) -> Option { + let retryable = match res { + Ok(success) => default_on_request_success(success), + Err(err) => retryable_on_request_failure(err), + }; + + // Log on transient errors + if retryable == Some(Retryable::Transient) { + match res { + Ok(response) => { + debug!("Transient request failure for: {}", response.url()); + } + Err(err) => { + let context = iter::successors(err.source(), |&err| err.source()) + .map(|err| format!(" Caused by: {err}")) + .join("\n"); + debug!( + "Transient request failure for {}, retrying: {err}\n{context}", + err.url().map(Url::as_str).unwrap_or("unknown URL") + ); + } + } + } + retryable + } +} + +/// Per-request retry state and policy. +pub struct RetryState { + retry_policy: ExponentialBackoff, + start_time: SystemTime, + total_retries: u32, + url: DisplaySafeUrl, +} + +impl RetryState { + /// Initialize the [`RetryState`] and record the start time for the retry policy. + pub fn start(retry_policy: ExponentialBackoff, url: impl Into) -> Self { + Self { + retry_policy, + start_time: SystemTime::now(), + total_retries: 0, + url: url.into(), + } + } + + /// The number of retries across all requests. + /// + /// After a failed retryable request, this equals the maximum number of retries. + pub fn total_retries(&self) -> u32 { + self.total_retries + } + + /// The total duration from the first request to the (failure) of the last request. + pub fn duration(&self) -> Result { + self.start_time.elapsed() + } + + /// Determines whether request should be retried. + /// + /// Takes the number of retries from nested layers associated with the specific `err` type as + /// `error_retries`. + /// + /// Returns the backoff duration if the request should be retried. + #[must_use] + pub fn should_retry( + &mut self, + err: &(dyn Error + 'static), + error_retries: u32, + ) -> Option { + // If the middleware performed any retries, consider them in our budget. + self.total_retries += error_retries; + match retryable_on_request_failure(err) { + Some(Retryable::Transient) => { + // Capture `now` before calling the policy so that `execute_after` + // (computed from a `SystemTime::now()` inside the library) is always + // >= `now`, making `duration_since` reliable. + let now = SystemTime::now(); + let retry_decision = self + .retry_policy + .should_retry(self.start_time, self.total_retries); + if let reqwest_retry::RetryDecision::Retry { execute_after } = retry_decision { + let duration = execute_after + .duration_since(now) + .unwrap_or_else(|_| Duration::default()); + + self.total_retries += 1; + return Some(duration); + } + + None + } + Some(Retryable::Fatal) | None => None, + } + } + + /// Wait before retrying the request. + pub async fn sleep_backoff(&self, duration: Duration) { + debug!( + "Transient failure while handling response from {}; retrying after {:.1}s...", + self.url, + duration.as_secs_f32(), + ); + // TODO(konsti): Should we show a spinner plus a message in the CLI while + // waiting? + tokio::time::sleep(duration).await; + } +} + +/// Whether the error looks like a network error that should be retried. +/// +/// This is an extension over [`reqwest_middleware::default_on_request_failure`], which is missing +/// a number of cases: +/// * Inside the reqwest or reqwest-middleware error is an `io::Error` such as a broken pipe +/// * When streaming a response, a reqwest error may be hidden several layers behind errors +/// of different crates processing the stream, including `io::Error` layers +/// * Any `h2` error +pub fn retryable_on_request_failure(err: &(dyn Error + 'static)) -> Option { + // First, try to show a nice trace log + if let Some((Some(status), Some(url))) = find_source::(&err) + .map(|request_err| (request_err.status(), request_err.url())) + { + trace!( + "Considering retry of response HTTP {status} for {url}", + url = DisplaySafeUrl::from_url(url.clone()) + ); + } else { + trace!("Considering retry of error: {err:?}"); + } + + let mut has_known_error = false; + // IO Errors or reqwest errors may be nested through custom IO errors or stream processing + // crates + let mut current_source = Some(err); + while let Some(source) = current_source { + // Handle different kinds of reqwest error nesting not accessible by downcast. + let reqwest_err = if let Some(reqwest_err) = source.downcast_ref::() { + Some(reqwest_err) + } else if let Some(reqwest_err) = source + .downcast_ref::() + .and_then(|err| err.inner()) + { + Some(reqwest_err) + } else if let Some(reqwest_middleware::Error::Reqwest(reqwest_err)) = + source.downcast_ref::() + { + Some(reqwest_err) + } else { + None + }; + + if let Some(reqwest_err) = reqwest_err { + has_known_error = true; + // Ignore the default retry strategy returning fatal. + if default_on_request_error(reqwest_err) == Some(Retryable::Transient) { + trace!("Transient nested reqwest error"); + return Some(Retryable::Transient); + } + if is_retryable_status_error(reqwest_err) { + trace!("Transient nested reqwest status code error"); + return Some(Retryable::Transient); + } + + trace!("Fatal nested reqwest error"); + } else if source.downcast_ref::().is_some() { + // All h2 errors look like errors that should be retried + // https://github.com/astral-sh/uv/issues/15916 + trace!("Transient nested h2 error"); + return Some(Retryable::Transient); + } else if let Some(io_err) = source.downcast_ref::() { + has_known_error = true; + let retryable_io_err_kinds = [ + // https://github.com/astral-sh/uv/issues/12054 + io::ErrorKind::BrokenPipe, + // From reqwest-middleware + io::ErrorKind::ConnectionAborted, + // https://github.com/astral-sh/uv/issues/3514 + io::ErrorKind::ConnectionReset, + // https://github.com/astral-sh/uv/issues/14699 + io::ErrorKind::InvalidData, + // https://github.com/astral-sh/uv/issues/17697#issuecomment-3817060484 + io::ErrorKind::TimedOut, + // https://github.com/astral-sh/uv/issues/9246 + io::ErrorKind::UnexpectedEof, + ]; + if retryable_io_err_kinds.contains(&io_err.kind()) { + trace!("Transient IO error: `{}`", io_err.kind()); + return Some(Retryable::Transient); + } + + trace!( + "Fatal IO error `{}`, not a transient IO error kind", + io_err.kind() + ); + } + + current_source = source.source(); + } + + if !has_known_error { + trace!("Cannot retry error: neither an IO error nor a reqwest error"); + } + + None +} + +/// An error type that supports URL-fallback and exponential-backoff retry logic. +/// +/// Used by [`fetch_with_url_fallback`] to drive the retry loop without knowing the concrete error +/// type. +pub trait RetriableError: std::error::Error + Sized + 'static { + /// Returns `true` if an alternative URL should be tried immediately (without backoff). + fn should_try_next_url(&self) -> bool; + + /// Returns the number of inner retries already recorded in this error. + fn retries(&self) -> u32; + + /// Wrap the error to indicate that the operation was retried `retries` times before failing. + #[must_use] + fn into_retried(self, retries: u32, duration: Duration) -> Self; +} + +/// Whether the error is a status code error that is retryable. +/// +/// Port of `reqwest_retry::default_on_request_success`. +fn is_retryable_status_error(reqwest_err: &reqwest::Error) -> bool { + let Some(status) = reqwest_err.status() else { + return false; + }; + status.is_server_error() + || status == StatusCode::REQUEST_TIMEOUT + || status == StatusCode::TOO_MANY_REQUESTS +} + +/// Find the first source error of a specific type. +/// +/// See +fn find_source(orig: &dyn Error) -> Option<&E> { + let mut cause = orig.source(); + while let Some(err) = cause { + if let Some(typed) = err.downcast_ref() { + return Some(typed); + } + cause = err.source(); + } + None +} + +#[cfg(test)] +mod tests { + use super::*; + + use anyhow::Result; + use insta::assert_debug_snapshot; + use reqwest::Client; + use reqwest_middleware::ClientWithMiddleware; + use wiremock::matchers::path; + use wiremock::{Mock, MockServer, ResponseTemplate}; + + use crate::{UvRetryableStrategy, retryable_on_request_failure}; + + /// Enumerate which status codes we are retrying. + #[tokio::test] + async fn retried_status_codes() -> Result<()> { + let server = MockServer::start().await; + let client = Client::default(); + let middleware_client = ClientWithMiddleware::default(); + let mut retried = Vec::new(); + for status in 100..599 { + // Test all standard status codes and an example for a non-RFC code used in the wild. + if StatusCode::from_u16(status)?.canonical_reason().is_none() && status != 420 { + continue; + } + + Mock::given(path(format!("/{status}"))) + .respond_with(ResponseTemplate::new(status)) + .mount(&server) + .await; + + let response = middleware_client + .get(format!("{}/{}", server.uri(), status)) + .send() + .await; + + let middleware_retry = + UvRetryableStrategy.handle(&response) == Some(Retryable::Transient); + + let response = client + .get(format!("{}/{}", server.uri(), status)) + .send() + .await?; + + let uv_retry = match response.error_for_status() { + Ok(_) => false, + Err(err) => retryable_on_request_failure(&err) == Some(Retryable::Transient), + }; + + // Ensure we're retrying the same status code as the reqwest_retry crate. We may choose + // to deviate from this later. + assert_eq!(middleware_retry, uv_retry); + if uv_retry { + retried.push(status); + } + } + + assert_debug_snapshot!(retried, @" + [ + 100, + 102, + 103, + 408, + 429, + 500, + 501, + 502, + 503, + 504, + 505, + 506, + 507, + 508, + 510, + 511, + ] + "); + + Ok(()) + } +} From 44e1cf8c1fa565b2aecb2c2c27e569621fc9cdc8 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Mon, 20 Apr 2026 09:36:39 -0500 Subject: [PATCH 16/70] Use a no-op timestamp for `exclude-newer` in lockfiles when a relative value is configured (#19022) Closes https://github.com/astral-sh/uv/issues/18708 This is a backwards compatible approach to resolve the issue where this timestamp is causing merge conflicts. This has no effect other than eliding the timestamp that was used for resolution. This value is write-only when using relative `exclude-newer` values. --------- Co-authored-by: Claude --- crates/uv-resolver/src/exclude_newer.rs | 4 +++- crates/uv-resolver/src/lock/mod.rs | 21 ++++++++++++++----- .../tests/it/lock_exclude_newer_relative.rs | 20 ++++++++---------- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/crates/uv-resolver/src/exclude_newer.rs b/crates/uv-resolver/src/exclude_newer.rs index 6a666e5c79768..ae9ed57ec4f51 100644 --- a/crates/uv-resolver/src/exclude_newer.rs +++ b/crates/uv-resolver/src/exclude_newer.rs @@ -545,7 +545,9 @@ impl ExcludeNewer { match (&self.global, &other.global) { (Some(self_global), Some(other_global)) => { if let Some(change) = compare_exclude_newer_value(self_global, other_global) { - return Some(ExcludeNewerChange::GlobalChanged(change)); + if !change.is_relative_timestamp_change() { + return Some(ExcludeNewerChange::GlobalChanged(change)); + } } } (None, Some(global)) => { diff --git a/crates/uv-resolver/src/lock/mod.rs b/crates/uv-resolver/src/lock/mod.rs index ce4a200f453a0..90a575743f4ae 100644 --- a/crates/uv-resolver/src/lock/mod.rs +++ b/crates/uv-resolver/src/lock/mod.rs @@ -1161,10 +1161,18 @@ impl Lock { if !exclude_newer.is_empty() { // Always serialize global exclude-newer as a string if let Some(global) = &exclude_newer.global { - options_table.insert("exclude-newer", value(global.to_string())); - // Serialize the original span if present if let Some(span) = global.span() { + // When a relative span is present, write a no-op timestamp to avoid + // merge conflicts in the lockfile. In a future version of uv, we'll drop + // this field entirely but it's retained for backwards compatibility for now. + let mut noop = value("0001-01-01T00:00:00Z"); + if let Item::Value(ref mut v) = noop { + v.decor_mut().set_suffix(" # This has no effect and is included for backwards compatibility when using relative exclude-newer values."); + } + options_table.insert("exclude-newer", noop); options_table.insert("exclude-newer-span", value(span.to_string())); + } else { + options_table.insert("exclude-newer", value(global.to_string())); } } @@ -2349,10 +2357,13 @@ struct ExcludeNewerWire { impl From for ExcludeNewer { fn from(wire: ExcludeNewerWire) -> Self { + let global = match (wire.exclude_newer, wire.exclude_newer_span) { + (Some(timestamp), span) => Some(ExcludeNewerValue::new(timestamp, span)), + (None, Some(span)) => Some(ExcludeNewerValue::new(Timestamp::UNIX_EPOCH, Some(span))), + (None, None) => None, + }; Self { - global: wire - .exclude_newer - .map(|timestamp| ExcludeNewerValue::new(timestamp, wire.exclude_newer_span)), + global, package: wire.exclude_newer_package, } } diff --git a/crates/uv/tests/it/lock_exclude_newer_relative.rs b/crates/uv/tests/it/lock_exclude_newer_relative.rs index 1de52f286db53..34d1d8caca57d 100644 --- a/crates/uv/tests/it/lock_exclude_newer_relative.rs +++ b/crates/uv/tests/it/lock_exclude_newer_relative.rs @@ -48,7 +48,7 @@ fn lock_exclude_newer_relative() -> Result<()> { requires-python = ">=3.12" [options] - exclude-newer = "2024-04-10T00:00:00Z" + exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. exclude-newer-span = "P3W" [[package]] @@ -118,7 +118,7 @@ fn lock_exclude_newer_relative() -> Result<()> { requires-python = ">=3.12" [options] - exclude-newer = "2024-04-17T00:00:00Z" + exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. exclude-newer-span = "P2W" [[package]] @@ -167,7 +167,7 @@ fn lock_exclude_newer_relative() -> Result<()> { requires-python = ">=3.12" [options] - exclude-newer = "2024-05-18T00:00:00Z" + exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. exclude-newer-span = "P2W" [[package]] @@ -562,7 +562,7 @@ fn lock_exclude_newer_relative_pyproject() -> Result<()> { requires-python = ">=3.12" [options] - exclude-newer = "2024-04-10T00:00:00Z" + exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. exclude-newer-span = "P3W" [[package]] @@ -716,7 +716,7 @@ fn lock_exclude_newer_relative_global_and_package() -> Result<()> { requires-python = ">=3.12" [options] - exclude-newer = "2024-04-10T00:00:00Z" + exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. exclude-newer-span = "P3W" [options.exclude-newer-package] @@ -909,7 +909,7 @@ fn lock_exclude_newer_relative_global_and_package() -> Result<()> { requires-python = ">=3.12" [options] - exclude-newer = "2024-04-10T00:00:00Z" + exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. exclude-newer-span = "P3W" [options.exclude-newer-package] @@ -1237,7 +1237,7 @@ fn lock_exclude_newer_relative_no_timestamp_in_lockfile() -> Result<()> { requires-python = ">=3.12" [options] - exclude-newer = "2024-04-10T00:00:00Z" + exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. exclude-newer-span = "P3W" [[package]] @@ -1262,7 +1262,7 @@ fn lock_exclude_newer_relative_no_timestamp_in_lockfile() -> Result<()> { "#); // Manually remove the exclude-newer timestamp from the lockfile, leaving the span. - let lock = lock.replace("exclude-newer = \"2024-04-10T00:00:00Z\"\n", ""); + let lock = lock.replace("exclude-newer = \"0001-01-01T00:00:00Z\" # This has no effect and is included for backwards compatibility when using relative exclude-newer values.\n", ""); context.temp_dir.child("uv.lock").write_str(&lock)?; // The lockfile now has no exclude-newer, but `pyproject.toml` still configures one, @@ -1276,11 +1276,10 @@ fn lock_exclude_newer_relative_no_timestamp_in_lockfile() -> Result<()> { ----- stdout ----- ----- stderr ----- - Resolving despite existing lockfile due to addition of global exclude newer 2024-04-10T00:00:00Z Resolved 2 packages in [TIME] "); - // The lockfile should have exclude-newer restored. + // The lockfile retains the span but the timestamp is not restored or updated. let lock = context.read("uv.lock"); assert_snapshot!(lock, @r#" version = 1 @@ -1288,7 +1287,6 @@ fn lock_exclude_newer_relative_no_timestamp_in_lockfile() -> Result<()> { requires-python = ">=3.12" [options] - exclude-newer = "2024-04-10T00:00:00Z" exclude-newer-span = "P3W" [[package]] From 79f791826f20cb3dddcd4d84eb0d11e7ca4aa78c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 12:36:19 -0400 Subject: [PATCH 17/70] Update Rust crate webpki to v0.103.12 (#19029) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [webpki](https://redirect.github.com/rustls/webpki) | workspace.dependencies | patch | `0.103.10` → `0.103.12` | ### GitHub Vulnerability Alerts #### [GHSA-xgp8-3hg3-c2mh](https://redirect.github.com/rustls/webpki/security/advisories/GHSA-xgp8-3hg3-c2mh) Permitted subtree name constraints for DNS names were accepted for certificates asserting a wildcard name. This was incorrect because, given a name constraint of `accept.example.com`, `*.example.com` could feasibly allow a name of `reject.example.com` which is outside the constraint. This is very similar to [CVE-2025-61727](https://go.dev/issue/76442). Since name constraints are restrictions on otherwise properly-issued certificates, this bug is reachable only after signature verification and requires misissuance to exploit. ##### Severity - CVSS Score: 2.2 / 10 (Low) - Vector String: `CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:N/I:L/A:N` #### [GHSA-965h-392x-2mh5](https://redirect.github.com/rustls/webpki/security/advisories/GHSA-965h-392x-2mh5) Name constraints for URI names were ignored and therefore accepted. Note this library does not provide an API for asserting URI names, and URI name constraints are otherwise not implemented. URI name constraints are now rejected unconditionally. Since name constraints are restrictions on otherwise properly-issued certificates, this bug is reachable only after signature verification and requires misissuance to exploit. ##### Severity - CVSS Score: 2.2 / 10 (Low) - Vector String: `CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:N/I:L/A:N` --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - "" - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/astral-sh/uv). --------- Signed-off-by: William Woodruff Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: William Woodruff --- Cargo.lock | 4 ++-- crates/uv-client/tests/it/ssl_certs.rs | 27 -------------------------- 2 files changed, 2 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a73feacab52ba..57a4344ec8716 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4256,9 +4256,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.10" +version = "0.103.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +checksum = "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06" dependencies = [ "aws-lc-rs", "ring", diff --git a/crates/uv-client/tests/it/ssl_certs.rs b/crates/uv-client/tests/it/ssl_certs.rs index 6b0df40b97861..7a284887ecd96 100644 --- a/crates/uv-client/tests/it/ssl_certs.rs +++ b/crates/uv-client/tests/it/ssl_certs.rs @@ -45,20 +45,6 @@ impl TestCertificate { Self::persist(ca, server, &client) } - /// Generate a fresh certificate set whose CA contains an unsupported - /// critical extension. - fn new_with_unsupported_critical_ca_extension() -> Result { - let mut unsupported_extension = CustomExtension::from_oid_content( - &[1, 2, 3, 4], - [vec![0x0c, 0x0b], b"unsupported".to_vec()].concat(), - ); - unsupported_extension.set_criticality(true); - - let (ca, server, client) = - generate_self_signed_certs_with_ca_custom_extensions(vec![unsupported_extension])?; - Self::persist(ca, server, &client) - } - /// Generate a fresh certificate set whose CA contains a duplicate /// `basicConstraints` extension, which webpki rejects as an invalid trust /// anchor. @@ -466,19 +452,6 @@ async fn test_ssl_cert_file_valid() -> Result<()> { Ok(()) } -/// If `SSL_CERT_FILE` contains only an invalid certificate with an -/// unsupported critical extension, the invalid certificate is ignored and the -/// client falls back to webpki roots. -#[tokio::test] -async fn test_ssl_cert_file_unsupported_critical_extension_falls_back() -> Result<()> { - let cert = TestCertificate::new_with_unsupported_critical_ca_extension()?; - client() - .ssl_cert_file(&cert.trust_path) - .expect_https_connect_fails(&cert) - .await; - Ok(()) -} - /// If `SSL_CERT_FILE` contains only an invalid trust anchor, the invalid /// certificate is ignored and the client falls back to webpki roots. #[tokio::test] From 6a847f4190c9f24097b9f9793667764b7ef542f1 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Mon, 20 Apr 2026 12:39:44 -0400 Subject: [PATCH 18/70] Support `pip uninstall -y` (#19082) ## Summary Fixes #19077. Both `-y` and `--yes` are now accepted (as compat no-ops) to `pip uninstall`. ## Test Plan Added two integration tests confirming that the flags are no-ops (besides emitting a warning). Signed-off-by: William Woodruff --- crates/uv-cli/src/compat.rs | 34 ++++++++++++++++++++++++ crates/uv-cli/src/lib.rs | 2 +- crates/uv/src/lib.rs | 2 ++ crates/uv/tests/it/pip_uninstall.rs | 40 +++++++++++++++++++++++++++++ 4 files changed, 77 insertions(+), 1 deletion(-) diff --git a/crates/uv-cli/src/compat.rs b/crates/uv-cli/src/compat.rs index 344d1a4e7ee54..75b46a4e30920 100644 --- a/crates/uv-cli/src/compat.rs +++ b/crates/uv-cli/src/compat.rs @@ -310,6 +310,40 @@ impl CompatArgs for VenvCompatArgs { } } +/// Arguments for `pip uninstall` compatibility. +/// +/// These represent a subset of the `pip uninstall` interface that uv supports by default. +#[derive(Args)] +pub struct PipUninstallCompatArgs { + /// Don't ask for confirmation of uninstall deletions. + /// + /// This option is for compatibility with `pip uninstall` and has no effect. + #[clap(short, long, hide = true)] + yes: bool, + + #[clap(long, hide = true)] + disable_pip_version_check: bool, +} + +impl CompatArgs for PipUninstallCompatArgs { + /// Validate the arguments passed for `pip uninstall` compatibility. + /// + /// This method will warn when an argument is passed that has no effect but matches uv's + /// behavior. If an argument is passed that does _not_ match uv's behavior, this method will + /// return an error. + fn validate(&self) -> Result<()> { + if self.yes { + warn_user!("`--yes` has no effect (uv never asks for confirmation)"); + } + + if self.disable_pip_version_check { + warn_user!("pip's `--disable-pip-version-check` has no effect"); + } + + Ok(()) + } +} + /// Arguments for `pip install` compatibility. /// /// These represent a subset of the `pip install` interface that uv supports by default. diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 490464508a4a3..a1f3d4b80029a 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -2551,7 +2551,7 @@ pub struct PipUninstallArgs { pub dry_run: bool, #[command(flatten)] - pub compat_args: compat::PipGlobalCompatArgs, + pub compat_args: compat::PipUninstallCompatArgs, } #[derive(Args)] diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index e77852ef86124..fd8aeb74f3b8b 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -962,6 +962,8 @@ async fn run(cli: Cli) -> Result { Commands::Pip(PipNamespace { command: PipCommand::Uninstall(args), }) => { + args.compat_args.validate()?; + // Resolve the settings from the command-line arguments and workspace configuration. let args = PipUninstallSettings::resolve(args, filesystem, environment); show_settings!(args); diff --git a/crates/uv/tests/it/pip_uninstall.rs b/crates/uv/tests/it/pip_uninstall.rs index 4cd6b26c06d0c..e475ac5be99e2 100644 --- a/crates/uv/tests/it/pip_uninstall.rs +++ b/crates/uv/tests/it/pip_uninstall.rs @@ -579,3 +579,43 @@ fn uninstall_record_path_traversal() -> Result<()> { Ok(()) } + +/// `--yes` is accepted for `pip uninstall` compatibility, but emits a warning. +#[test] +fn yes_flag() { + let context = uv_test::test_context!("3.12"); + + uv_snapshot!(context.filters(), context.pip_uninstall() + .arg("--yes") + .arg("flask"), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: `--yes` has no effect (uv never asks for confirmation) + warning: Skipping flask as it is not installed + warning: No packages to uninstall + " + ); +} + +/// `-y` is accepted for `pip uninstall` compatibility, but emits a warning. +#[test] +fn yes_short_flag() { + let context = uv_test::test_context!("3.12"); + + uv_snapshot!(context.filters(), context.pip_uninstall() + .arg("-y") + .arg("flask"), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: `--yes` has no effect (uv never asks for confirmation) + warning: Skipping flask as it is not installed + warning: No packages to uninstall + " + ); +} From 6bbe6839cfafb9e231d4737948b41e38324795ac Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 14:29:25 -0400 Subject: [PATCH 19/70] Update maturin to v1.13.1 (#19064) --- .github/workflows/build-release-binaries.yml | 44 ++++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/.github/workflows/build-release-binaries.yml b/.github/workflows/build-release-binaries.yml index bee6b878e1941..dc9857c6963f7 100644 --- a/.github/workflows/build-release-binaries.yml +++ b/.github/workflows/build-release-binaries.yml @@ -48,7 +48,7 @@ jobs: - name: "Build sdist" uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 with: - maturin-version: v1.12.6 + maturin-version: v1.13.1 command: sdist args: --out dist - name: "Test sdist" @@ -69,7 +69,7 @@ jobs: - name: "Build sdist uv-build" uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 with: - maturin-version: v1.12.6 + maturin-version: v1.13.1 command: sdist args: --out crates/uv-build/dist -m crates/uv-build/Cargo.toml - name: "Fix Cargo.lock in sdist uv-build" @@ -107,7 +107,7 @@ jobs: - name: "Build wheels - x86_64" uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 with: - maturin-version: v1.12.6 + maturin-version: v1.13.1 target: x86_64 args: --release --locked --out dist --features self-update --compatibility pypi env: @@ -140,7 +140,7 @@ jobs: - name: "Build wheels uv-build - x86_64" uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 with: - maturin-version: v1.12.6 + maturin-version: v1.13.1 target: x86_64 args: --profile minimal-size --locked --out crates/uv-build/dist -m crates/uv-build/Cargo.toml --compatibility pypi env: @@ -173,7 +173,7 @@ jobs: - name: "Build wheels - aarch64" uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 with: - maturin-version: v1.12.6 + maturin-version: v1.13.1 target: aarch64 manylinux: 2_17 args: --release --locked --out dist --features self-update --compatibility pypi @@ -213,7 +213,7 @@ jobs: - name: "Build wheels uv-build - aarch64" uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 with: - maturin-version: v1.12.6 + maturin-version: v1.13.1 target: aarch64 args: --profile minimal-size --locked --out crates/uv-build/dist -m crates/uv-build/Cargo.toml --compatibility pypi env: @@ -270,7 +270,7 @@ jobs: - name: "Build wheels" uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 with: - maturin-version: v1.12.6 + maturin-version: v1.13.1 target: ${{ matrix.platform.target }} args: --release --locked --out dist --features self-update,windows-gui-bin --compatibility pypi env: @@ -312,7 +312,7 @@ jobs: - name: "Build wheels uv-build" uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 with: - maturin-version: v1.12.6 + maturin-version: v1.13.1 target: ${{ matrix.platform.target }} args: --profile minimal-size --locked --out crates/uv-build/dist -m crates/uv-build/Cargo.toml --compatibility pypi env: @@ -353,7 +353,7 @@ jobs: - name: "Build wheels" uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 with: - maturin-version: v1.12.6 + maturin-version: v1.13.1 target: ${{ matrix.target }} # Generally, we try to build in a target docker container. In this case however, a # 32-bit compiler runs out of memory (4GB memory limit for 32-bit), so we cross compile @@ -424,7 +424,7 @@ jobs: - name: "Build wheels uv-build" uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 with: - maturin-version: v1.12.6 + maturin-version: v1.13.1 target: ${{ matrix.target }} manylinux: 2_17 docker-options: -e CARGO @@ -483,7 +483,7 @@ jobs: - name: "Build wheels" uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 with: - maturin-version: v1.12.6 + maturin-version: v1.13.1 target: ${{ matrix.platform.target }} manylinux: ${{ matrix.platform.manylinux }} docker-options: -e CARGO ${{ matrix.platform.maturin_docker_options }} @@ -541,7 +541,7 @@ jobs: - name: "Build wheels uv-build" uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 with: - maturin-version: v1.12.6 + maturin-version: v1.13.1 target: ${{ matrix.platform.target }} manylinux: ${{ matrix.platform.manylinux }} docker-options: -e CARGO ${{ matrix.platform.maturin_docker_options }} @@ -600,7 +600,7 @@ jobs: - name: "Build wheels" uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 with: - maturin-version: v1.12.6 + maturin-version: v1.13.1 target: ${{ matrix.platform.target }} manylinux: 2_17 docker-options: -e CARGO ${{ matrix.platform.maturin_docker_options }} @@ -663,7 +663,7 @@ jobs: - name: "Build wheels uv-build" uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 with: - maturin-version: v1.12.6 + maturin-version: v1.13.1 target: ${{ matrix.platform.target }} manylinux: 2_17 docker-options: -e CARGO ${{ matrix.platform.maturin_docker_options }} @@ -723,7 +723,7 @@ jobs: - name: "Build wheels" uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 with: - maturin-version: v1.12.6 + maturin-version: v1.13.1 target: ${{ matrix.platform.target }} manylinux: 2_17 docker-options: -e CARGO ${{ matrix.platform.maturin_docker_options }} @@ -784,7 +784,7 @@ jobs: - name: "Build wheels uv-build" uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 with: - maturin-version: v1.12.6 + maturin-version: v1.13.1 target: ${{ matrix.platform.target }} manylinux: 2_17 docker-options: -e CARGO ${{ matrix.platform.maturin_docker_options }} @@ -832,7 +832,7 @@ jobs: - name: "Build wheels" uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 with: - maturin-version: v1.12.6 + maturin-version: v1.13.1 target: ${{ matrix.platform.target }} manylinux: 2_31 docker-options: -e CARGO ${{ matrix.platform.maturin_docker_options }} @@ -891,7 +891,7 @@ jobs: - name: "Build wheels uv-build" uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 with: - maturin-version: v1.12.6 + maturin-version: v1.13.1 target: ${{ matrix.platform.target }} manylinux: 2_31 docker-options: -e CARGO ${{ matrix.platform.maturin_docker_options }} @@ -949,7 +949,7 @@ jobs: - name: "Build wheels" uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 with: - maturin-version: v1.12.6 + maturin-version: v1.13.1 target: ${{ matrix.target }} manylinux: musllinux_1_1 docker-options: -e CARGO @@ -1001,7 +1001,7 @@ jobs: - name: "Build wheels uv-build" uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 with: - maturin-version: v1.12.6 + maturin-version: v1.13.1 target: ${{ matrix.target }} manylinux: musllinux_1_1 docker-options: -e CARGO @@ -1060,7 +1060,7 @@ jobs: - name: "Build wheels" uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 with: - maturin-version: v1.12.6 + maturin-version: v1.13.1 target: ${{ matrix.platform.target }} manylinux: musllinux_1_1 # Tag the musl builds as manylinux 2_17 fallback cause the aarch64 build only support 2_28 @@ -1140,7 +1140,7 @@ jobs: - name: "Build wheels" uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 with: - maturin-version: v1.12.6 + maturin-version: v1.13.1 target: ${{ matrix.platform.target }} manylinux: musllinux_1_1 args: --profile minimal-size --locked ${{ matrix.platform.arch == 'aarch64' && '--compatibility 2_17' || ''}} --out crates/uv-build/dist -m crates/uv-build/Cargo.toml --compatibility pypi From f376822148ba121f5da27dcb76817ad9805ac96c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 14:29:47 -0400 Subject: [PATCH 20/70] Update aws-actions/configure-aws-credentials action to v6.1.0 (#19062) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [aws-actions/configure-aws-credentials](https://redirect.github.com/aws-actions/configure-aws-credentials) | action | minor | `v6.0.0` → `v6.1.0` | --- ### Release Notes
aws-actions/configure-aws-credentials (aws-actions/configure-aws-credentials) ### [`v6.1.0`](https://redirect.github.com/aws-actions/configure-aws-credentials/releases/tag/v6.1.0) [Compare Source](https://redirect.github.com/aws-actions/configure-aws-credentials/compare/v6...v6.1.0) ##### Features - add skip cleanup option ([#​1716](https://redirect.github.com/aws-actions/configure-aws-credentials/issues/1716)) ([11b1c58](https://redirect.github.com/aws-actions/configure-aws-credentials/commit/11b1c58b24724e66aa52a847862a0c1b0c4b0c7b)), closes [#​1545](https://redirect.github.com/aws-actions/configure-aws-credentials/issues/1545) - Support usage of AWS Profiles ([#​1696](https://redirect.github.com/aws-actions/configure-aws-credentials/issues/1696)) ([a7f0c82](https://redirect.github.com/aws-actions/configure-aws-credentials/commit/a7f0c828ac76e0d049e34c920172c60f579f9eb3))
--- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/astral-sh/uv). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/test-integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-integration.yml b/.github/workflows/test-integration.yml index 34bea2734898c..e0ea4d354effa 100644 --- a/.github/workflows/test-integration.yml +++ b/.github/workflows/test-integration.yml @@ -934,7 +934,7 @@ jobs: run: chmod +x ./uv - name: "Configure AWS credentials" - uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v6.0.0 + uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37 # v6.1.0 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} From 7e5d83ae4bb3d97fabf03c8541516bee28ad3160 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 14:29:55 -0400 Subject: [PATCH 21/70] Update PyO3/maturin-action action to v1.51.0 (#19066) --- .github/workflows/build-release-binaries.yml | 44 ++++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/.github/workflows/build-release-binaries.yml b/.github/workflows/build-release-binaries.yml index dc9857c6963f7..a743d37c0270b 100644 --- a/.github/workflows/build-release-binaries.yml +++ b/.github/workflows/build-release-binaries.yml @@ -46,7 +46,7 @@ jobs: - name: "Prep README.md" run: python scripts/transform_readme.py --target pypi - name: "Build sdist" - uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 + uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 with: maturin-version: v1.13.1 command: sdist @@ -67,7 +67,7 @@ jobs: # uv-build - name: "Build sdist uv-build" - uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 + uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 with: maturin-version: v1.13.1 command: sdist @@ -105,7 +105,7 @@ jobs: # uv - name: "Build wheels - x86_64" - uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 + uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 with: maturin-version: v1.13.1 target: x86_64 @@ -138,7 +138,7 @@ jobs: # uv-build - name: "Build wheels uv-build - x86_64" - uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 + uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 with: maturin-version: v1.13.1 target: x86_64 @@ -171,7 +171,7 @@ jobs: # uv - name: "Build wheels - aarch64" - uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 + uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 with: maturin-version: v1.13.1 target: aarch64 @@ -211,7 +211,7 @@ jobs: # uv-build - name: "Build wheels uv-build - aarch64" - uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 + uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 with: maturin-version: v1.13.1 target: aarch64 @@ -268,7 +268,7 @@ jobs: echo "C:\Program Files\NASM" | Out-File -FilePath $env:GITHUB_PATH -Append # uv - name: "Build wheels" - uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 + uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 with: maturin-version: v1.13.1 target: ${{ matrix.platform.target }} @@ -310,7 +310,7 @@ jobs: # uv-build - name: "Build wheels uv-build" - uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 + uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 with: maturin-version: v1.13.1 target: ${{ matrix.platform.target }} @@ -351,7 +351,7 @@ jobs: # uv - name: "Build wheels" - uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 + uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 with: maturin-version: v1.13.1 target: ${{ matrix.target }} @@ -422,7 +422,7 @@ jobs: # uv-build - name: "Build wheels uv-build" - uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 + uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 with: maturin-version: v1.13.1 target: ${{ matrix.target }} @@ -481,7 +481,7 @@ jobs: # uv - name: "Build wheels" - uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 + uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 with: maturin-version: v1.13.1 target: ${{ matrix.platform.target }} @@ -539,7 +539,7 @@ jobs: # uv-build - name: "Build wheels uv-build" - uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 + uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 with: maturin-version: v1.13.1 target: ${{ matrix.platform.target }} @@ -598,7 +598,7 @@ jobs: # uv - name: "Build wheels" - uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 + uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 with: maturin-version: v1.13.1 target: ${{ matrix.platform.target }} @@ -661,7 +661,7 @@ jobs: # uv-build - name: "Build wheels uv-build" - uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 + uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 with: maturin-version: v1.13.1 target: ${{ matrix.platform.target }} @@ -721,7 +721,7 @@ jobs: # uv - name: "Build wheels" - uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 + uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 with: maturin-version: v1.13.1 target: ${{ matrix.platform.target }} @@ -782,7 +782,7 @@ jobs: # uv-build - name: "Build wheels uv-build" - uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 + uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 with: maturin-version: v1.13.1 target: ${{ matrix.platform.target }} @@ -830,7 +830,7 @@ jobs: # uv - name: "Build wheels" - uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 + uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 with: maturin-version: v1.13.1 target: ${{ matrix.platform.target }} @@ -889,7 +889,7 @@ jobs: # uv-build - name: "Build wheels uv-build" - uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 + uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 with: maturin-version: v1.13.1 target: ${{ matrix.platform.target }} @@ -947,7 +947,7 @@ jobs: # uv - name: "Build wheels" - uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 + uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 with: maturin-version: v1.13.1 target: ${{ matrix.target }} @@ -999,7 +999,7 @@ jobs: # uv-build - name: "Build wheels uv-build" - uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 + uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 with: maturin-version: v1.13.1 target: ${{ matrix.target }} @@ -1058,7 +1058,7 @@ jobs: # uv - name: "Build wheels" - uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 + uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 with: maturin-version: v1.13.1 target: ${{ matrix.platform.target }} @@ -1138,7 +1138,7 @@ jobs: # uv-build - name: "Build wheels" - uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1 + uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 with: maturin-version: v1.13.1 target: ${{ matrix.platform.target }} From 4f129b2b0506e07c0ac55a35f58bb96eb7f51a30 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 14:30:22 -0400 Subject: [PATCH 22/70] Update astral-sh/setup-uv action to v8.1.0 (#19061) --- .github/workflows/build-release-binaries.yml | 2 +- .github/workflows/check-docs.yml | 2 +- .github/workflows/check-fmt.yml | 2 +- .github/workflows/check-lint.yml | 6 +++--- .github/workflows/publish-docs.yml | 2 +- .github/workflows/publish-pypi.yml | 4 ++-- .github/workflows/publish-versions.yml | 2 +- .github/workflows/sync-python-releases.yml | 2 +- .github/workflows/test-windows-trampolines.yml | 2 +- .github/workflows/test.yml | 6 +++--- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build-release-binaries.yml b/.github/workflows/build-release-binaries.yml index a743d37c0270b..2ce284b46569e 100644 --- a/.github/workflows/build-release-binaries.yml +++ b/.github/workflows/build-release-binaries.yml @@ -1211,7 +1211,7 @@ jobs: with: persist-credentials: false - name: "Install uv" - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: pattern: wheels_*-* diff --git a/.github/workflows/check-docs.yml b/.github/workflows/check-docs.yml index 6f02357e57363..3a327133258a1 100644 --- a/.github/workflows/check-docs.yml +++ b/.github/workflows/check-docs.yml @@ -13,7 +13,7 @@ jobs: with: fetch-depth: 0 persist-credentials: false - - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: version: "0.11.6" diff --git a/.github/workflows/check-fmt.yml b/.github/workflows/check-fmt.yml index 42bf690cd360a..8142a1629ba2c 100644 --- a/.github/workflows/check-fmt.yml +++ b/.github/workflows/check-fmt.yml @@ -32,7 +32,7 @@ jobs: with: persist-credentials: false - name: "Install uv" - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: version: "0.11.6" - run: uvx ruff format --diff . diff --git a/.github/workflows/check-lint.yml b/.github/workflows/check-lint.yml index 7bfe3006439d9..1b07326154eda 100644 --- a/.github/workflows/check-lint.yml +++ b/.github/workflows/check-lint.yml @@ -26,7 +26,7 @@ jobs: with: persist-credentials: false - name: "Install uv" - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: version: "0.11.6" - run: uvx ruff check . @@ -39,7 +39,7 @@ jobs: with: persist-credentials: false - name: "Install uv" - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: version: "0.11.6" - run: | @@ -73,7 +73,7 @@ jobs: with: persist-credentials: false - name: "Install uv" - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: version: "0.11.6" - run: uvx --from 'validate-pyproject[all,store]' validate-pyproject pyproject.toml diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index 86a94bfd553d0..e2a87bf2735a1 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -73,7 +73,7 @@ jobs: echo "TIMESTAMP=$timestamp" >> $GITHUB_ENV - name: "Install uv" - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: version: "0.11.6" diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index c2e894c6536aa..85c915e352b32 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -21,7 +21,7 @@ jobs: id-token: write # For PyPI's trusted publishing steps: - name: "Install uv" - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: pattern: wheels_uv-* @@ -39,7 +39,7 @@ jobs: id-token: write # For PyPI's trusted publishing steps: - name: "Install uv" - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: pattern: wheels_uv_build-* diff --git a/.github/workflows/publish-versions.yml b/.github/workflows/publish-versions.yml index 7dba042cf55d9..1e4f51f2a614f 100644 --- a/.github/workflows/publish-versions.yml +++ b/.github/workflows/publish-versions.yml @@ -32,7 +32,7 @@ jobs: run: git clone https://${{ secrets.ASTRAL_VERSIONS_PAT }}@github.com/astral-sh/versions.git astral-versions - name: "Install uv" - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 - name: "Update versions" env: diff --git a/.github/workflows/sync-python-releases.yml b/.github/workflows/sync-python-releases.yml index ceca4c86dcef6..96c050eaa44c7 100644 --- a/.github/workflows/sync-python-releases.yml +++ b/.github/workflows/sync-python-releases.yml @@ -20,7 +20,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: version: "latest" enable-cache: true diff --git a/.github/workflows/test-windows-trampolines.yml b/.github/workflows/test-windows-trampolines.yml index acaa4ce228203..3955700200b96 100644 --- a/.github/workflows/test-windows-trampolines.yml +++ b/.github/workflows/test-windows-trampolines.yml @@ -20,7 +20,7 @@ jobs: with: persist-credentials: false - - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 - name: "Check windows crate versions match" run: uv run --no-project scripts/check-trampoline-version-consistency.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 803cb8731faad..13d1a9b7ddb9d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -43,7 +43,7 @@ jobs: - name: "Install Rust toolchain" run: rustup show - - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: version: "0.11.6" @@ -142,7 +142,7 @@ jobs: hdiutil attach /tmp/noreflink.dmg echo "HFS_MOUNT=/Volumes/NoReflink" >> "$GITHUB_ENV" - - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: version: "0.11.6" @@ -206,7 +206,7 @@ jobs: run: | Copy-Item -Path "${{ github.workspace }}" -Destination "$Env:UV_WORKSPACE" -Recurse - - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: version: "0.11.6" From c7c2c19c51b9cabe21d6a2e48b04ca33e2ccbae1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 14:30:35 -0400 Subject: [PATCH 23/70] Update Rust crate tokio to v1.51.1 (#19060) --- Cargo.lock | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 57a4344ec8716..4d9acdff0eb6d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -90,7 +90,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -101,7 +101,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -1318,7 +1318,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -1497,7 +1497,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -2365,7 +2365,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -2426,7 +2426,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde_core", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -2543,7 +2543,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cfc352a66ba903c23239ef51e809508b6fc2b0f90e3476ac7a9ff47e863ae95" dependencies = [ "scopeguard", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -2814,7 +2814,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "536bfad37a309d62069485248eeaba1e8d9853aaf951caaeaed0585a95346f08" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -2902,7 +2902,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -4188,7 +4188,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -4245,7 +4245,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -4733,7 +4733,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -4961,7 +4961,7 @@ dependencies = [ "getrandom 0.4.1", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -5217,9 +5217,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.51.0" +version = "1.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bd1c4c0fc4a7ab90fc15ef6daaa3ec3b893f004f915f2392557ed23237820cd" +checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c" dependencies = [ "bytes", "libc", @@ -5578,7 +5578,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ "memoffset", "tempfile", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -7754,7 +7754,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] From fcbb510efdfd521865f578975f65a1aaedf8c7bc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 14:30:55 -0400 Subject: [PATCH 24/70] Update Rust crate hashbrown to 0.17.0 (#19069) --- Cargo.lock | 10 ++++++++-- Cargo.toml | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4d9acdff0eb6d..721d2c3e655f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1943,6 +1943,12 @@ name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" dependencies = [ "allocator-api2", "equivalent", @@ -6930,7 +6936,7 @@ name = "uv-pypi-types" version = "0.0.40" dependencies = [ "anyhow", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "indexmap", "insta", "itertools 0.14.0", @@ -7111,7 +7117,7 @@ dependencies = [ "either", "fs-err", "futures", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "indexmap", "insta", "itertools 0.14.0", diff --git a/Cargo.toml b/Cargo.toml index f7318f557d016..df11156a00f5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -161,7 +161,7 @@ goblin = { version = "0.10.0", default-features = false, features = [ "pe64", ] } h2 = { version = "0.4.7" } -hashbrown = { version = "0.16.0" } +hashbrown = { version = "0.17.0" } hex = { version = "0.4.3" } html-escape = { version = "0.2.13" } http = { version = "1.1.0" } From e5ca84cadfff6d4a1ba625efb396f0d4f8d01467 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 14:31:07 -0400 Subject: [PATCH 25/70] Update Rust crate indexmap to v2.14.0 (#19070) --- Cargo.lock | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 721d2c3e655f2..f6e0597680b90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1955,6 +1955,12 @@ dependencies = [ "foldhash 0.2.0", ] +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + [[package]] name = "heck" version = "0.5.0" @@ -2280,12 +2286,12 @@ checksum = "09e54e57b4c48b40f7aec75635392b12b3421fa26fe8b4332e63138ed278459c" [[package]] name = "indexmap" -version = "2.13.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "serde", "serde_core", ] From eb903317e8adf878cbcf90568bd378d0ac028b9c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 14:32:04 -0400 Subject: [PATCH 26/70] Update dependency astral-sh/uv to v0.11.7 (#19056) --- .github/workflows/check-docs.yml | 2 +- .github/workflows/check-fmt.yml | 2 +- .github/workflows/check-lint.yml | 6 +++--- .github/workflows/publish-docs.yml | 2 +- .github/workflows/test.yml | 6 +++--- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/check-docs.yml b/.github/workflows/check-docs.yml index 3a327133258a1..99ee006e0aa4f 100644 --- a/.github/workflows/check-docs.yml +++ b/.github/workflows/check-docs.yml @@ -15,7 +15,7 @@ jobs: persist-credentials: false - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: - version: "0.11.6" + version: "0.11.7" - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 diff --git a/.github/workflows/check-fmt.yml b/.github/workflows/check-fmt.yml index 8142a1629ba2c..9c1ccae25b711 100644 --- a/.github/workflows/check-fmt.yml +++ b/.github/workflows/check-fmt.yml @@ -34,5 +34,5 @@ jobs: - name: "Install uv" uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: - version: "0.11.6" + version: "0.11.7" - run: uvx ruff format --diff . diff --git a/.github/workflows/check-lint.yml b/.github/workflows/check-lint.yml index 1b07326154eda..b2d4832ac7c8f 100644 --- a/.github/workflows/check-lint.yml +++ b/.github/workflows/check-lint.yml @@ -28,7 +28,7 @@ jobs: - name: "Install uv" uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: - version: "0.11.6" + version: "0.11.7" - run: uvx ruff check . ty: @@ -41,7 +41,7 @@ jobs: - name: "Install uv" uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: - version: "0.11.6" + version: "0.11.7" - run: | uvx ty check python/uv uvx \ @@ -75,7 +75,7 @@ jobs: - name: "Install uv" uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: - version: "0.11.6" + version: "0.11.7" - run: uvx --from 'validate-pyproject[all,store]' validate-pyproject pyproject.toml readme: diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index e2a87bf2735a1..88aa1b827cd63 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -75,7 +75,7 @@ jobs: - name: "Install uv" uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: - version: "0.11.6" + version: "0.11.7" - name: "Build docs" run: uv run --only-group docs mkdocs build --strict -f mkdocs.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 13d1a9b7ddb9d..c2b308f88d584 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -45,7 +45,7 @@ jobs: - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: - version: "0.11.6" + version: "0.11.7" - name: "Install required Python versions" run: uv python install @@ -144,7 +144,7 @@ jobs: - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: - version: "0.11.6" + version: "0.11.7" - name: "Install required Python versions" run: uv python install @@ -208,7 +208,7 @@ jobs: - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: - version: "0.11.6" + version: "0.11.7" - name: "Install required Python versions" run: uv python install From 602aae4417242d58ff103fc8c1a79397567c0fa0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 14:32:27 -0400 Subject: [PATCH 27/70] Update EmbarkStudios/cargo-deny-action action to v2.0.16 (#19057) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Type | Update | Change | Pending | |---|---|---|---|---| | [EmbarkStudios/cargo-deny-action](https://redirect.github.com/EmbarkStudios/cargo-deny-action) | action | patch | `v2.0.15` → `v2.0.16` | `v2.0.17` | --- ### Release Notes
EmbarkStudios/cargo-deny-action (EmbarkStudios/cargo-deny-action) ### [`v2.0.16`](https://redirect.github.com/EmbarkStudios/cargo-deny-action/releases/tag/v2.0.16): Release 2.0.16 - cargo-deny 0.19.1 [Compare Source](https://redirect.github.com/EmbarkStudios/cargo-deny-action/compare/v2.0.15...v2.0.16) ##### Fixed - [PR#833](https://redirect.github.com/EmbarkStudios/cargo-deny/pull/833) fixed an issue where the maximum advisory database staleness was over 14 years instead of the intended 90 days. - [PR#839](https://redirect.github.com/EmbarkStudios/cargo-deny/pull/839) fixed an issue where unsound advisories would appear for transitive dependencies despite requesting them only for workspace dependencies, resolving [#​829](https://redirect.github.com/EmbarkStudios/cargo-deny/issues/829). - [PR#840](https://redirect.github.com/EmbarkStudios/cargo-deny/pull/840) resolved [#​797](https://redirect.github.com/EmbarkStudios/cargo-deny/issues/797) by passing `--filter-platform` when collecting cargo metadata if only a single target was requested either in the config or via the command line. - [PR#841](https://redirect.github.com/EmbarkStudios/cargo-deny/pull/841) fixed an issue where `--frozen` would not disable fetching of the advisory DB, resolving [#​759](https://redirect.github.com/EmbarkStudios/cargo-deny/issues/759). - [PR#842](https://redirect.github.com/EmbarkStudios/cargo-deny/pull/842) and [PR#844](https://redirect.github.com/EmbarkStudios/cargo-deny/pull/844) updated crates. Notably `krates` was updated to resolve two issues with crates being pruned from the graph used when running checks. Resolving these two issues may mean that updating cargo-deny may highlight issues that were previously hidden. - [EmbarkStudios/krates#106](https://redirect.github.com/EmbarkStudios/krates/issues/106) would fail to pull in crates brought in via a feature if that crate had its `lib` target renamed by the package author. - [EmbarkStudios/krates#109](https://redirect.github.com/EmbarkStudios/krates/issues/109) would fail to bring in optional dependencies if they were brought in by a weak feature in a crate *also* brought in by a weak feature. ##### Changed - [PR#830](https://redirect.github.com/EmbarkStudios/cargo-deny/pull/830) removed `gix` in favor of shelling out to `git`. This massively improves build times and eases maintenance as `gix` bumps minor versions quite frequently. If cargo-deny is used in an environment that for some reason allows internet access but doesn't have `git` available, the advisory database would need to be updated before calling cargo-deny. - [PR#838](https://redirect.github.com/EmbarkStudios/cargo-deny/pull/838) removed `rustsec` in favor of manually implemented advisory parsing and checking, with a nightly cron job that checks that the implementation exactly matches rustsec on the official rustsec advisory db.
--- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/astral-sh/uv). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/check-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-lint.yml b/.github/workflows/check-lint.yml index b2d4832ac7c8f..4090e5d65c927 100644 --- a/.github/workflows/check-lint.yml +++ b/.github/workflows/check-lint.yml @@ -103,7 +103,7 @@ jobs: with: save-if: ${{ inputs.save-rust-cache == 'true' }} - name: "Check uv_build dependencies" - uses: EmbarkStudios/cargo-deny-action@3fd3802e88374d3fe9159b834c7714ec57d6c979 # v2.0.15 + uses: EmbarkStudios/cargo-deny-action@175dc7fd4fb85ec8f46948fb98f44db001149081 # v2.0.16 with: command: check bans manifest-path: crates/uv-build/Cargo.toml From 79357860e4cacf7c4aed6c1450c0db83409e2fcc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 14:32:33 -0400 Subject: [PATCH 28/70] Update actions/upload-artifact action to v7.0.1 (#19054) --- .github/workflows/bench.yml | 2 +- .github/workflows/build-dev-binaries.yml | 18 +++--- .github/workflows/build-release-binaries.yml | 64 ++++++++++---------- .github/workflows/test.yml | 6 +- 4 files changed, 45 insertions(+), 45 deletions(-) diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 64d73859b4b4b..8494dfb62434c 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -54,7 +54,7 @@ jobs: run: tar -cvf benchmarks-walltime.tar target/codspeed target/debug/uv .cache - name: "Upload benchmark artifacts" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: benchmarks-walltime path: benchmarks-walltime.tar diff --git a/.github/workflows/build-dev-binaries.yml b/.github/workflows/build-dev-binaries.yml index 62fc145775113..c9961e899ad03 100644 --- a/.github/workflows/build-dev-binaries.yml +++ b/.github/workflows/build-dev-binaries.yml @@ -35,7 +35,7 @@ jobs: run: cargo build --profile no-debug - name: "Upload binary" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: uv-linux-libc-${{ github.sha }} path: | @@ -63,7 +63,7 @@ jobs: run: cargo build --profile no-debug - name: "Upload binary" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: uv-linux-aarch64-${{ github.sha }} path: | @@ -99,7 +99,7 @@ jobs: run: cargo build --profile no-debug --target armv7-unknown-linux-gnueabihf --bin uv --bin uvx - name: "Upload binary" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: uv-linux-armv7-gnueabihf-${{ github.sha }} path: | @@ -132,7 +132,7 @@ jobs: run: cargo build --profile no-debug --target x86_64-unknown-linux-musl --bin uv --bin uvx - name: "Upload binary" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: uv-linux-musl-${{ github.sha }} path: | @@ -156,7 +156,7 @@ jobs: run: cargo build --profile no-debug --bin uv --bin uvx - name: "Upload binary" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: uv-macos-aarch64-${{ github.sha }} path: | @@ -180,7 +180,7 @@ jobs: run: cargo build --profile no-debug --bin uv --bin uvx - name: "Upload binary" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: uv-macos-x86_64-${{ github.sha }} path: | @@ -215,7 +215,7 @@ jobs: run: cargo build --profile no-debug --bin uv --bin uvx - name: "Upload binary" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: uv-windows-x86_64-${{ github.sha }} path: | @@ -250,7 +250,7 @@ jobs: run: cargo build --profile no-debug --bin uv --bin uvx - name: "Upload binary" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: uv-windows-aarch64-${{ github.sha }} path: | @@ -327,7 +327,7 @@ jobs: run: cargo build --profile no-debug --target aarch64-linux-android --bin uv --bin uvx - name: "Upload binary" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: uv-android-aarch64-${{ github.sha }} path: | diff --git a/.github/workflows/build-release-binaries.yml b/.github/workflows/build-release-binaries.yml index 2ce284b46569e..e11f6ced5c4fc 100644 --- a/.github/workflows/build-release-binaries.yml +++ b/.github/workflows/build-release-binaries.yml @@ -60,7 +60,7 @@ jobs: python -m ${MODULE_NAME} --help uvx --help - name: "Upload sdist" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: wheels_uv-sdist path: dist @@ -80,7 +80,7 @@ jobs: ${MODULE_NAME}-build --help python -m ${MODULE_NAME}_build --help - name: "Upload sdist uv-build" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: wheels_uv_build-sdist path: crates/uv-build/dist @@ -113,7 +113,7 @@ jobs: env: CARGO: ${{ github.workspace }}/scripts/cargo.sh - name: "Upload wheels" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: wheels_uv-macos-x86_64 path: dist @@ -129,7 +129,7 @@ jobs: tar czvf $ARCHIVE_FILE $ARCHIVE_NAME shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256 - name: "Upload binary" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: artifacts-macos-x86_64 path: | @@ -146,7 +146,7 @@ jobs: env: CARGO: ${{ github.workspace }}/scripts/cargo.sh - name: "Upload wheels uv-build" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: wheels_uv_build-macos-x86_64 path: crates/uv-build/dist @@ -186,7 +186,7 @@ jobs: python -m ${MODULE_NAME} --help uvx --help - name: "Upload wheels" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: wheels_uv-aarch64-apple-darwin path: dist @@ -202,7 +202,7 @@ jobs: tar czvf $ARCHIVE_FILE $ARCHIVE_NAME shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256 - name: "Upload binary" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: artifacts-aarch64-apple-darwin path: | @@ -224,7 +224,7 @@ jobs: ${MODULE_NAME}-build --help python -m ${MODULE_NAME}_build --help - name: "Upload wheels uv-build" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: wheels_uv_build-aarch64-apple-darwin path: crates/uv-build/dist @@ -286,7 +286,7 @@ jobs: uvx --help uvw --help - name: "Upload wheels" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: wheels_uv-${{ matrix.platform.target }} path: dist @@ -301,7 +301,7 @@ jobs: env: PLATFORM_TARGET: ${{ matrix.platform.target }} - name: "Upload binary" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: artifacts-${{ matrix.platform.target }} path: | @@ -324,7 +324,7 @@ jobs: ${MODULE_NAME}-build --help python -m ${MODULE_NAME}_build --help - name: "Upload wheels uv-build" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: wheels_uv_build-${{ matrix.platform.target }} path: crates/uv-build/dist @@ -395,7 +395,7 @@ jobs: python -m ${MODULE_NAME} --help uvx --help - name: "Upload wheels" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: wheels_uv-${{ matrix.target }} path: dist @@ -413,7 +413,7 @@ jobs: env: TARGET: ${{ matrix.target }} - name: "Upload binary" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: artifacts-${{ matrix.target }} path: | @@ -440,7 +440,7 @@ jobs: ${MODULE_NAME}-build --help python -m ${MODULE_NAME}_build --help - name: "Upload wheels uv-build" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: wheels_uv_build-${{ matrix.target }} path: crates/uv-build/dist @@ -512,7 +512,7 @@ jobs: PACKAGE_NAME: ${{ env.PACKAGE_NAME }} MODULE_NAME: ${{ env.MODULE_NAME }} - name: "Upload wheels" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: wheels_uv-${{ matrix.platform.target }} path: dist @@ -530,7 +530,7 @@ jobs: env: TARGET: ${{ matrix.platform.target }} - name: "Upload binary" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: artifacts-${{ matrix.platform.target }} path: | @@ -569,7 +569,7 @@ jobs: PACKAGE_NAME: ${{ env.PACKAGE_NAME }} MODULE_NAME: ${{ env.MODULE_NAME }} - name: "Upload wheels uv-build" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: wheels_uv_build-${{ matrix.platform.target }} path: crates/uv-build/dist @@ -634,7 +634,7 @@ jobs: PACKAGE_NAME: ${{ env.PACKAGE_NAME }} MODULE_NAME: ${{ env.MODULE_NAME }} - name: "Upload wheels" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: wheels_uv-${{ matrix.platform.target }} path: dist @@ -652,7 +652,7 @@ jobs: env: TARGET: ${{ matrix.platform.target }} - name: "Upload binary" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: artifacts-${{ matrix.platform.target }} path: | @@ -691,7 +691,7 @@ jobs: PACKAGE_NAME: ${{ env.PACKAGE_NAME }} MODULE_NAME: ${{ env.MODULE_NAME }} - name: "Upload wheels uv-build" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: wheels_uv_build-${{ matrix.platform.target }} path: crates/uv-build/dist @@ -755,7 +755,7 @@ jobs: # # python -m ${MODULE_NAME} --helppython -m ${MODULE_NAME} --help # uvx --help - name: "Upload wheels" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: wheels_uv-${{ matrix.platform.target }} path: dist @@ -773,7 +773,7 @@ jobs: env: TARGET: ${{ matrix.platform.target }} - name: "Upload binary" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: artifacts-${{ matrix.platform.target }} path: | @@ -801,7 +801,7 @@ jobs: CARGO: ${{ github.workspace }}/scripts/cargo.sh # TODO(charlie): Re-enable testing for PPC wheels. - name: "Upload wheels uv-build" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: wheels_uv_build-${{ matrix.platform.target }} path: crates/uv-build/dist @@ -862,7 +862,7 @@ jobs: PACKAGE_NAME: ${{ env.PACKAGE_NAME }} MODULE_NAME: ${{ env.MODULE_NAME }} - name: "Upload wheels" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: wheels_uv-${{ matrix.platform.target }} path: dist @@ -880,7 +880,7 @@ jobs: env: TARGET: ${{ matrix.platform.target }} - name: "Upload binary" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: artifacts-${{ matrix.platform.target }} path: | @@ -920,7 +920,7 @@ jobs: PACKAGE_NAME: ${{ env.PACKAGE_NAME }} MODULE_NAME: ${{ env.MODULE_NAME }} - name: "Upload wheels uv-build" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: wheels_uv_build-${{ matrix.platform.target }} path: crates/uv-build/dist @@ -972,7 +972,7 @@ jobs: .venv/bin/uvx --help; " - name: "Upload wheels" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: wheels_uv-${{ matrix.target }} path: dist @@ -990,7 +990,7 @@ jobs: env: TARGET: ${{ matrix.target }} - name: "Upload binary" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: artifacts-${{ matrix.target }} path: | @@ -1023,7 +1023,7 @@ jobs: # .venv/bin/python -m ${MODULE_NAME}_build --help; " - name: "Upload wheels uv-build" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: wheels_uv_build-${{ matrix.target }} path: crates/uv-build/dist @@ -1110,7 +1110,7 @@ jobs: PACKAGE_NAME: ${{ env.PACKAGE_NAME }} MODULE_NAME: ${{ env.MODULE_NAME }} - name: "Upload wheels" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: wheels_uv-${{ matrix.platform.target }} path: dist @@ -1129,7 +1129,7 @@ jobs: TARGET: ${{ matrix.platform.target }} PROFILE: ${{ matrix.platform.arch == 'ppc64le' && 'release-no-lto' || 'release' }} - name: "Upload binary" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: artifacts-${{ matrix.platform.target }} path: | @@ -1187,7 +1187,7 @@ jobs: PACKAGE_NAME: ${{ env.PACKAGE_NAME }} MODULE_NAME: ${{ env.MODULE_NAME }} - name: "Upload wheels" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: wheels_uv_build-${{ matrix.platform.target }} path: crates/uv-build/dist diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c2b308f88d584..9be98a06a83ca 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -110,7 +110,7 @@ jobs: - name: "Upload pending snapshots" if: ${{ failure() }} - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: pending-snapshots-linux path: pending-snapshots/ @@ -177,7 +177,7 @@ jobs: - name: "Upload pending snapshots" if: ${{ failure() }} - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: pending-snapshots-macos path: pending-snapshots/ @@ -257,7 +257,7 @@ jobs: - name: "Upload pending snapshots" if: ${{ failure() }} - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: pending-snapshots-windows-${{ matrix.partition }} path: pending-snapshots/ From 6ab38f59df8c7418981ffb795347742f0f7be50f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 14:32:38 -0400 Subject: [PATCH 29/70] Update CodSpeedHQ/action action to v4.13.1 (#19055) --- .github/workflows/bench.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 8494dfb62434c..a51729b2a7fd8 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -88,7 +88,7 @@ jobs: run: ./target/debug/uv venv --cache-dir .cache - name: "Run benchmarks" - uses: CodSpeedHQ/action@d872884a306dd4853acf0f584f4b706cf0cc72a2 # v4.13.0 + uses: CodSpeedHQ/action@db35df748deb45fdef0960669f57d627c1956c30 # v4.13.1 with: run: cargo codspeed run mode: walltime @@ -129,7 +129,7 @@ jobs: run: cargo codspeed build --profile profiling -p uv-bench - name: "Run benchmarks" - uses: CodSpeedHQ/action@d872884a306dd4853acf0f584f4b706cf0cc72a2 # v4.13.0 + uses: CodSpeedHQ/action@db35df748deb45fdef0960669f57d627c1956c30 # v4.13.1 with: run: cargo codspeed run mode: simulation From ff5fbce132db22f6f6da28043446056d767859cc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 14:35:31 -0400 Subject: [PATCH 30/70] Update peter-evans/create-pull-request action to v8.1.1 (#19058) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [peter-evans/create-pull-request](https://redirect.github.com/peter-evans/create-pull-request) | action | patch | `v8.1.0` → `v8.1.1` | --- ### Release Notes
peter-evans/create-pull-request (peter-evans/create-pull-request) ### [`v8.1.1`](https://redirect.github.com/peter-evans/create-pull-request/releases/tag/v8.1.1): Create Pull Request v8.1.1 [Compare Source](https://redirect.github.com/peter-evans/create-pull-request/compare/v8.1.0...v8.1.1) #### What's Changed - build(deps-dev): bump the npm group with 2 updates by [@​dependabot](https://redirect.github.com/dependabot)\[bot] in [#​4305](https://redirect.github.com/peter-evans/create-pull-request/pull/4305) - build(deps): bump minimatch by [@​dependabot](https://redirect.github.com/dependabot)\[bot] in [#​4311](https://redirect.github.com/peter-evans/create-pull-request/pull/4311) - build(deps): bump the github-actions group with 2 updates by [@​dependabot](https://redirect.github.com/dependabot)\[bot] in [#​4316](https://redirect.github.com/peter-evans/create-pull-request/pull/4316) - build(deps): bump [@​tootallnate/once](https://redirect.github.com/tootallnate/once) and jest-environment-jsdom by [@​dependabot](https://redirect.github.com/dependabot)\[bot] in [#​4323](https://redirect.github.com/peter-evans/create-pull-request/pull/4323) - build(deps-dev): bump undici from 6.23.0 to 6.24.0 by [@​dependabot](https://redirect.github.com/dependabot)\[bot] in [#​4328](https://redirect.github.com/peter-evans/create-pull-request/pull/4328) - build(deps-dev): bump flatted from 3.3.1 to 3.4.2 by [@​dependabot](https://redirect.github.com/dependabot)\[bot] in [#​4334](https://redirect.github.com/peter-evans/create-pull-request/pull/4334) - build(deps): bump picomatch by [@​dependabot](https://redirect.github.com/dependabot)\[bot] in [#​4339](https://redirect.github.com/peter-evans/create-pull-request/pull/4339) - build(deps-dev): bump handlebars from 4.7.8 to 4.7.9 by [@​dependabot](https://redirect.github.com/dependabot)\[bot] in [#​4344](https://redirect.github.com/peter-evans/create-pull-request/pull/4344) - build(deps-dev): bump the npm group with 3 updates by [@​dependabot](https://redirect.github.com/dependabot)\[bot] in [#​4349](https://redirect.github.com/peter-evans/create-pull-request/pull/4349) - fix: retry post-creation API calls on 422 eventual consistency errors by [@​peter-evans](https://redirect.github.com/peter-evans) in [#​4356](https://redirect.github.com/peter-evans/create-pull-request/pull/4356) **Full Changelog**:
--- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/astral-sh/uv). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/sync-python-releases.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync-python-releases.yml b/.github/workflows/sync-python-releases.yml index 96c050eaa44c7..d8ae66e719b5e 100644 --- a/.github/workflows/sync-python-releases.yml +++ b/.github/workflows/sync-python-releases.yml @@ -44,7 +44,7 @@ jobs: run: uv run --no-project scripts/sync-python-version-constants.py - name: "Create Pull Request" - uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0 + uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 with: commit-message: "Sync latest Python releases" add-paths: | From 224d722ce589957acb19b66da30b1141df4bc2b9 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Mon, 20 Apr 2026 13:50:13 -0500 Subject: [PATCH 31/70] Add `UV_PYTHON_NO_REGISTRY` (#19035) Required for https://github.com/astral-sh/uv/pull/19034 Arguably, we could keep our existing behavior and have `UV_PYTHON_SEARCH_PATH` implicitly disable the registry lookup, but I think that's too confusing. This disables registry lookups everywhere and modifications in `uv python install`. In the latter case, `UV_PYTHON_INSTALL_REGISTRY` takes precedence. --- crates/uv-python/src/discovery.rs | 84 +++++++++++++++------------- crates/uv-python/src/lib.rs | 5 ++ crates/uv-settings/src/lib.rs | 2 + crates/uv-static/src/env_vars.rs | 8 +++ crates/uv-test/src/lib.rs | 5 +- crates/uv/src/settings.rs | 20 ++++++- crates/uv/tests/it/python_install.rs | 62 ++++++++++++++++++-- 7 files changed, 137 insertions(+), 49 deletions(-) diff --git a/crates/uv-python/src/discovery.rs b/crates/uv-python/src/discovery.rs index 3d8179ce925f6..f52c00594b644 100644 --- a/crates/uv-python/src/discovery.rs +++ b/crates/uv-python/src/discovery.rs @@ -274,6 +274,9 @@ pub enum Error { #[error("Failed to query installed Python versions from the Windows registry")] RegistryError(#[from] windows::core::Error), + #[error(transparent)] + InvalidEnvironmentVariable(#[from] uv_static::InvalidEnvironmentVariable), + /// An invalid version request was given #[error("Invalid version request: {0}")] InvalidVersionRequest(String), @@ -441,49 +444,50 @@ fn python_executables_from_installed<'a>( }) .flatten(); - let from_windows_registry = iter::once_with(move || { - #[cfg(windows)] - { - // Skip interpreter probing if we already know the version doesn't match. - let version_filter = move |entry: &WindowsPython| { - if let Some(found) = &entry.version { - // Some distributions emit the patch version (example: `SysVersion: 3.9`) - if found.string.chars().filter(|c| *c == '.').count() == 1 { - version.matches_major_minor(found.major(), found.minor()) + #[cfg(windows)] + let from_windows_registry: Box< + dyn Iterator> + 'a, + > = match uv_static::parse_boolish_environment_variable(EnvVars::UV_PYTHON_NO_REGISTRY) { + Ok(Some(true)) => Box::new(iter::empty()), + Ok(Some(false) | None) => Box::new( + iter::once_with(move || { + // Skip interpreter probing if we already know the version doesn't match. + let version_filter = move |entry: &WindowsPython| { + if let Some(found) = &entry.version { + // Some distributions emit the patch version (example: `SysVersion: 3.9`) + if found.string.chars().filter(|c| *c == '.').count() == 1 { + version.matches_major_minor(found.major(), found.minor()) + } else { + version.matches_version(found) + } } else { - version.matches_version(found) + true } - } else { - true - } - }; + }; - env::var_os(EnvVars::UV_TEST_PYTHON_PATH) - .is_none() - .then(|| { - registry_pythons() - .map(|entries| { - entries - .into_iter() - .filter(version_filter) - .map(|entry| (PythonSource::Registry, entry.path)) - .chain( - find_microsoft_store_pythons() - .filter(version_filter) - .map(|entry| (PythonSource::MicrosoftStore, entry.path)), - ) - }) - .map_err(Error::from) - }) - .into_iter() - .flatten_ok() - } - #[cfg(not(windows))] - { - Vec::new() - } - }) - .flatten(); + registry_pythons() + .map(|entries| { + entries + .into_iter() + .filter(version_filter) + .map(|entry| (PythonSource::Registry, entry.path)) + .chain( + find_microsoft_store_pythons() + .filter(version_filter) + .map(|entry| (PythonSource::MicrosoftStore, entry.path)), + ) + }) + .map_err(Error::from) + }) + .flatten_ok(), + ), + Err(err) => Box::new(iter::once(Err(Error::from(err)))), + }; + + #[cfg(not(windows))] + let from_windows_registry: Box< + dyn Iterator> + 'a, + > = Box::new(iter::empty()); match preference { PythonPreference::OnlyManaged => { diff --git a/crates/uv-python/src/lib.rs b/crates/uv-python/src/lib.rs index e7d253380e4ee..6c0ba9ab2b008 100644 --- a/crates/uv-python/src/lib.rs +++ b/crates/uv-python/src/lib.rs @@ -220,6 +220,11 @@ mod tests { let mut run_vars = vec![ // Ensure `PATH` is used (EnvVars::UV_TEST_PYTHON_PATH, None), + // Keep discovery hermetic by disabling registry-based sources unless a test opts in. + ( + EnvVars::UV_PYTHON_NO_REGISTRY, + Some(std::ffi::OsStr::new("1")), + ), // Ignore active virtual environments (i.e. that the dev is using) (EnvVars::VIRTUAL_ENV, None), (EnvVars::PATH, path.as_deref()), diff --git a/crates/uv-settings/src/lib.rs b/crates/uv-settings/src/lib.rs index feba1a0d8788d..ec5d8587675a5 100644 --- a/crates/uv-settings/src/lib.rs +++ b/crates/uv-settings/src/lib.rs @@ -717,6 +717,7 @@ pub struct EnvironmentOptions { pub hide_build_output: Option, pub python_install_bin: Option, pub python_install_registry: Option, + pub python_no_registry: EnvFlag, pub install_mirrors: PythonInstallMirrors, pub log_context: Option, pub lfs: Option, @@ -780,6 +781,7 @@ impl EnvironmentOptions { python_install_registry: parse_boolish_environment_variable( EnvVars::UV_PYTHON_INSTALL_REGISTRY, )?, + python_no_registry: EnvFlag::new(EnvVars::UV_PYTHON_NO_REGISTRY)?, concurrency: Concurrency { downloads: parse_integer_environment_variable( EnvVars::UV_CONCURRENT_DOWNLOADS, diff --git a/crates/uv-static/src/env_vars.rs b/crates/uv-static/src/env_vars.rs index 35c327dbb9ba8..243097e28f7d2 100644 --- a/crates/uv-static/src/env_vars.rs +++ b/crates/uv-static/src/env_vars.rs @@ -420,6 +420,14 @@ impl EnvVars { #[attr_added_in("0.8.0")] pub const UV_PYTHON_INSTALL_REGISTRY: &'static str = "UV_PYTHON_INSTALL_REGISTRY"; + /// Disable use of the Windows registry for Python discovery and registration. + /// + /// When set, uv will not discover Python interpreters from the Windows registry or Microsoft + /// Store locations, and managed Python installations will not be registered in the Windows + /// registry. + #[attr_added_in("next release")] + pub const UV_PYTHON_NO_REGISTRY: &'static str = "UV_PYTHON_NO_REGISTRY"; + /// Managed Python installations information is hardcoded in the `uv` binary. /// /// This variable can be set to a local path or URL pointing to diff --git a/crates/uv-test/src/lib.rs b/crates/uv-test/src/lib.rs index 0549407def3f8..ba21a0c0230fc 100755 --- a/crates/uv-test/src/lib.rs +++ b/crates/uv-test/src/lib.rs @@ -1236,8 +1236,9 @@ impl TestContext { .env(EnvVars::UV_EXCLUDE_NEWER, TEST_TIMESTAMP) .env(EnvVars::UV_TEST_CURRENT_TIMESTAMP, TEST_TIMESTAMP) .env(EnvVars::UV_TEST_AVAILABLE_VERSION_CUTOFF, TEST_TIMESTAMP) - // When installations are allowed, we don't want to write to global state, like the - // Windows registry + // Keep Python discovery hermetic and avoid mutating global state, like the Windows + // registry, unless a test opts in explicitly. + .env(EnvVars::UV_PYTHON_NO_REGISTRY, "1") .env(EnvVars::UV_PYTHON_INSTALL_REGISTRY, "0") // Since downloads, fetches and builds run in parallel, their message output order is // non-deterministic, so can't capture them in test output. diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index bc8ac253c927a..53afa8d5a83b9 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -1372,8 +1372,16 @@ impl PythonInstallSettings { PythonUpgrade::Disabled }, bin: flag(bin, no_bin, "bin").or(environment.python_install_bin), - registry: flag(registry, no_registry, "registry") - .or(environment.python_install_registry), + registry: match flag(registry, no_registry, "registry") { + Some(registry) => Some(registry), + None => environment.python_install_registry.or( + if environment.python_no_registry.value == Some(true) { + Some(false) + } else { + None + }, + ), + }, python_install_mirror, pypy_install_mirror, python_downloads_json_url, @@ -1430,7 +1438,13 @@ impl PythonUpgradeSettings { let force = false; let default = false; let bin = None; - let registry = None; + let registry = environment.python_install_registry.or( + if environment.python_no_registry.value == Some(true) { + Some(false) + } else { + None + }, + ); let PythonUpgradeArgs { install_dir, diff --git a/crates/uv/tests/it/python_install.rs b/crates/uv/tests/it/python_install.rs index dbbfac16e6549..ef0ce260f9067 100644 --- a/crates/uv/tests/it/python_install.rs +++ b/crates/uv/tests/it/python_install.rs @@ -1233,8 +1233,8 @@ fn python_install_freethreaded() { /// Regression test for . /// /// IMPORTANT: this test writes to the shared `HKCU` registry. The trailing uninstall is -/// best-effort cleanup; panics will leak entries. This is fine for now since this is the only -/// test exercising the registry pathway, but adding more will probably require isolation. +/// best-effort cleanup; panics will leak entries. These registry tests still share global state, +/// so adding more will probably require isolation. #[cfg(all(windows, feature = "test-windows-registry"))] #[test] fn python_install_freethreaded_and_gil_list() { @@ -1254,6 +1254,7 @@ fn python_install_freethreaded_and_gil_list() { context .python_install() .arg("3.13") + .env_remove(EnvVars::UV_PYTHON_NO_REGISTRY) .env(EnvVars::UV_PYTHON_INSTALL_REGISTRY, "1") .assert() .success(); @@ -1261,13 +1262,14 @@ fn python_install_freethreaded_and_gil_list() { .python_install() .arg("--preview") .arg("3.13t") + .env_remove(EnvVars::UV_PYTHON_NO_REGISTRY) .env(EnvVars::UV_PYTHON_INSTALL_REGISTRY, "1") .assert() .success(); // List installed versions with registry discovery enabled. - // We remove UV_TEST_PYTHON_PATH to enable registry discovery (it's skipped when set), - // and use `--managed-python --only-installed` to exclude unrelated system Pythons. + // We remove `UV_PYTHON_NO_REGISTRY` to opt back into registry discovery, and remove + // `UV_TEST_PYTHON_PATH` so the test can discover the installed bin trampolines. // // Both the GIL and freethreaded variants should show entries from: // - The registry (patch-versioned managed directory path) @@ -1277,6 +1279,7 @@ fn python_install_freethreaded_and_gil_list() { .arg("3.13") .arg("--only-installed") .arg("--managed-python") + .env_remove(EnvVars::UV_PYTHON_NO_REGISTRY) .env_remove(EnvVars::UV_TEST_PYTHON_PATH) .env(EnvVars::UV_PYTHON_INSTALL_REGISTRY, "1"), @" success: true @@ -1293,6 +1296,7 @@ fn python_install_freethreaded_and_gil_list() { .arg("3.13t") .arg("--only-installed") .arg("--managed-python") + .env_remove(EnvVars::UV_PYTHON_NO_REGISTRY) .env_remove(EnvVars::UV_TEST_PYTHON_PATH) .env(EnvVars::UV_PYTHON_INSTALL_REGISTRY, "1"), @" success: true @@ -1314,6 +1318,56 @@ fn python_install_freethreaded_and_gil_list() { .success(); } +#[cfg(all(windows, feature = "test-windows-registry"))] +#[test] +fn python_install_registry_takes_precedence_over_no_registry() { + use assert_cmd::assert::OutputAssertExt; + + let context = uv_test::test_context_with_versions!(&[]) + .with_filtered_python_keys() + .with_filtered_latest_python_versions() + .with_managed_python_dirs() + .with_python_download_cache() + .with_filtered_python_install_bin() + .with_filtered_python_names() + .with_filtered_exe_suffix() + .with_collapsed_whitespace(); + + context + .python_install() + .arg("3.13") + .env(EnvVars::UV_PYTHON_INSTALL_REGISTRY, "1") + .env(EnvVars::UV_PYTHON_NO_REGISTRY, "1") + .assert() + .success(); + + // `UV_PYTHON_INSTALL_REGISTRY` should take precedence over `UV_PYTHON_NO_REGISTRY`. + // When we re-enable registry discovery for this command and clear the search path, we should + // see both the registry entry and the managed installation entry. + uv_snapshot!(context.filters(), context.python_list() + .arg("3.13") + .arg("--only-installed") + .arg("--managed-python") + .env_remove(EnvVars::UV_PYTHON_NO_REGISTRY) + .env(EnvVars::UV_TEST_PYTHON_PATH, "") + .env(EnvVars::UV_PYTHON_INSTALL_REGISTRY, "1"), @" + success: true + exit_code: 0 + ----- stdout ----- + cpython-3.13.[LATEST]-[PLATFORM] managed/cpython-3.13.[LATEST]-[PLATFORM]/[INSTALL-BIN]/[PYTHON] + cpython-3.13.[LATEST]-[PLATFORM] managed/cpython-3.13-[PLATFORM]/[INSTALL-BIN]/[PYTHON] + + ----- stderr ----- + "); + + context + .python_uninstall() + .arg("--all") + .env(EnvVars::UV_PYTHON_INSTALL_REGISTRY, "1") + .assert() + .success(); +} + #[test] fn python_upgrade_not_allowed() { let context = uv_test::test_context_with_versions!(&[]) From e72fbd97e10aef781c1900bb00147859ad9d7a40 Mon Sep 17 00:00:00 2001 From: Tomasz Kramkowski Date: Mon, 20 Apr 2026 19:58:36 +0100 Subject: [PATCH 32/70] Remove duplicate hashbrown lockfile entry (#19083) ## Summary Duplicate introduced by #19070 ## Test Plan N/A --- Cargo.lock | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f6e0597680b90..6641d79c5473d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1955,12 +1955,6 @@ dependencies = [ "foldhash 0.2.0", ] -[[package]] -name = "hashbrown" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" - [[package]] name = "heck" version = "0.5.0" From f3ed0e95211214b309a6a6a98d03ea7875ebf8d2 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Mon, 20 Apr 2026 14:03:37 -0500 Subject: [PATCH 33/70] Use an Ubuntu snapshot for apt installs in CI for reproducibility (#19032) Co-authored-by: Claude --- .github/workflows/test-integration.yml | 5 +++-- .github/workflows/test.yml | 7 ++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test-integration.yml b/.github/workflows/test-integration.yml index e0ea4d354effa..d821f01313d2d 100644 --- a/.github/workflows/test-integration.yml +++ b/.github/workflows/test-integration.yml @@ -14,6 +14,7 @@ env: CARGO_TERM_COLOR: always PYTHON_VERSION: "3.12" RUSTUP_MAX_RETRIES: 10 + UBUNTU_SNAPSHOT: "20260301T000000Z" jobs: integration-test-nushell: @@ -197,8 +198,8 @@ jobs: - name: "Install armhf runtime" run: | sudo dpkg --add-architecture armhf - sudo apt-get update - sudo apt-get install -y libc6:armhf libgcc-s1:armhf + echo 'Acquire::Retries "3";' | sudo tee /etc/apt/apt.conf.d/80-retries > /dev/null + sudo apt install -y --update --snapshot ${UBUNTU_SNAPSHOT} libc6:armhf libgcc-s1:armhf - name: "Prepare binary" run: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9be98a06a83ca..caf1c50d89f1f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,6 +18,7 @@ env: CARGO_TERM_COLOR: always PYTHON_VERSION: "3.12" RUSTUP_MAX_RETRIES: 10 + UBUNTU_SNAPSHOT: "20260301T000000Z" jobs: # We use the large GitHub actions runners @@ -52,8 +53,8 @@ jobs: - name: "Install secret service" run: | - sudo apt update -y - sudo apt install -y gnome-keyring + echo 'Acquire::Retries "3";' | sudo tee /etc/apt/apt.conf.d/80-retries > /dev/null + sudo apt install -y --update --snapshot ${UBUNTU_SNAPSHOT} gnome-keyring - name: "Start gnome-keyring" # run gnome-keyring with 'foobar' as password for the login keyring @@ -63,7 +64,7 @@ jobs: - name: "Create btrfs filesystem" run: | - sudo apt-get install -y btrfs-progs + sudo apt install -y --update --snapshot ${UBUNTU_SNAPSHOT} btrfs-progs truncate -s 1G /tmp/btrfs.img mkfs.btrfs /tmp/btrfs.img sudo mkdir /btrfs From 7cd3c2348b0eb59d6ed8ed0587a948c5ddcd2bcc Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Mon, 20 Apr 2026 14:13:40 -0500 Subject: [PATCH 34/70] Expose `UV_PYTHON_SEARCH_PATH` for Python discovery `PATH` overrides (#19034) Closes https://github.com/astral-sh/uv/issues/19027 Closes https://github.com/astral-sh/uv/issues/9506 (while the request there is to extend `PATH`, they can set `UV_PYTHON_SEARCH_PATH=...;$PATH`) --------- Co-authored-by: Claude --- crates/uv-python/src/discovery.rs | 4 +- crates/uv-python/src/lib.rs | 7 +- crates/uv-static/src/env_vars.rs | 10 +-- crates/uv-test/src/lib.rs | 8 +-- crates/uv/tests/it/pip_compile.rs | 2 +- crates/uv/tests/it/pip_compile_scenarios.rs | 2 +- crates/uv/tests/it/pip_install.rs | 4 +- crates/uv/tests/it/python_find.rs | 75 +++++++++++++++++--- crates/uv/tests/it/python_install.rs | 10 +-- crates/uv/tests/it/python_list.rs | 12 ++-- crates/uv/tests/it/venv.rs | 6 +- scripts/scenarios/templates/compile.mustache | 2 +- 12 files changed, 100 insertions(+), 42 deletions(-) diff --git a/crates/uv-python/src/discovery.rs b/crates/uv-python/src/discovery.rs index f52c00594b644..cbbeec70bf71a 100644 --- a/crates/uv-python/src/discovery.rs +++ b/crates/uv-python/src/discovery.rs @@ -588,8 +588,8 @@ fn python_executables_from_search_path<'a>( version: &'a VersionRequest, implementation: Option<&'a ImplementationName>, ) -> impl Iterator + 'a { - // `UV_TEST_PYTHON_PATH` can be used to override `PATH` to limit Python executable availability in the test suite - let search_path = env::var_os(EnvVars::UV_TEST_PYTHON_PATH) + // `UV_PYTHON_SEARCH_PATH` can be used to override `PATH` for Python executable discovery + let search_path = env::var_os(EnvVars::UV_PYTHON_SEARCH_PATH) .unwrap_or(env::var_os(EnvVars::PATH).unwrap_or_default()); let possible_names: Vec<_> = version diff --git a/crates/uv-python/src/lib.rs b/crates/uv-python/src/lib.rs index 6c0ba9ab2b008..f25b9d7283cb3 100644 --- a/crates/uv-python/src/lib.rs +++ b/crates/uv-python/src/lib.rs @@ -219,12 +219,9 @@ mod tests { let mut run_vars = vec![ // Ensure `PATH` is used - (EnvVars::UV_TEST_PYTHON_PATH, None), + (EnvVars::UV_PYTHON_SEARCH_PATH, None), // Keep discovery hermetic by disabling registry-based sources unless a test opts in. - ( - EnvVars::UV_PYTHON_NO_REGISTRY, - Some(std::ffi::OsStr::new("1")), - ), + (EnvVars::UV_PYTHON_NO_REGISTRY, Some(OsStr::new("1"))), // Ignore active virtual environments (i.e. that the dev is using) (EnvVars::VIRTUAL_ENV, None), (EnvVars::PATH, path.as_deref()), diff --git a/crates/uv-static/src/env_vars.rs b/crates/uv-static/src/env_vars.rs index 243097e28f7d2..7187f570a4054 100644 --- a/crates/uv-static/src/env_vars.rs +++ b/crates/uv-static/src/env_vars.rs @@ -504,10 +504,12 @@ impl EnvVars { #[attr_added_in("0.5.21")] pub const UV_VENV_SEED: &'static str = "UV_VENV_SEED"; - /// Used to override `PATH` to limit Python executable availability in the test suite. - #[attr_hidden] - #[attr_added_in("0.0.5")] - pub const UV_TEST_PYTHON_PATH: &'static str = "UV_TEST_PYTHON_PATH"; + /// Used to override `PATH` for Python executable discovery. + /// + /// When set, uv will search for Python interpreters in the directories specified by this + /// variable instead of `PATH`. + #[attr_added_in("next release")] + pub const UV_PYTHON_SEARCH_PATH: &'static str = "UV_PYTHON_SEARCH_PATH"; /// Include resolver and installer output related to environment modifications. #[attr_hidden] diff --git a/crates/uv-test/src/lib.rs b/crates/uv-test/src/lib.rs index ba21a0c0230fc..350d3d0499208 100755 --- a/crates/uv-test/src/lib.rs +++ b/crates/uv-test/src/lib.rs @@ -1183,7 +1183,7 @@ impl TestContext { /// but snapshotted to a string. /// * Use a fake `HOME` to avoid accidentally changing the developer's machine. /// * Hide other Pythons with `UV_PYTHON_INSTALL_DIR` and installed interpreters with - /// `UV_TEST_PYTHON_PATH` and an active venv (if applicable) by removing `VIRTUAL_ENV`. + /// `UV_PYTHON_SEARCH_PATH` and an active venv (if applicable) by removing `VIRTUAL_ENV`. /// * Increase the stack size to avoid stack overflows on windows due to large async functions. pub fn add_shared_options(&self, command: &mut Command, activate_venv: bool) { self.add_shared_args(command); @@ -1232,7 +1232,7 @@ impl TestContext { .env(EnvVars::UV_PYTHON_INSTALL_DIR, "") // Installations are not allowed by default; see `Self::with_managed_python_dirs` .env(EnvVars::UV_PYTHON_DOWNLOADS, "never") - .env(EnvVars::UV_TEST_PYTHON_PATH, self.python_path()) + .env(EnvVars::UV_PYTHON_SEARCH_PATH, self.python_path()) .env(EnvVars::UV_EXCLUDE_NEWER, TEST_TIMESTAMP) .env(EnvVars::UV_TEST_CURRENT_TIMESTAMP, TEST_TIMESTAMP) .env(EnvVars::UV_TEST_AVAILABLE_VERSION_CUTOFF, TEST_TIMESTAMP) @@ -2068,7 +2068,7 @@ pub fn create_venv_from_executable>( /// Create a `PATH` with the requested Python versions available in order. /// -/// Generally this should be used with `UV_TEST_PYTHON_PATH`. +/// Generally this should be used with `UV_PYTHON_SEARCH_PATH`. pub fn python_path_with_versions( temp_dir: &ChildPath, python_versions: &[&str], @@ -2083,7 +2083,7 @@ pub fn python_path_with_versions( /// Returns a list of Python executables for the given versions. /// -/// Generally this should be used with `UV_TEST_PYTHON_PATH`. +/// Generally this should be used with `UV_PYTHON_SEARCH_PATH`. pub fn python_installations_for_versions( temp_dir: &ChildPath, python_versions: &[&str], diff --git a/crates/uv/tests/it/pip_compile.rs b/crates/uv/tests/it/pip_compile.rs index 2f71ce1c0ad75..464fa3dbb4d48 100644 --- a/crates/uv/tests/it/pip_compile.rs +++ b/crates/uv/tests/it/pip_compile.rs @@ -1908,7 +1908,7 @@ fn compile_fallback_interpreter_broken_in_path() -> Result<()> { .arg("--python-version") .arg("3.12") // In tests, we ignore `PATH` during Python discovery so we need to add the context `bin` - .env(EnvVars::UV_TEST_PYTHON_PATH, context.bin_dir.as_os_str()), @" + .env(EnvVars::UV_PYTHON_SEARCH_PATH, context.bin_dir.as_os_str()), @" success: true exit_code: 0 ----- stdout ----- diff --git a/crates/uv/tests/it/pip_compile_scenarios.rs b/crates/uv/tests/it/pip_compile_scenarios.rs index 17ad35fbe98a8..8215975a3d593 100644 --- a/crates/uv/tests/it/pip_compile_scenarios.rs +++ b/crates/uv/tests/it/pip_compile_scenarios.rs @@ -35,7 +35,7 @@ fn command(context: &TestContext, python_versions: &[&str]) -> Command { .arg(build_vendor_links_url()); context.add_shared_options(&mut command, true); command.env_remove(EnvVars::UV_EXCLUDE_NEWER); - command.env(EnvVars::UV_TEST_PYTHON_PATH, python_path); + command.env(EnvVars::UV_PYTHON_SEARCH_PATH, python_path); command } diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index f339bfe84b713..aec16cc03f397 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -9043,7 +9043,7 @@ fn install_incompatible_python_version_interpreter_broken_in_path() -> Result<() .arg("-p").arg("3.12") .arg("anyio") // In tests, we ignore `PATH` during Python discovery so we need to add the context `bin` - .env(EnvVars::UV_TEST_PYTHON_PATH, path.as_os_str()), @" + .env(EnvVars::UV_PYTHON_SEARCH_PATH, path.as_os_str()), @" success: false exit_code: 2 ----- stdout ----- @@ -9070,7 +9070,7 @@ fn install_incompatible_python_version_interpreter_broken_in_path() -> Result<() .arg("-p").arg("3.12") .arg("anyio") // In tests, we ignore `PATH` during Python discovery so we need to add the context `bin` - .env(EnvVars::UV_TEST_PYTHON_PATH, path.as_os_str()), @" + .env(EnvVars::UV_PYTHON_SEARCH_PATH, path.as_os_str()), @" success: false exit_code: 2 ----- stdout ----- diff --git a/crates/uv/tests/it/python_find.rs b/crates/uv/tests/it/python_find.rs index 65d5ab5ae5a87..71cd2d5a78c86 100644 --- a/crates/uv/tests/it/python_find.rs +++ b/crates/uv/tests/it/python_find.rs @@ -14,7 +14,7 @@ fn python_find() { uv_test::test_context_with_versions!(&["3.11", "3.12"]).with_filtered_python_sources(); // No interpreters on the path - uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_TEST_PYTHON_PATH, ""), @" + uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_PYTHON_SEARCH_PATH, ""), @" success: false exit_code: 2 ----- stdout ----- @@ -724,7 +724,7 @@ fn python_find_venv() { // Or at the front of the PATH #[cfg(not(windows))] - uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_TEST_PYTHON_PATH, child_dir.join(".venv").join("bin").as_os_str()), @" + uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_PYTHON_SEARCH_PATH, child_dir.join(".venv").join("bin").as_os_str()), @" success: true exit_code: 0 ----- stdout ----- @@ -743,7 +743,7 @@ fn python_find_venv() { ]) .unwrap(); - uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_TEST_PYTHON_PATH, path.as_os_str()), @" + uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_PYTHON_SEARCH_PATH, path.as_os_str()), @" success: true exit_code: 0 ----- stdout ----- @@ -762,7 +762,7 @@ fn python_find_venv() { ) .unwrap(); - uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_TEST_PYTHON_PATH, path.as_os_str()), @" + uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_PYTHON_SEARCH_PATH, path.as_os_str()), @" success: true exit_code: 0 ----- stdout ----- @@ -994,7 +994,7 @@ fn python_required_python_major_minor() { .unwrap(); // Find `python3.11`, which is `>=3.11.4`. - uv_snapshot!(context.filters(), context.python_find().arg(">=3.11.4, <3.12").env(EnvVars::UV_TEST_PYTHON_PATH, context.temp_dir.child("child").path()), @" + uv_snapshot!(context.filters(), context.python_find().arg(">=3.11.4, <3.12").env(EnvVars::UV_PYTHON_SEARCH_PATH, context.temp_dir.child("child").path()), @" success: true exit_code: 0 ----- stdout ----- @@ -1004,7 +1004,7 @@ fn python_required_python_major_minor() { "); // Find `python3.11`, which is `>3.11.4`. - uv_snapshot!(context.filters(), context.python_find().arg(">3.11.4, <3.12").env(EnvVars::UV_TEST_PYTHON_PATH, context.temp_dir.child("child").path()), @" + uv_snapshot!(context.filters(), context.python_find().arg(">3.11.4, <3.12").env(EnvVars::UV_PYTHON_SEARCH_PATH, context.temp_dir.child("child").path()), @" success: true exit_code: 0 ----- stdout ----- @@ -1014,7 +1014,7 @@ fn python_required_python_major_minor() { "); // Fail to find any matching Python interpreter. - uv_snapshot!(context.filters(), context.python_find().arg(">3.11.255, <3.12").env(EnvVars::UV_TEST_PYTHON_PATH, context.temp_dir.child("child").path()), @" + uv_snapshot!(context.filters(), context.python_find().arg(">3.11.255, <3.12").env(EnvVars::UV_PYTHON_SEARCH_PATH, context.temp_dir.child("child").path()), @" success: false exit_code: 2 ----- stdout ----- @@ -1167,7 +1167,7 @@ fn python_find_show_version() { uv_test::test_context_with_versions!(&["3.11", "3.12"]).with_filtered_python_sources(); // No interpreters found - uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_TEST_PYTHON_PATH, "").arg("--show-version"), @" + uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_PYTHON_SEARCH_PATH, "").arg("--show-version"), @" success: false exit_code: 2 ----- stdout ----- @@ -1536,3 +1536,62 @@ fn python_find_equal() { ----- stderr ----- "###); } + +/// Test that `UV_PYTHON_SEARCH_PATH` overrides `PATH` for Python discovery. +#[test] +fn python_find_search_path() { + let context = + uv_test::test_context_with_versions!(&["3.11", "3.12"]).with_filtered_python_sources(); + + // When `UV_PYTHON_SEARCH_PATH` is empty, no interpreters are found + uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_PYTHON_SEARCH_PATH, ""), @" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: No interpreter found in [PYTHON SOURCES] + "); + + // When `UV_PYTHON_SEARCH_PATH` is set, it is used instead of `PATH` + uv_snapshot!(context.filters(), context.python_find(), @" + success: true + exit_code: 0 + ----- stdout ----- + [PYTHON-3.11] + + ----- stderr ----- + "); + + // We can use `UV_PYTHON_SEARCH_PATH` to control which Python versions are visible + let python_path_3_12_only = std::env::join_paths( + std::env::split_paths(&context.python_path()) + .filter(|p| p.to_string_lossy().contains("3.12")), + ) + .unwrap(); + uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_PYTHON_SEARCH_PATH, python_path_3_12_only.as_os_str()), @" + success: true + exit_code: 0 + ----- stdout ----- + [PYTHON-3.12] + + ----- stderr ----- + "); + + // We can use `UV_PYTHON_SEARCH_PATH` to control the order of Python versions + let reversed_path = std::env::join_paths( + std::env::split_paths(&context.python_path()) + .collect::>() + .into_iter() + .rev(), + ) + .unwrap(); + uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_PYTHON_SEARCH_PATH, reversed_path.as_os_str()), @" + success: true + exit_code: 0 + ----- stdout ----- + [PYTHON-3.12] + + ----- stderr ----- + "); +} diff --git a/crates/uv/tests/it/python_install.rs b/crates/uv/tests/it/python_install.rs index ef0ce260f9067..d9278078f7dce 100644 --- a/crates/uv/tests/it/python_install.rs +++ b/crates/uv/tests/it/python_install.rs @@ -333,7 +333,7 @@ fn python_install_automatic() { uv_snapshot!(context.filters(), context.run() .env_remove(EnvVars::VIRTUAL_ENV) // In tests, we ignore `PATH` during Python discovery so we need to add the context `bin` - .env(EnvVars::UV_TEST_PYTHON_PATH, context.bin_dir.as_os_str()) + .env(EnvVars::UV_PYTHON_SEARCH_PATH, context.bin_dir.as_os_str()) .arg("-p").arg("3.11") .arg("python").arg("-c").arg("import sys; print(sys.version_info[:2])"), @" success: true @@ -1269,7 +1269,7 @@ fn python_install_freethreaded_and_gil_list() { // List installed versions with registry discovery enabled. // We remove `UV_PYTHON_NO_REGISTRY` to opt back into registry discovery, and remove - // `UV_TEST_PYTHON_PATH` so the test can discover the installed bin trampolines. + // `UV_PYTHON_SEARCH_PATH` so the test can discover the installed bin trampolines. // // Both the GIL and freethreaded variants should show entries from: // - The registry (patch-versioned managed directory path) @@ -1280,7 +1280,7 @@ fn python_install_freethreaded_and_gil_list() { .arg("--only-installed") .arg("--managed-python") .env_remove(EnvVars::UV_PYTHON_NO_REGISTRY) - .env_remove(EnvVars::UV_TEST_PYTHON_PATH) + .env_remove(EnvVars::UV_PYTHON_SEARCH_PATH) .env(EnvVars::UV_PYTHON_INSTALL_REGISTRY, "1"), @" success: true exit_code: 0 @@ -1297,7 +1297,7 @@ fn python_install_freethreaded_and_gil_list() { .arg("--only-installed") .arg("--managed-python") .env_remove(EnvVars::UV_PYTHON_NO_REGISTRY) - .env_remove(EnvVars::UV_TEST_PYTHON_PATH) + .env_remove(EnvVars::UV_PYTHON_SEARCH_PATH) .env(EnvVars::UV_PYTHON_INSTALL_REGISTRY, "1"), @" success: true exit_code: 0 @@ -1349,7 +1349,7 @@ fn python_install_registry_takes_precedence_over_no_registry() { .arg("--only-installed") .arg("--managed-python") .env_remove(EnvVars::UV_PYTHON_NO_REGISTRY) - .env(EnvVars::UV_TEST_PYTHON_PATH, "") + .env(EnvVars::UV_PYTHON_SEARCH_PATH, "") .env(EnvVars::UV_PYTHON_INSTALL_REGISTRY, "1"), @" success: true exit_code: 0 diff --git a/crates/uv/tests/it/python_list.rs b/crates/uv/tests/it/python_list.rs index 8cd223ada76d8..9a2e41ae5d3a8 100644 --- a/crates/uv/tests/it/python_list.rs +++ b/crates/uv/tests/it/python_list.rs @@ -15,7 +15,7 @@ fn python_list() { .with_filtered_python_keys() .with_collapsed_whitespace(); - uv_snapshot!(context.filters(), context.python_list().env(EnvVars::UV_TEST_PYTHON_PATH, ""), @" + uv_snapshot!(context.filters(), context.python_list().env(EnvVars::UV_PYTHON_SEARCH_PATH, ""), @" success: true exit_code: 0 ----- stdout ----- @@ -303,7 +303,7 @@ fn python_list_duplicate_path_entries() { ) .unwrap(); - uv_snapshot!(context.filters(), context.python_list().env(EnvVars::UV_TEST_PYTHON_PATH, &path), @" + uv_snapshot!(context.filters(), context.python_list().env(EnvVars::UV_PYTHON_SEARCH_PATH, &path), @" success: true exit_code: 0 ----- stdout ----- @@ -325,7 +325,7 @@ fn python_list_duplicate_path_entries() { )) .unwrap(); - uv_snapshot!(context.filters(), context.python_list().env(EnvVars::UV_TEST_PYTHON_PATH, &path), @" + uv_snapshot!(context.filters(), context.python_list().env(EnvVars::UV_PYTHON_SEARCH_PATH, &path), @" success: true exit_code: 0 ----- stdout ----- @@ -346,7 +346,7 @@ fn python_list_duplicate_path_entries() { ) .unwrap(); - uv_snapshot!(context.filters(), context.python_list().env(EnvVars::UV_TEST_PYTHON_PATH, &path), @" + uv_snapshot!(context.filters(), context.python_list().env(EnvVars::UV_PYTHON_SEARCH_PATH, &path), @" success: true exit_code: 0 ----- stdout ----- @@ -527,7 +527,7 @@ fn python_list_managed_symlinks() { .arg("3.10") .arg("--only-installed") .arg("--no-managed-python") - .env(EnvVars::UV_TEST_PYTHON_PATH, &bin_dir), @" + .env(EnvVars::UV_PYTHON_SEARCH_PATH, &bin_dir), @" success: true exit_code: 0 ----- stdout ----- @@ -540,7 +540,7 @@ fn python_list_managed_symlinks() { .arg("3.10") .arg("--only-installed") .arg("--managed-python") - .env(EnvVars::UV_TEST_PYTHON_PATH, &bin_dir), @" + .env(EnvVars::UV_PYTHON_SEARCH_PATH, &bin_dir), @" success: true exit_code: 0 ----- stdout ----- diff --git a/crates/uv/tests/it/venv.rs b/crates/uv/tests/it/venv.rs index 89460e2500cf8..d3bf5eb7b2227 100644 --- a/crates/uv/tests/it/venv.rs +++ b/crates/uv/tests/it/venv.rs @@ -923,7 +923,7 @@ fn create_venv_unknown_python_minor() { .arg("--python") .arg("3.100") // Unset this variable to force what the user would see - .env_remove(EnvVars::UV_TEST_PYTHON_PATH); + .env_remove(EnvVars::UV_PYTHON_SEARCH_PATH); uv_snapshot!(context.filters(), &mut command, @" success: false @@ -949,7 +949,7 @@ fn create_venv_unknown_python_patch() { .arg("--python") .arg("3.12.100") // Unset this variable to force what the user would see - .env_remove(EnvVars::UV_TEST_PYTHON_PATH); + .env_remove(EnvVars::UV_PYTHON_SEARCH_PATH); uv_snapshot!(context.filters(), &mut command, @" success: false @@ -1211,7 +1211,7 @@ fn windows_shims() -> Result<()> { // Create a virtual environment at `.venv` with the shim uv_snapshot!(context.filters(), context.venv() .arg(context.venv.as_os_str()) - .env(EnvVars::UV_TEST_PYTHON_PATH, format!("{};{}", shim_path.display(), context.python_path().to_string_lossy())), @r###" + .env(EnvVars::UV_PYTHON_SEARCH_PATH, format!("{};{}", shim_path.display(), context.python_path().to_string_lossy())), @r###" success: true exit_code: 0 ----- stdout ----- diff --git a/scripts/scenarios/templates/compile.mustache b/scripts/scenarios/templates/compile.mustache index 0955df3bfbf16..066357a7156ca 100644 --- a/scripts/scenarios/templates/compile.mustache +++ b/scripts/scenarios/templates/compile.mustache @@ -35,7 +35,7 @@ fn command(context: &TestContext, python_versions: &[&str]) -> Command { .arg(build_vendor_links_url()); context.add_shared_options(&mut command, true); command.env_remove(EnvVars::UV_EXCLUDE_NEWER); - command.env(EnvVars::UV_TEST_PYTHON_PATH, python_path); + command.env(EnvVars::UV_PYTHON_SEARCH_PATH, python_path); command } From e5a5fba0da909e15aeb0008941e9465d68b9f7a9 Mon Sep 17 00:00:00 2001 From: Oshadha Gunawardena Date: Tue, 21 Apr 2026 01:20:34 +0530 Subject: [PATCH 35/70] Run `LookaheadResolver` when resolving PEP 517 build requirements (#19076) Build dispatch was constructing its manifest without lookaheads, so the build dependency resolver missed transitive URL dependencies from workspace members referenced via build-system.requires. Run LookaheadResolver first and pass the result into `Manifest::with_lookaheads`, matching the behavior the project resolver already uses. Added a regression integration test that fails on main and passes with this change. Closes #19074! --------- Co-authored-by: Charlie Marsh --- Cargo.lock | 1 + crates/uv-dispatch/Cargo.toml | 1 + crates/uv-dispatch/src/lib.rs | 42 ++++++++- crates/uv-resolver/src/manifest.rs | 6 ++ crates/uv/tests/it/build.rs | 133 +++++++++++++++++++++++++++++ 5 files changed, 179 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6641d79c5473d..8984bd3b3b063 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6395,6 +6395,7 @@ dependencies = [ "uv-preview", "uv-pypi-types", "uv-python", + "uv-requirements", "uv-resolver", "uv-types", "uv-version", diff --git a/crates/uv-dispatch/Cargo.toml b/crates/uv-dispatch/Cargo.toml index 0862a542e34d5..6fb96b2f9d176 100644 --- a/crates/uv-dispatch/Cargo.toml +++ b/crates/uv-dispatch/Cargo.toml @@ -32,6 +32,7 @@ uv-platform-tags = { workspace = true } uv-preview = { workspace = true } uv-pypi-types = { workspace = true } uv-python = { workspace = true } +uv-requirements = { workspace = true } uv-resolver = { workspace = true } uv-types = { workspace = true } uv-version = { workspace = true } diff --git a/crates/uv-dispatch/src/lib.rs b/crates/uv-dispatch/src/lib.rs index 673e7e5656c15..b19f5fc1e0bf7 100644 --- a/crates/uv-dispatch/src/lib.rs +++ b/crates/uv-dispatch/src/lib.rs @@ -16,7 +16,9 @@ use uv_build_backend::check_direct_build; use uv_build_frontend::{SourceBuild, SourceBuildContext}; use uv_cache::Cache; use uv_client::RegistryClient; -use uv_configuration::{BuildKind, BuildOptions, Constraints, IndexStrategy, NoSources, Reinstall}; +use uv_configuration::{ + BuildKind, BuildOptions, Constraints, IndexStrategy, NoSources, Overrides, Reinstall, +}; use uv_configuration::{BuildOutput, Concurrency}; use uv_distribution::DistributionDatabase; use uv_distribution_filename::DistFilename; @@ -30,6 +32,7 @@ use uv_installer::{InstallationStrategy, Installer, Plan, Planner, Preparer, Sit use uv_preview::Preview; use uv_pypi_types::Conflicts; use uv_python::{Interpreter, PythonEnvironment}; +use uv_requirements::LookaheadResolver; use uv_resolver::{ ExcludeNewer, FlatIndex, Flexibility, InMemoryIndex, Manifest, OptionsBuilder, PythonRequirement, Resolver, ResolverEnvironment, @@ -59,6 +62,9 @@ pub enum BuildDispatchError { #[error(transparent)] Prepare(#[from] uv_installer::PrepareError), + + #[error(transparent)] + Lookahead(#[from] uv_requirements::Error), } impl IsBuildBackendError for BuildDispatchError { @@ -68,7 +74,8 @@ impl IsBuildBackendError for BuildDispatchError { | Self::Resolve(_) | Self::Join(_) | Self::Anyhow(_) - | Self::Prepare(_) => false, + | Self::Prepare(_) + | Self::Lookahead(_) => false, Self::BuildFrontend(err) => err.is_build_backend_error(), } } @@ -250,10 +257,37 @@ impl BuildContext for BuildDispatch<'_> { ) -> Result { let python_requirement = PythonRequirement::from_interpreter(self.interpreter); let marker_env = self.interpreter.resolver_marker_environment(); + let resolver_env = ResolverEnvironment::specific(marker_env); let tags = self.interpreter.tags()?; + // Walk any URL requirements transitively so their sub-URLs (for example, a workspace + // member that depends on another workspace member) are known before the resolver runs + // its URL allow-list check. This mirrors what the project resolver does in + // `uv_requirements::LookaheadResolver` and prevents a `DisallowedUrl` error when one + // `build-system.requires` entry pulls in another URL dependency. + let overrides = Overrides::default(); + let (lookaheads, _hasher) = LookaheadResolver::new( + requirements, + self.constraints, + &overrides, + self.hasher, + &self.shared_state.index, + DistributionDatabase::new( + self.client, + self, + self.concurrency.downloads_semaphore.clone(), + ) + .with_build_stack(build_stack), + ) + .resolve(&resolver_env) + .await?; + + let manifest = Manifest::simple(requirements.to_vec()) + .with_constraints(self.constraints.clone()) + .with_lookaheads(lookaheads); + let resolver = Resolver::new( - Manifest::simple(requirements.to_vec()).with_constraints(self.constraints.clone()), + manifest, OptionsBuilder::new() .exclude_newer(self.exclude_newer.clone()) .index_strategy(self.index_strategy) @@ -261,7 +295,7 @@ impl BuildContext for BuildDispatch<'_> { .flexibility(Flexibility::Fixed) .build(), &python_requirement, - ResolverEnvironment::specific(marker_env), + resolver_env, self.interpreter.markers(), // Conflicting groups only make sense when doing universal resolution. Conflicts::empty(), diff --git a/crates/uv-resolver/src/manifest.rs b/crates/uv-resolver/src/manifest.rs index fd39470de97e9..a64069fa54fdb 100644 --- a/crates/uv-resolver/src/manifest.rs +++ b/crates/uv-resolver/src/manifest.rs @@ -98,6 +98,12 @@ impl Manifest { self } + #[must_use] + pub fn with_lookaheads(mut self, lookaheads: Vec) -> Self { + self.lookaheads = lookaheads; + self + } + /// Return an iterator over all requirements, constraints, and overrides, in priority order, /// such that requirements come first, followed by constraints, followed by overrides. /// diff --git a/crates/uv/tests/it/build.rs b/crates/uv/tests/it/build.rs index 7532c3c4dfa20..e6a963c617d84 100644 --- a/crates/uv/tests/it/build.rs +++ b/crates/uv/tests/it/build.rs @@ -1078,6 +1078,139 @@ fn build_source_path_ignores_workspace_build_constraint_dependencies() -> Result Ok(()) } +/// A workspace member can use another workspace member as a PEP 517 build dependency, even when +/// that build dependency itself depends on a third workspace member. Regression test for +/// . +#[test] +fn build_workspace_transitive_build_dependency() -> Result<()> { + let context = uv_test::test_context!("3.12"); + let filters = context + .filters() + .into_iter() + .chain([ + (r"\\\.", ""), + (r"\[my-util\]", "[PKG]"), + (r"\[my-backend\]", "[PKG]"), + (r"\[my-tool\]", "[PKG]"), + ]) + .collect::>(); + + let project = context.temp_dir.child("project"); + + project.child("pyproject.toml").write_str( + r#" + [project] + name = "my-workspace" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + + [tool.uv] + package = false + + [tool.uv.workspace] + members = ["my-util", "my-backend", "my-tool"] + + [tool.uv.sources] + my-backend = { workspace = true } + my-util = { workspace = true } + "#, + )?; + project.child("README.md").touch()?; + + let my_util = project.child("my-util"); + fs_err::create_dir_all(my_util.path())?; + my_util.child("pyproject.toml").write_str( + r#" + [project] + name = "my-util" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + + [build-system] + requires = ["hatchling"] + build-backend = "hatchling.build" + "#, + )?; + my_util + .child("src") + .child("my_util") + .child("__init__.py") + .touch()?; + my_util.child("README.md").touch()?; + + let my_backend = project.child("my-backend"); + fs_err::create_dir_all(my_backend.path())?; + my_backend.child("pyproject.toml").write_str( + r#" + [project] + name = "my-backend" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["my-util"] + + [build-system] + requires = ["hatchling"] + build-backend = "hatchling.build" + "#, + )?; + my_backend + .child("src") + .child("my_backend") + .child("__init__.py") + .touch()?; + my_backend.child("README.md").touch()?; + + let my_tool = project.child("my-tool"); + fs_err::create_dir_all(my_tool.path())?; + my_tool.child("pyproject.toml").write_str( + r#" + [project] + name = "my-tool" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + + [build-system] + requires = ["my-backend", "hatchling"] + build-backend = "hatchling.build" + "#, + )?; + my_tool + .child("src") + .child("my_tool") + .child("__init__.py") + .touch()?; + my_tool.child("README.md").touch()?; + + uv_snapshot!( + &filters, + context + .build() + .arg("--wheel") + .arg("--package") + .arg("my-tool") + .current_dir(&project), + @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Building wheel... + Successfully built dist/my_tool-0.1.0-py3-none-any.whl + " + ); + + project + .child("dist") + .child("my_tool-0.1.0-py3-none-any.whl") + .assert(predicate::path::is_file()); + + Ok(()) +} + #[test] fn build_sha() -> Result<()> { let context = uv_test::test_context!(DEFAULT_PYTHON_VERSION); From abba7753636d9a8ccecb825e1bc41f786dabfeb8 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 20 Apr 2026 16:17:10 -0400 Subject: [PATCH 36/70] Respect discovered hashes from lookahead build dependencies (#19086) ## Summary Small follow-up to https://github.com/astral-sh/uv/pull/19076. If a lookahead URL has its own SHA, we need to include it in the acceptable SHAs for that URL. --- crates/uv-build-frontend/src/lib.rs | 9 ++- crates/uv-dispatch/src/lib.rs | 30 +++++--- crates/uv-types/src/requirements.rs | 30 +++++++- crates/uv-types/src/traits.rs | 8 +- crates/uv/src/commands/venv.rs | 4 +- crates/uv/tests/it/build.rs | 112 +++++++++++++++++++++++++++- 6 files changed, 170 insertions(+), 23 deletions(-) diff --git a/crates/uv-build-frontend/src/lib.rs b/crates/uv-build-frontend/src/lib.rs index 98381e4c7f103..5ad3497edd1d6 100644 --- a/crates/uv-build-frontend/src/lib.rs +++ b/crates/uv-build-frontend/src/lib.rs @@ -33,7 +33,6 @@ use uv_configuration::{BuildKind, BuildOutput, NoSources}; use uv_distribution::BuildRequires; use uv_distribution_types::{ ConfigSettings, ExtraBuildRequirement, ExtraBuildRequires, IndexLocations, Requirement, - Resolution, }; use uv_fs::{LockedFile, LockedFileMode}; use uv_fs::{PythonExt, Simplified}; @@ -42,7 +41,9 @@ use uv_pep440::Version; use uv_pypi_types::VerbatimParsedUrl; use uv_python::{Interpreter, PythonEnvironment}; use uv_static::EnvVars; -use uv_types::{AnyErrorBuild, BuildContext, BuildIsolation, BuildStack, SourceBuildTrait}; +use uv_types::{ + AnyErrorBuild, BuildContext, BuildIsolation, BuildStack, ResolvedRequirements, SourceBuildTrait, +}; use uv_warnings::warn_user_once; use uv_workspace::WorkspaceCache; @@ -219,7 +220,7 @@ impl Pep517Backend { #[derive(Debug, Clone)] pub struct SourceBuildContext { /// An in-memory resolution of the default backend's requirements for PEP 517 builds. - default_resolution: Arc>>, + default_resolution: Arc>>, /// A shared semaphore to limit the number of concurrent builds. concurrent_build_slots: Arc, } @@ -522,7 +523,7 @@ impl SourceBuild { pep517_backend: &Pep517Backend, extra_build_dependencies: Vec, build_stack: &BuildStack, - ) -> Result { + ) -> Result { Ok( if pep517_backend.requirements == DEFAULT_BACKEND.requirements && extra_build_dependencies.is_empty() diff --git a/crates/uv-dispatch/src/lib.rs b/crates/uv-dispatch/src/lib.rs index b19f5fc1e0bf7..dd9ad8cf7fcf1 100644 --- a/crates/uv-dispatch/src/lib.rs +++ b/crates/uv-dispatch/src/lib.rs @@ -39,7 +39,7 @@ use uv_resolver::{ }; use uv_types::{ AnyErrorBuild, BuildArena, BuildContext, BuildIsolation, BuildStack, EmptyInstalledPackages, - HashStrategy, InFlight, SourceTreeEditablePolicy, + HashStrategy, InFlight, ResolvedRequirements, SourceTreeEditablePolicy, }; use uv_workspace::WorkspaceCache; @@ -254,7 +254,7 @@ impl BuildContext for BuildDispatch<'_> { &'data self, requirements: &'data [Requirement], build_stack: &'data BuildStack, - ) -> Result { + ) -> Result { let python_requirement = PythonRequirement::from_interpreter(self.interpreter); let marker_env = self.interpreter.resolver_marker_environment(); let resolver_env = ResolverEnvironment::specific(marker_env); @@ -265,12 +265,17 @@ impl BuildContext for BuildDispatch<'_> { // its URL allow-list check. This mirrors what the project resolver does in // `uv_requirements::LookaheadResolver` and prevents a `DisallowedUrl` error when one // `build-system.requires` entry pulls in another URL dependency. + let hasher = self + .hasher + .clone() + .augment_with_requirements(requirements.iter()) + .map_err(uv_requirements::Error::from)?; let overrides = Overrides::default(); - let (lookaheads, _hasher) = LookaheadResolver::new( + let (lookaheads, hasher) = LookaheadResolver::new( requirements, self.constraints, &overrides, - self.hasher, + &hasher, &self.shared_state.index, DistributionDatabase::new( self.client, @@ -302,7 +307,7 @@ impl BuildContext for BuildDispatch<'_> { Some(tags), self.flat_index, &self.shared_state.index, - self.hasher, + &hasher, self, EmptyInstalledPackages, DistributionDatabase::new( @@ -321,22 +326,25 @@ impl BuildContext for BuildDispatch<'_> { .join(", ") ) })?); - Ok(resolution) + Ok(ResolvedRequirements::new(resolution, hasher)) } #[instrument( - skip(self, resolution, venv), + skip(self, requirements, venv), fields( - resolution = resolution.distributions().map(ToString::to_string).join(", "), + resolution = requirements.resolution().distributions().map(ToString::to_string).join(", "), venv = ?venv.root() ) )] async fn install<'data>( &'data self, - resolution: &'data Resolution, + requirements: &'data ResolvedRequirements, venv: &'data PythonEnvironment, build_stack: &'data BuildStack, ) -> Result, BuildDispatchError> { + let resolution = requirements.resolution(); + let hasher = requirements.hasher(); + debug!( "Installing in {} in {}", resolution @@ -362,7 +370,7 @@ impl BuildContext for BuildDispatch<'_> { InstallationStrategy::Permissive, &Reinstall::default(), self.build_options, - self.hasher, + hasher, self.index_locations, self.config_settings, self.config_settings_package, @@ -396,7 +404,7 @@ impl BuildContext for BuildDispatch<'_> { let preparer = Preparer::new( self.cache, tags, - self.hasher, + hasher, self.build_options, DistributionDatabase::new( self.client, diff --git a/crates/uv-types/src/requirements.rs b/crates/uv-types/src/requirements.rs index 6b3b0195918ac..3ecc3da6704b2 100644 --- a/crates/uv-types/src/requirements.rs +++ b/crates/uv-types/src/requirements.rs @@ -1,6 +1,34 @@ -use uv_distribution_types::Requirement; +use uv_distribution_types::{Requirement, Resolution}; use uv_normalize::ExtraName; +use crate::HashStrategy; + +/// A resolved set of requirements, along with the hash policy discovered while resolving them. +#[derive(Debug, Clone)] +pub struct ResolvedRequirements { + /// The resolved distributions to install. + resolution: Resolution, + /// The hash policy to apply when installing the resolution. + hasher: HashStrategy, +} + +impl ResolvedRequirements { + /// Instantiate a [`ResolvedRequirements`] with the given [`Resolution`] and [`HashStrategy`]. + pub fn new(resolution: Resolution, hasher: HashStrategy) -> Self { + Self { resolution, hasher } + } + + /// Return the resolved distributions to install. + pub fn resolution(&self) -> &Resolution { + &self.resolution + } + + /// Return the hash policy to apply when installing the resolution. + pub fn hasher(&self) -> &HashStrategy { + &self.hasher + } +} + /// A set of requirements as requested by a parent requirement. /// /// For example, given `flask[dotenv]`, the `RequestedRequirements` would include the `dotenv` diff --git a/crates/uv-types/src/traits.rs b/crates/uv-types/src/traits.rs index 79d39e174c0cb..b0e2809efea0f 100644 --- a/crates/uv-types/src/traits.rs +++ b/crates/uv-types/src/traits.rs @@ -12,14 +12,14 @@ use uv_distribution_filename::DistFilename; use uv_distribution_types::{ CachedDist, ConfigSettings, DependencyMetadata, DistributionId, ExtraBuildRequires, ExtraBuildVariables, IndexCapabilities, IndexLocations, InstalledDist, IsBuildBackendError, - PackageConfigSettings, Requirement, Resolution, SourceDist, + PackageConfigSettings, Requirement, SourceDist, }; use uv_git::GitResolver; use uv_normalize::PackageName; use uv_python::{Interpreter, PythonEnvironment}; use uv_workspace::WorkspaceCache; -use crate::{BuildArena, BuildIsolation}; +use crate::{BuildArena, BuildIsolation, ResolvedRequirements}; /// Controls how source tree requirements influence workspace-member editability during lowering. #[derive(Debug, Clone, Copy, Default, Eq, PartialEq)] @@ -151,13 +151,13 @@ pub trait BuildContext { &'a self, requirements: &'a [Requirement], build_stack: &'a BuildStack, - ) -> impl Future> + 'a; + ) -> impl Future> + 'a; /// Install the given set of package versions into the virtual environment. The environment must /// use the same base Python as [`BuildContext::interpreter`] fn install<'a>( &'a self, - resolution: &'a Resolution, + requirements: &'a ResolvedRequirements, venv: &'a PythonEnvironment, build_stack: &'a BuildStack, ) -> impl Future, impl IsBuildBackendError>> + 'a; diff --git a/crates/uv/src/commands/venv.rs b/crates/uv/src/commands/venv.rs index 806d266cd5ce4..ac587f206ded2 100644 --- a/crates/uv/src/commands/venv.rs +++ b/crates/uv/src/commands/venv.rs @@ -300,12 +300,12 @@ pub(crate) async fn venv( // // Since the virtual environment is empty, and the set of requirements is trivial (no // constraints, no editables, etc.), we can use the build dispatch APIs directly. - let resolution = build_dispatch + let requirements = build_dispatch .resolve(&requirements, &build_stack) .await .map_err(|err| VenvError::Seed(err.into()))?; let installed = build_dispatch - .install(&resolution, &venv, &build_stack) + .install(&requirements, &venv, &build_stack) .await .map_err(|err| VenvError::Seed(err.into()))?; diff --git a/crates/uv/tests/it/build.rs b/crates/uv/tests/it/build.rs index e6a963c617d84..48c5d0d3cbc8d 100644 --- a/crates/uv/tests/it/build.rs +++ b/crates/uv/tests/it/build.rs @@ -2,12 +2,17 @@ use anyhow::Result; use assert_cmd::assert::OutputAssertExt; use assert_fs::prelude::*; use fs_err::File; -use indoc::indoc; +use indoc::{formatdoc, indoc}; use insta::assert_snapshot; use predicates::prelude::predicate; use std::env::current_dir; +use url::Url; use uv_static::EnvVars; use uv_test::{DEFAULT_PYTHON_VERSION, apply_filters, uv_snapshot}; +use wiremock::{ + Mock, MockServer, ResponseTemplate, + matchers::{method, path as url_path}, +}; use zip::ZipArchive; #[test] @@ -1415,6 +1420,111 @@ fn build_sha() -> Result<()> { Ok(()) } +#[tokio::test] +async fn build_transitive_url_build_requirement_hashes() -> Result<()> { + let context = uv_test::test_context!("3.12"); + let filters = context + .filters() + .into_iter() + .chain([(r"\\\.", "")]) + .collect::>(); + + let ok_wheel = current_dir()?.join("../../test/links/ok-1.0.0-py3-none-any.whl"); + let validation_wheel = + current_dir()?.join("../../test/links/validation-1.0.0-py3-none-any.whl"); + let server = MockServer::start().await; + let ok_wheel_url = Url::parse(&format!("{}/ok-1.0.0-py3-none-any.whl", server.uri()))?; + let validation_wheel_url = Url::parse(&format!( + "{}/validation-1.0.0-py3-none-any.whl", + server.uri() + ))?; + + Mock::given(method("GET")) + .and(url_path("/ok-1.0.0-py3-none-any.whl")) + .respond_with(ResponseTemplate::new(200).set_body_bytes(fs_err::read(ok_wheel)?)) + .mount(&server) + .await; + Mock::given(method("GET")) + .and(url_path("/validation-1.0.0-py3-none-any.whl")) + .respond_with(ResponseTemplate::new(200).set_body_bytes(fs_err::read(validation_wheel)?)) + .mount(&server) + .await; + + let project = context.temp_dir.child("project"); + + project.child("pyproject.toml").write_str(&formatdoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + + [build-system] + requires = ["validation @ {validation_wheel_url}#sha256=23ee8bda94d44f5480dccca240b37a4de7c823bc4683d00fd8e5eb85cf056ce6"] + build-backend = "backend" + backend-path = ["."] + + [[tool.uv.dependency-metadata]] + name = "validation" + version = "1.0.0" + requires-dist = ["ok @ {ok_wheel_url}#sha256=79f0b33e6ce1e09eaa1784c8eee275dfe84d215d9c65c652f07c18e85fdaac5f"] + "#})?; + project.child("backend.py").write_str(indoc! {r#" + import pathlib + import zipfile + + + def build_wheel(wheel_directory, config_settings=None, metadata_directory=None): + wheel_name = "project-0.1.0-py3-none-any.whl" + wheel_path = pathlib.Path(wheel_directory, wheel_name) + records = [ + ("project/__init__.py", b""), + ( + "project-0.1.0.dist-info/METADATA", + b"Metadata-Version: 2.1\nName: project\nVersion: 0.1.0\n", + ), + ( + "project-0.1.0.dist-info/WHEEL", + b"Wheel-Version: 1.0\nGenerator: uv-test\nRoot-Is-Purelib: true\nTag: py3-none-any\n", + ), + ] + + with zipfile.ZipFile(wheel_path, "w") as wheel: + for path, contents in records: + wheel.writestr(path, contents) + record = "\n".join(f"{path},," for path, _ in records) + wheel.writestr( + "project-0.1.0.dist-info/RECORD", + record + "\nproject-0.1.0.dist-info/RECORD,,\n", + ) + + return wheel_name + "#})?; + uv_snapshot!( + &filters, + context + .build() + .arg("--wheel") + .arg("--require-hashes") + .current_dir(&project), + @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Building wheel... + Successfully built dist/project-0.1.0-py3-none-any.whl + " + ); + + project + .child("dist") + .child("project-0.1.0-py3-none-any.whl") + .assert(predicate::path::is_file()); + + Ok(()) +} + #[test] fn build_quiet() -> Result<()> { let context = uv_test::test_context!("3.12"); From 45b930fdc793302f675009a7f9858a148e478841 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 16:36:59 -0400 Subject: [PATCH 37/70] Update taiki-e/install-action action to v2.75.10 (#19071) --- .github/workflows/bench.yml | 6 +++--- .github/workflows/check-lint.yml | 2 +- .github/workflows/test-windows-trampolines.yml | 2 +- .github/workflows/test.yml | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index a51729b2a7fd8..8cd65d1d0839d 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -35,7 +35,7 @@ jobs: run: rustup show - name: "Install codspeed" - uses: taiki-e/install-action@7a562dfa955aa2e4d5b0fd6ebd57ff9715c07b0b # v2.73.0 + uses: taiki-e/install-action@85b24a67ef0c632dfefad70b9d5ce8fddb040754 # v2.75.10 with: tool: cargo-codspeed @@ -72,7 +72,7 @@ jobs: persist-credentials: false - name: "Install codspeed" - uses: taiki-e/install-action@7a562dfa955aa2e4d5b0fd6ebd57ff9715c07b0b # v2.73.0 + uses: taiki-e/install-action@85b24a67ef0c632dfefad70b9d5ce8fddb040754 # v2.75.10 with: tool: cargo-codspeed @@ -113,7 +113,7 @@ jobs: run: rustup show - name: "Install codspeed" - uses: taiki-e/install-action@7a562dfa955aa2e4d5b0fd6ebd57ff9715c07b0b # v2.73.0 + uses: taiki-e/install-action@85b24a67ef0c632dfefad70b9d5ce8fddb040754 # v2.75.10 with: tool: cargo-codspeed diff --git a/.github/workflows/check-lint.yml b/.github/workflows/check-lint.yml index 4090e5d65c927..4f399a49a85a0 100644 --- a/.github/workflows/check-lint.yml +++ b/.github/workflows/check-lint.yml @@ -151,7 +151,7 @@ jobs: with: persist-credentials: false - name: "Install cargo shear" - uses: taiki-e/install-action@7a562dfa955aa2e4d5b0fd6ebd57ff9715c07b0b # v2.73.0 + uses: taiki-e/install-action@85b24a67ef0c632dfefad70b9d5ce8fddb040754 # v2.75.10 with: tool: cargo-shear - run: cargo shear --deny-warnings diff --git a/.github/workflows/test-windows-trampolines.yml b/.github/workflows/test-windows-trampolines.yml index 3955700200b96..f6b210342a3d5 100644 --- a/.github/workflows/test-windows-trampolines.yml +++ b/.github/workflows/test-windows-trampolines.yml @@ -81,7 +81,7 @@ jobs: rustup component add rust-src --target ${{ matrix.target-arch }}-pc-windows-msvc - name: "Install cargo-bloat" - uses: taiki-e/install-action@7a562dfa955aa2e4d5b0fd6ebd57ff9715c07b0b # v2.73.0 + uses: taiki-e/install-action@85b24a67ef0c632dfefad70b9d5ce8fddb040754 # v2.75.10 with: tool: cargo-bloat diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index caf1c50d89f1f..25f8fd214305f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -86,7 +86,7 @@ jobs: sudo chown "$(id -u):$(id -g)" /minix - name: "Install cargo nextest" - uses: taiki-e/install-action@7a562dfa955aa2e4d5b0fd6ebd57ff9715c07b0b # v2.73.0 + uses: taiki-e/install-action@85b24a67ef0c632dfefad70b9d5ce8fddb040754 # v2.75.10 with: tool: cargo-nextest @@ -151,7 +151,7 @@ jobs: run: uv python install - name: "Install cargo nextest" - uses: taiki-e/install-action@7a562dfa955aa2e4d5b0fd6ebd57ff9715c07b0b # v2.73.0 + uses: taiki-e/install-action@85b24a67ef0c632dfefad70b9d5ce8fddb040754 # v2.75.10 with: tool: cargo-nextest @@ -229,7 +229,7 @@ jobs: run: New-Item -Path "C:\uv" -ItemType Directory -Force - name: "Install cargo nextest" - uses: taiki-e/install-action@7a562dfa955aa2e4d5b0fd6ebd57ff9715c07b0b # v2.73.0 + uses: taiki-e/install-action@85b24a67ef0c632dfefad70b9d5ce8fddb040754 # v2.75.10 with: tool: cargo-nextest From 7b537ed25c6145fa038296b7f59b78fa6c0be7dd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 09:05:43 -0500 Subject: [PATCH 38/70] Update acj/freebsd-firecracker-action action to v0.9.1 (#19053) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [acj/freebsd-firecracker-action](https://redirect.github.com/acj/freebsd-firecracker-action) | action | patch | `v0.9.0` → `v0.9.1` | --- ### Release Notes
acj/freebsd-firecracker-action (acj/freebsd-firecracker-action) ### [`v0.9.1`](https://redirect.github.com/acj/freebsd-firecracker-action/releases/tag/v0.9.1) [Compare Source](https://redirect.github.com/acj/freebsd-firecracker-action/compare/v0.9.0...v0.9.1) Changes: - Upgrade to Firecracker 1.15.1
--- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/astral-sh/uv). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-dev-binaries.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-dev-binaries.yml b/.github/workflows/build-dev-binaries.yml index c9961e899ad03..b2519636c038a 100644 --- a/.github/workflows/build-dev-binaries.yml +++ b/.github/workflows/build-dev-binaries.yml @@ -356,7 +356,7 @@ jobs: cross build --target x86_64-unknown-freebsd --profile no-debug - name: Test in Firecracker VM - uses: acj/freebsd-firecracker-action@1cc93bd507a8376bddcd15a2b9c660394dc60456 # v0.9.0 + uses: acj/freebsd-firecracker-action@bab3e77871573c7943b80816f1641b6c1ce36896 # v0.9.1 with: verbose: false checkout: false From eddd2a8beea991bafe7fc5c66fa9caabc3bb4714 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 09:05:58 -0500 Subject: [PATCH 39/70] Update MSRV to v1.93.0 (#19065) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Update | Change | Pending | |---|---|---|---| | [msrv](https://redirect.github.com/rust-lang/rust) | minor | `1.92.0` → `1.93.0` | `1.95.0` (+3) | --- ### Release Notes
rust-lang/rust (msrv) ### [`v1.93.0`](https://redirect.github.com/rust-lang/rust/blob/HEAD/RELEASES.md#Version-1930-2026-01-22) [Compare Source](https://redirect.github.com/rust-lang/rust/compare/1.92.0...1.93.0) \========================== ## Language - [Stabilize several s390x `vector`-related target features and the `is_s390x_feature_detected!` macro](https://redirect.github.com/rust-lang/rust/pull/145656) - [Stabilize declaration of C-style variadic functions for the `system` ABI](https://redirect.github.com/rust-lang/rust/pull/145954) - [Emit error when using some keyword as a `cfg` predicate](https://redirect.github.com/rust-lang/rust/pull/146978) - [Stabilize `asm_cfg`](https://redirect.github.com/rust-lang/rust/pull/147736) - [During const-evaluation, support copying pointers byte-by-byte](https://redirect.github.com/rust-lang/rust/pull/148259) - [LUB coercions now correctly handle function item types, and functions with differing safeties](https://redirect.github.com/rust-lang/rust/pull/148602) - [Allow `const` items that contain mutable references to `static` (which is *very* unsafe, but not *always* UB)](https://redirect.github.com/rust-lang/rust/pull/148746) - [Add warn-by-default `const_item_interior_mutations` lint to warn against calls which mutate interior mutable `const` items](https://redirect.github.com/rust-lang/rust/pull/148407) - [Add warn-by-default `function_casts_as_integer` lint](https://redirect.github.com/rust-lang/rust/pull/141470) ## Compiler - [Stabilize `-Cjump-tables=bool`](https://redirect.github.com/rust-lang/rust/pull/145974). The flag was previously called `-Zno-jump-tables`. ## Platform Support - [Promote `riscv64a23-unknown-linux-gnu` to Tier 2 (without host tools)](https://redirect.github.com/rust-lang/rust/pull/148435) Refer to Rust's [platform support page][platform-support-doc] for more information on Rust's tiered platform support. [platform-support-doc]: https://doc.rust-lang.org/rustc/platform-support.html ## Libraries - [Stop internally using `specialization` on the `Copy` trait as it is unsound in the presence of lifetime dependent `Copy` implementations. This may result in some performance regressions as some standard library APIs may now call `Clone::clone` instead of performing bitwise copies](https://redirect.github.com/rust-lang/rust/pull/135634) - [Allow the global allocator to use thread-local storage and `std::thread::current()`](https://redirect.github.com/rust-lang/rust/pull/144465) - [Make `BTree::append` not update existing keys when appending an entry which already exists](https://redirect.github.com/rust-lang/rust/pull/145628) - [Don't require `T: RefUnwindSafe` for `vec::IntoIter: UnwindSafe`](https://redirect.github.com/rust-lang/rust/pull/145665) ## Stabilized APIs - [`<[MaybeUninit]>::assume_init_drop`](https://doc.rust-lang.org/stable/core/primitive.slice.html#method.assume_init_drop) - [`<[MaybeUninit]>::assume_init_ref`](https://doc.rust-lang.org/stable/core/primitive.slice.html#method.assume_init_ref) - [`<[MaybeUninit]>::assume_init_mut`](https://doc.rust-lang.org/stable/core/primitive.slice.html#method.assume_init_mut) - [`<[MaybeUninit]>::write_copy_of_slice`](https://doc.rust-lang.org/stable/std/primitive.slice.html#method.write_copy_of_slice) - [`<[MaybeUninit]>::write_clone_of_slice`](https://doc.rust-lang.org/stable/std/primitive.slice.html#method.write_clone_of_slice) - [`String::into_raw_parts`](https://doc.rust-lang.org/stable/std/string/struct.String.html#method.into_raw_parts) - [`Vec::into_raw_parts`](https://doc.rust-lang.org/stable/std/vec/struct.Vec.html#method.into_raw_parts) - [`::unchecked_neg`](https://doc.rust-lang.org/stable/std/primitive.isize.html#method.unchecked_neg) - [`::unchecked_shl`](https://doc.rust-lang.org/stable/std/primitive.isize.html#method.unchecked_shl) - [`::unchecked_shr`](https://doc.rust-lang.org/stable/std/primitive.isize.html#method.unchecked_shr) - [`::unchecked_shl`](https://doc.rust-lang.org/stable/std/primitive.usize.html#method.unchecked_shl) - [`::unchecked_shr`](https://doc.rust-lang.org/stable/std/primitive.usize.html#method.unchecked_shr) - [`<[T]>::as_array`](https://doc.rust-lang.org/stable/std/primitive.slice.html#method.as_array) - [`<[T]>::as_mut_array`](https://doc.rust-lang.org/stable/std/primitive.slice.html#method.as_mut_array) - [`<*const [T]>::as_array`](https://doc.rust-lang.org/stable/std/primitive.pointer.html#method.as_array) - [`<*mut [T]>::as_mut_array`](https://doc.rust-lang.org/stable/std/primitive.pointer.html#method.as_mut_array) - [`VecDeque::pop_front_if`](https://doc.rust-lang.org/stable/std/collections/struct.VecDeque.html#method.pop_front_if) - [`VecDeque::pop_back_if`](https://doc.rust-lang.org/stable/std/collections/struct.VecDeque.html#method.pop_back_if) - [`Duration::from_nanos_u128`](https://doc.rust-lang.org/stable/std/time/struct.Duration.html#method.from_nanos_u128) - [`char::MAX_LEN_UTF8`](https://doc.rust-lang.org/stable/std/primitive.char.html#associatedconstant.MAX_LEN_UTF8) - [`char::MAX_LEN_UTF16`](https://doc.rust-lang.org/stable/std/primitive.char.html#associatedconstant.MAX_LEN_UTF16) - [`std::fmt::from_fn`](https://doc.rust-lang.org/stable/std/fmt/fn.from_fn.html) - [`std::fmt::FromFn`](https://doc.rust-lang.org/stable/std/fmt/struct.FromFn.html) ## Cargo - [Enable CARGO\_CFG\_DEBUG\_ASSERTIONS in build scripts based on profile](https://redirect.github.com/rust-lang/cargo/pull/16160/) - [In `cargo tree`, support long forms for `--format` variables](https://redirect.github.com/rust-lang/cargo/pull/16204/) - [Add `--workspace` to `cargo clean`](https://redirect.github.com/rust-lang/cargo/pull/16263/) ## Rustdoc - [Remove `#![doc(document_private_items)]`](https://redirect.github.com/rust-lang/rust/pull/146495) - [Include attribute and derive macros in search filters for "macros"](https://redirect.github.com/rust-lang/rust/pull/148176) - [Include extern crates in search filters for `import`](https://redirect.github.com/rust-lang/rust/pull/148301) - [Validate usage of crate-level doc attributes](https://redirect.github.com/rust-lang/rust/pull/149197). This means if any of `html_favicon_url`, `html_logo_url`, `html_playground_url`, `issue_tracker_base_url`, or `html_no_source` either has a missing value, an unexpected value, or a value of the wrong type, rustdoc will emit the deny-by-default lint `rustdoc::invalid_doc_attributes`. ## Compatibility Notes - [Introduce `pin_v2` into the builtin attributes namespace](https://redirect.github.com/rust-lang/rust/pull/139751) - [Update bundled musl to 1.2.5](https://redirect.github.com/rust-lang/rust/pull/142682) - [On Emscripten, the unwinding ABI used when compiling with `panic=unwind` was changed from the JS exception handling ABI to the wasm exception handling ABI.](https://redirect.github.com/rust-lang/rust/pull/147224) If linking C/C++ object files with Rust objects, `-fwasm-exceptions` must be passed to the linker now. On nightly Rust, it is possible to get the old behavior with `-Zwasm-emscripten-eh=false -Zbuild-std`, but it will be removed in a future release. - The `#[test]` attribute, used to define tests, was previously ignored in various places where it had no meaning (e.g on trait methods or types). Putting the `#[test]` attribute in these places is no longer ignored, and will now result in an error; this may also result in errors when generating rustdoc. [Error when `test` attribute is applied to structs](https://redirect.github.com/rust-lang/rust/pull/147841) - Cargo now sets the `CARGO_CFG_DEBUG_ASSERTIONS` environment variable in more situations. This will cause crates depending on `static-init` versions 1.0.1 to 1.0.3 to fail compilation with "failed to resolve: use of unresolved module or unlinked crate `parking_lot`". See [the linked issue](https://redirect.github.com/rust-lang/rust/issues/150646#issuecomment-3718964342) for details. - [User written types in the `offset_of!` macro are now checked to be well formed.](https://redirect.github.com/rust-lang/rust/issues/150465/) - `cargo publish` no longer emits `.crate` files as a final artifact for user access when the `build.build-dir` config is unset - [Upgrade the `deref_nullptr` lint from warn-by-default to deny-by-default](https://redirect.github.com/rust-lang/rust/pull/148122) - [Add future-incompatibility warning for `...` function parameters without a pattern outside of `extern` blocks](https://redirect.github.com/rust-lang/rust/pull/143619) - [Introduce future-compatibility warning for `repr(C)` enums whose discriminant values do not fit into a `c_int` or `c_uint`](https://redirect.github.com/rust-lang/rust/pull/147017) - [Introduce future-compatibility warning against ignoring `repr(C)` types as part of `repr(transparent)`](https://redirect.github.com/rust-lang/rust/pull/147185)
--- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/astral-sh/uv). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index df11156a00f5c..083dae3305dce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ resolver = "2" [workspace.package] edition = "2024" -rust-version = "1.92.0" +rust-version = "1.93.0" homepage = "https://pypi.org/project/uv/" repository = "https://github.com/astral-sh/uv" authors = ["uv"] From a321f23f5e1234695d14f9d5b9e530d815f143ef Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 21 Apr 2026 10:29:47 -0400 Subject: [PATCH 40/70] Fix `UV_NO_PROJECT` added-in metadata (#19093) Follow-up to #19052. --- crates/uv-macros/src/lib.rs | 41 +++++++++++++++++++++++++++----- crates/uv-static/src/env_vars.rs | 2 +- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/crates/uv-macros/src/lib.rs b/crates/uv-macros/src/lib.rs index db2cc98533c90..8f50bfcbdc864 100644 --- a/crates/uv-macros/src/lib.rs +++ b/crates/uv-macros/src/lib.rs @@ -86,6 +86,31 @@ fn get_added_in(attrs: &[Attribute]) -> Option { .map(|lit_str| lit_str.value()) } +fn is_valid_added_in(added_in: &str) -> bool { + added_in == "next release" || is_semantic_version(added_in) +} + +fn is_semantic_version(version: &str) -> bool { + let mut components = version.split('.'); + let Some(major) = components.next() else { + return false; + }; + let Some(minor) = components.next() else { + return false; + }; + let Some(patch) = components.next() else { + return false; + }; + + if components.next().is_some() { + return false; + } + + [major, minor, patch].into_iter().all(|component| { + !component.is_empty() && component.bytes().all(|byte| byte.is_ascii_digit()) + }) +} + fn is_hidden(attrs: &[Attribute]) -> bool { attrs.iter().any(|attr| attr.path().is_ident("attr_hidden")) } @@ -126,16 +151,20 @@ pub fn attribute_env_vars_metadata(_attr: TokenStream, input: TokenStream) -> To }) .collect(); - // Look for missing attr_added_in and issue a compiler error if any are found. + // Look for missing or invalid attr_added_in values and issue a compiler error if any are found. let added_in_errors: Vec<_> = constants .iter() .filter_map(|(name, _, added_in, span)| { - added_in.is_none().then_some({ - let msg = format!( + let msg = match added_in { + None => format!( "missing #[attr_added_in(\"x.y.z\")] on `{name}`\nnote: env vars for an upcoming release should be annotated with `#[attr_added_in(\"next release\")]`" - ); - quote_spanned! {*span => compile_error!(#msg); } - }) + ), + Some(added_in) if !is_valid_added_in(added_in) => format!( + "invalid #[attr_added_in(\"{added_in}\")] on `{name}`\nnote: expected `#[attr_added_in(\"x.y.z\")]` or `#[attr_added_in(\"next release\")]`" + ), + Some(_) => return None, + }; + Some(quote_spanned! {*span => compile_error!(#msg); }) }) .collect(); diff --git a/crates/uv-static/src/env_vars.rs b/crates/uv-static/src/env_vars.rs index 7187f570a4054..391424416729e 100644 --- a/crates/uv-static/src/env_vars.rs +++ b/crates/uv-static/src/env_vars.rs @@ -1282,7 +1282,7 @@ impl EnvVars { pub const UV_PROJECT: &'static str = "UV_PROJECT"; /// Equivalent to the `--no-project` command-line argument. - #[attr_added_in("next")] + #[attr_added_in("next release")] pub const UV_NO_PROJECT: &'static str = "UV_NO_PROJECT"; /// Equivalent to the `--directory` command-line argument. `UV_WORKING_DIRECTORY` (added in From e459e50c132b0793327962aa6a9d09d473e529f5 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 21 Apr 2026 10:47:44 -0400 Subject: [PATCH 41/70] Add test for `exclude-newer-package` with missing `timestamp` in the lockfile (#19094) https://github.com/astral-sh/uv/pull/19021 for per-package values --------- Co-authored-by: Claude --- .../tests/it/lock_exclude_newer_relative.rs | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/crates/uv/tests/it/lock_exclude_newer_relative.rs b/crates/uv/tests/it/lock_exclude_newer_relative.rs index 34d1d8caca57d..023b316b89448 100644 --- a/crates/uv/tests/it/lock_exclude_newer_relative.rs +++ b/crates/uv/tests/it/lock_exclude_newer_relative.rs @@ -1313,6 +1313,97 @@ fn lock_exclude_newer_relative_no_timestamp_in_lockfile() -> Result<()> { Ok(()) } +#[test] +fn lock_exclude_newer_package_relative_no_timestamp_in_lockfile() -> Result<()> { + let context = uv_test::test_context!("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 = ["idna"] + + [tool.uv] + exclude-newer-package = { idna = "3 weeks" } + "#, + )?; + + let current_timestamp = "2024-05-01T00:00:00Z"; + uv_snapshot!(context.filters(), context + .lock() + .env_remove(EnvVars::UV_EXCLUDE_NEWER) + .env(EnvVars::UV_TEST_CURRENT_TIMESTAMP, current_timestamp), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + "); + + let lock = context.read("uv.lock"); + assert_snapshot!(lock, @r#" + version = 1 + revision = 3 + requires-python = ">=3.12" + + [options] + + [options.exclude-newer-package] + idna = { timestamp = "2024-04-10T00:00:00Z", span = "P3W" } + + [[package]] + name = "idna" + version = "3.6" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", size = 175426, upload-time = "2023-11-25T15:40:54.902Z" } + wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f", size = 61567, upload-time = "2023-11-25T15:40:52.604Z" }, + ] + + [[package]] + name = "project" + version = "0.1.0" + source = { virtual = "." } + dependencies = [ + { name = "idna" }, + ] + + [package.metadata] + requires-dist = [{ name = "idna" }] + "#); + + // Manually remove the per-package exclude-newer timestamp from the lockfile, leaving the span. + let lock = lock.replace( + "idna = { timestamp = \"2024-04-10T00:00:00Z\", span = \"P3W\" }", + "idna = { span = \"P3W\" }", + ); + context.temp_dir.child("uv.lock").write_str(&lock)?; + + // Unlike the global case, a per-package entry with only a span (no timestamp) fails to + // deserialize. + uv_snapshot!(context.filters(), context + .lock() + .env_remove(EnvVars::UV_EXCLUDE_NEWER) + .env(EnvVars::UV_TEST_CURRENT_TIMESTAMP, current_timestamp), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Failed to parse `uv.lock` + Caused by: TOML parse error at line 5, column 1 + | + 5 | [options] + | ^^^^^^^^^ + data did not match any variant of untagged enum Helper + "); + + Ok(()) +} + /// Lock with various relative exclude newer value formats in a `pyproject.toml`. #[test] fn lock_exclude_newer_relative_values_pyproject() -> Result<()> { From caee484551a9db8c375c0f3ddb0017bcc694ea67 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 21 Apr 2026 10:49:24 -0400 Subject: [PATCH 42/70] Allow `exclude-newer` to be missing from the lockfile when `exclude-newer-span` is present (#19024) Following #19022 this is an incremental step towards removing `exclude-newer` timestamps from lockfiles. We don't remove it here, but we ensure from this version onward, uv will not behave poorly when the value is missing. This includes substantial refactoring to better represent the desired end state in our exclude-newer types. --- .../src/exclude_newer.rs | 116 +++++++++--------- crates/uv-resolver/src/exclude_newer.rs | 67 +++------- crates/uv-resolver/src/lock/mod.rs | 26 ++-- crates/uv-resolver/src/pubgrub/report.rs | 7 +- crates/uv-resolver/src/resolver/mod.rs | 14 +-- crates/uv-resolver/src/resolver/provider.rs | 2 +- crates/uv-resolver/src/version_map.rs | 11 +- crates/uv-settings/src/settings.rs | 39 ++---- crates/uv/src/commands/pip/latest.rs | 6 +- crates/uv/src/commands/project/tree.rs | 5 +- crates/uv/src/commands/tool/list.rs | 10 +- crates/uv/src/commands/tool/upgrade.rs | 1 - .../tests/it/lock_exclude_newer_relative.rs | 9 +- 13 files changed, 134 insertions(+), 179 deletions(-) diff --git a/crates/uv-distribution-types/src/exclude_newer.rs b/crates/uv-distribution-types/src/exclude_newer.rs index d2d3311499096..1cd69c82d9408 100644 --- a/crates/uv-distribution-types/src/exclude_newer.rs +++ b/crates/uv-distribution-types/src/exclude_newer.rs @@ -61,65 +61,61 @@ impl<'de> serde::Deserialize<'de> for ExcludeNewerSpan { } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct ExcludeNewerValue { - timestamp: Timestamp, - span: Option, +pub enum ExcludeNewerValue { + /// An absolute timestamp. + Absolute(Timestamp), + /// A span used to compute a timestamp relative to the current time. + Relative(ExcludeNewerSpan), } impl ExcludeNewerValue { - pub fn into_parts(self) -> (Timestamp, Option) { - (self.timestamp, self.span) - } - - /// Return the [`Timestamp`] in milliseconds. - pub fn timestamp_millis(&self) -> i64 { - self.timestamp.as_millisecond() - } + /// A placeholder timestamp used when serializing a [`Relative`](Self::Relative) + /// value to a wire format that requires a timestamp field. + pub const PLACEHOLDER: &'static str = "0001-01-01T00:00:00Z"; - /// Return the [`Timestamp`]. + /// Return the effective [`Timestamp`]. + /// + /// For [`Relative`](Self::Relative) values this is computed from the span and the current + /// time on each call. pub fn timestamp(&self) -> Timestamp { - self.timestamp + match self { + Self::Absolute(timestamp) => *timestamp, + Self::Relative(span) => { + let now = current_time(); + now.checked_sub(span.0.abs()) + .map_or(now.timestamp(), |cutoff| cutoff.timestamp()) + } + } } - /// Return the [`ExcludeNewerSpan`] used to construct the [`Timestamp`], if any. + /// Return the [`ExcludeNewerSpan`], if any. pub fn span(&self) -> Option<&ExcludeNewerSpan> { - self.span.as_ref() + match self { + Self::Absolute(_) => None, + Self::Relative(span) => Some(span), + } } - /// Create a new [`ExcludeNewerValue`]. - pub fn new(timestamp: Timestamp, span: Option) -> Self { - Self { timestamp, span } + /// Create a new [`ExcludeNewerValue`] from an absolute timestamp. + pub fn absolute(timestamp: Timestamp) -> Self { + Self::Absolute(timestamp) } - /// If this value was derived from a relative span, recompute the timestamp relative to now. - /// - /// Returns `self` unchanged if there is no span (i.e., the timestamp is absolute). - #[must_use] - pub fn recompute(self) -> Self { - let Some(span) = self.span else { - return self; - }; - - let now = if let Ok(test_time) = std::env::var("UV_TEST_CURRENT_TIMESTAMP") { - test_time - .parse::() - .expect("UV_TEST_CURRENT_TIMESTAMP must be a valid RFC 3339 timestamp") - .to_zoned(TimeZone::UTC) - } else { - Timestamp::now().to_zoned(TimeZone::UTC) - }; - - let Ok(cutoff) = now.checked_sub(span.0.abs()) else { - return Self { - timestamp: self.timestamp, - span: Some(span), - }; - }; + /// Create a new [`ExcludeNewerValue`] from a relative span. + pub fn relative(span: ExcludeNewerSpan) -> Self { + Self::Relative(span) + } +} - Self { - timestamp: cutoff.into(), - span: Some(span), - } +/// Return the current time, respecting the `UV_TEST_CURRENT_TIMESTAMP` override. +fn current_time() -> jiff::Zoned { + if let Ok(test_time) = std::env::var("UV_TEST_CURRENT_TIMESTAMP") { + test_time + .parse::() + .expect("UV_TEST_CURRENT_TIMESTAMP must be a valid RFC 3339 timestamp") + .to_zoned(TimeZone::UTC) + } else { + Timestamp::now().to_zoned(TimeZone::UTC) } } @@ -128,7 +124,7 @@ impl serde::Serialize for ExcludeNewerValue { where S: serde::Serializer, { - self.timestamp.serialize(serializer) + self.timestamp().serialize(serializer) } } @@ -152,17 +148,23 @@ impl<'de> serde::Deserialize<'de> for ExcludeNewerValue { match Helper::deserialize(deserializer)? { Helper::String(s) => Self::from_str(&s).map_err(serde::de::Error::custom), - Helper::Table(table) => Ok(Self::new(table.timestamp, table.span)), + Helper::Table(table) => Ok(match table.span { + Some(span) => Self::relative(span), + None => Self::absolute(table.timestamp), + }), } } } impl From for ExcludeNewerValue { fn from(timestamp: Timestamp) -> Self { - Self { - timestamp, - span: None, - } + Self::Absolute(timestamp) + } +} + +impl From for ExcludeNewerValue { + fn from(span: ExcludeNewerSpan) -> Self { + Self::Relative(span) } } @@ -213,7 +215,7 @@ impl FromStr for ExcludeNewerValue { fn from_str(input: &str) -> Result { if let Ok(timestamp) = input.parse::() { - return Ok(Self::new(timestamp, None)); + return Ok(Self::absolute(timestamp)); } let date_err = match input.parse::() { @@ -227,7 +229,7 @@ impl FromStr for ExcludeNewerValue { "`{input}` parsed to date `{date}`, but could not be converted to a timestamp: {err}", ) })?; - return Ok(Self::new(timestamp, None)); + return Ok(Self::absolute(timestamp)); } Err(err) => err, }; @@ -266,10 +268,10 @@ impl FromStr for ExcludeNewerValue { )); } - let cutoff = now.checked_sub(span.abs()).map_err(|err| { + now.checked_sub(span.abs()).map_err(|err| { format!("Duration `{input}` is too large to subtract from current time: {err}") })?; - return Ok(Self::new(cutoff.into(), Some(ExcludeNewerSpan(span)))); + return Ok(Self::relative(ExcludeNewerSpan(span))); } Err(err) => err, }; @@ -280,7 +282,7 @@ impl FromStr for ExcludeNewerValue { impl std::fmt::Display for ExcludeNewerValue { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.timestamp.fmt(f) + self.timestamp().fmt(f) } } diff --git a/crates/uv-resolver/src/exclude_newer.rs b/crates/uv-resolver/src/exclude_newer.rs index ae9ed57ec4f51..c57dae41edfac 100644 --- a/crates/uv-resolver/src/exclude_newer.rs +++ b/crates/uv-resolver/src/exclude_newer.rs @@ -348,25 +348,6 @@ impl ExcludeNewerPackage { self.0.is_empty() } - /// Recompute all relative span timestamps relative to the current time. - #[must_use] - pub fn recompute(self) -> Self { - Self( - self.0 - .into_iter() - .map(|(name, setting)| { - let setting = match setting { - ExcludeNewerOverride::Disabled => ExcludeNewerOverride::Disabled, - ExcludeNewerOverride::Enabled(value) => { - ExcludeNewerOverride::Enabled(Box::new((*value).recompute())) - } - }; - (name, setting) - }) - .collect(), - ) - } - pub fn compare(&self, other: &Self) -> Option { for (package, setting) in self { match (setting, other.get(package)) { @@ -470,57 +451,54 @@ impl ExcludeNewer { Self { global, package } } - /// Returns the exclude-newer value for a specific package, returning `Some(value)` if the - /// package has a package-specific setting or falls back to the global value if set, or `None` - /// if exclude-newer is explicitly disabled for the package (set to `false`) or if no - /// exclude-newer is configured. - pub fn exclude_newer_package(&self, package_name: &PackageName) -> Option { + /// Returns the effective exclude-newer timestamp for a specific package, falling back to the + /// global value if no package-specific setting exists. + pub fn exclude_newer_package(&self, package_name: &PackageName) -> Option { match self.package.get(package_name) { - Some(ExcludeNewerOverride::Enabled(timestamp)) => Some(timestamp.as_ref().clone()), + Some(ExcludeNewerOverride::Enabled(value)) => Some(value.timestamp()), Some(ExcludeNewerOverride::Disabled) => None, - None => self.global.clone(), + None => self.global.as_ref().map(ExcludeNewerValue::timestamp), } } - /// Returns the effective exclude-newer value for a package resolved from a specific index. + /// Returns the effective exclude-newer timestamp for a package resolved from a specific index. pub fn exclude_newer_package_for_index( &self, package_name: &PackageName, index: Option<&ExcludeNewerOverride>, - ) -> Option { + ) -> Option { self.exclude_newer_package_for_index_with_source(package_name, index) - .map(|(exclude_newer, _)| exclude_newer) + .map(|(timestamp, _)| timestamp) } - /// Returns the effective exclude-newer value and its source for a package resolved from a + /// Returns the effective exclude-newer timestamp and its source for a package resolved from a /// specific index. pub(crate) fn exclude_newer_package_for_index_with_source( &self, package_name: &PackageName, index: Option<&ExcludeNewerOverride>, - ) -> Option<(ExcludeNewerValue, EffectiveExcludeNewerSource)> { + ) -> Option<(Timestamp, EffectiveExcludeNewerSource)> { match self.package.get(package_name) { - Some(ExcludeNewerOverride::Enabled(timestamp)) => Some(( - timestamp.as_ref().clone(), - EffectiveExcludeNewerSource::Package, - )), + Some(ExcludeNewerOverride::Enabled(value)) => { + Some((value.timestamp(), EffectiveExcludeNewerSource::Package)) + } Some(ExcludeNewerOverride::Disabled) => None, None => match index { Some(ExcludeNewerOverride::Disabled) => { Self::warn_index_exclude_newer_preview(); None } - Some(ExcludeNewerOverride::Enabled(timestamp)) => Some(( + Some(ExcludeNewerOverride::Enabled(value)) => Some(( { Self::warn_index_exclude_newer_preview(); - ExcludeNewerValue::from(timestamp.timestamp()) + value.timestamp() }, EffectiveExcludeNewerSource::Index, )), None => self .global - .clone() - .map(|timestamp| (timestamp, EffectiveExcludeNewerSource::Global)), + .as_ref() + .map(|value| (value.timestamp(), EffectiveExcludeNewerSource::Global)), }, } } @@ -530,17 +508,6 @@ impl ExcludeNewer { self.global.is_none() && self.package.is_empty() } - /// Recompute all relative span timestamps relative to the current time. - /// - /// For values with an absolute timestamp (no span), the timestamp is unchanged. - #[must_use] - pub fn recompute(self) -> Self { - Self { - global: self.global.map(ExcludeNewerValue::recompute), - package: self.package.recompute(), - } - } - pub fn compare(&self, other: &Self) -> Option { match (&self.global, &other.global) { (Some(self_global), Some(other_global)) => { diff --git a/crates/uv-resolver/src/lock/mod.rs b/crates/uv-resolver/src/lock/mod.rs index 90a575743f4ae..b250862137383 100644 --- a/crates/uv-resolver/src/lock/mod.rs +++ b/crates/uv-resolver/src/lock/mod.rs @@ -1165,7 +1165,7 @@ impl Lock { // When a relative span is present, write a no-op timestamp to avoid // merge conflicts in the lockfile. In a future version of uv, we'll drop // this field entirely but it's retained for backwards compatibility for now. - let mut noop = value("0001-01-01T00:00:00Z"); + let mut noop = value(ExcludeNewerValue::PLACEHOLDER); if let Item::Value(ref mut v) = noop { v.decor_mut().set_suffix(" # This has no effect and is included for backwards compatibility when using relative exclude-newer values."); } @@ -2358,8 +2358,13 @@ struct ExcludeNewerWire { impl From for ExcludeNewer { fn from(wire: ExcludeNewerWire) -> Self { let global = match (wire.exclude_newer, wire.exclude_newer_span) { - (Some(timestamp), span) => Some(ExcludeNewerValue::new(timestamp, span)), - (None, Some(span)) => Some(ExcludeNewerValue::new(Timestamp::UNIX_EPOCH, Some(span))), + (Some(timestamp), None) => Some(ExcludeNewerValue::absolute(timestamp)), + // We're phasing out writing a timestamp when spans are used. uv writes a dummy + // timestamp for backwards compatibility that we can ignore on deserialization. + (Some(_), Some(span)) => Some(ExcludeNewerValue::relative(span)), + // A future version of uv will remove the timestamp entirely, so for forwards + // compatibility we ignore a missing value. + (None, Some(span)) => Some(ExcludeNewerValue::relative(span)), (None, None) => None, }; Self { @@ -2371,10 +2376,11 @@ impl From for ExcludeNewer { impl From for ExcludeNewerWire { fn from(exclude_newer: ExcludeNewer) -> Self { - let (timestamp, span) = exclude_newer - .global - .map(ExcludeNewerValue::into_parts) - .map_or((None, None), |(t, s)| (Some(t), s)); + let (timestamp, span) = match exclude_newer.global { + Some(ExcludeNewerValue::Absolute(timestamp)) => (Some(timestamp), None), + Some(ExcludeNewerValue::Relative(span)) => (None, Some(span)), + None => (None, None), + }; Self { exclude_newer: timestamp, exclude_newer_span: span, @@ -2557,12 +2563,16 @@ impl TryFrom for Lock { .map(|simplified_marker| simplified_marker.into_marker(&wire.requires_python)) .map(UniversalMarker::from_combined) .collect(); + let mut options = wire.options; + if options.exclude_newer.exclude_newer_span.is_some() { + options.exclude_newer.exclude_newer = None; + } let lock = Self::new( wire.version, wire.revision.unwrap_or(0), packages, wire.requires_python, - wire.options, + options, wire.manifest, wire.conflicts.unwrap_or_else(Conflicts::empty), supported_environments, diff --git a/crates/uv-resolver/src/pubgrub/report.rs b/crates/uv-resolver/src/pubgrub/report.rs index b8e4624723037..1a4fbc70a433f 100644 --- a/crates/uv-resolver/src/pubgrub/report.rs +++ b/crates/uv-resolver/src/pubgrub/report.rs @@ -4,6 +4,7 @@ use std::ops::Bound; use indexmap::IndexSet; use itertools::Itertools; +use jiff::Timestamp; use owo_colors::OwoColorize; use pubgrub::{DerivationTree, Derived, External, Map, Range, ReportFormatter, Term}; use rustc_hash::FxHashMap; @@ -30,9 +31,7 @@ use crate::resolver::{ MetadataUnavailable, UnavailableErrorChain, UnavailablePackage, UnavailableReason, UnavailableVersion, }; -use crate::{ - ExcludeNewerValue, Flexibility, InMemoryIndex, Options, ResolverEnvironment, VersionsResponse, -}; +use crate::{Flexibility, InMemoryIndex, Options, ResolverEnvironment, VersionsResponse}; #[derive(Debug)] pub(crate) struct PubGrubReportFormatter<'a> { @@ -1393,7 +1392,7 @@ pub(crate) enum PubGrubHint { package: PackageName, source: EffectiveExcludeNewerSource, // excluded from `PartialEq` and `Hash` - exclude_newer: ExcludeNewerValue, + exclude_newer: Timestamp, // excluded from `PartialEq` and `Hash` matching_version: Option, }, diff --git a/crates/uv-resolver/src/resolver/mod.rs b/crates/uv-resolver/src/resolver/mod.rs index f05c4e7ca02d9..46e35cf19edeb 100644 --- a/crates/uv-resolver/src/resolver/mod.rs +++ b/crates/uv-resolver/src/resolver/mod.rs @@ -23,10 +23,10 @@ use tracing::{Level, debug, info, instrument, trace, warn}; use uv_configuration::{Constraints, Excludes, Overrides}; use uv_distribution::{ArchiveMetadata, DistributionDatabase}; use uv_distribution_types::{ - BuiltDist, CompatibleDist, DerivationChain, Dist, DistErrorKind, ExcludeNewerValue, Identifier, - IncompatibleDist, IncompatibleSource, IncompatibleWheel, IndexCapabilities, IndexLocations, - IndexMetadata, IndexUrl, InstalledDist, Name, PythonRequirementKind, RemoteSource, Requirement, - ResolvedDist, ResolvedDistRef, SourceDist, VersionOrUrlRef, implied_markers, + BuiltDist, CompatibleDist, DerivationChain, Dist, DistErrorKind, Identifier, IncompatibleDist, + IncompatibleSource, IncompatibleWheel, IndexCapabilities, IndexLocations, IndexMetadata, + IndexUrl, InstalledDist, Name, PythonRequirementKind, RemoteSource, Requirement, ResolvedDist, + ResolvedDistRef, SourceDist, VersionOrUrlRef, implied_markers, }; use uv_git::GitResolver; use uv_normalize::{ExtraName, GroupName, PackageName}; @@ -2725,7 +2725,7 @@ impl ResolverState = + let available_version_cutoff: Option = std::env::var(EnvVars::UV_TEST_AVAILABLE_VERSION_CUTOFF) .ok() .and_then(|s| s.parse().ok()); @@ -2771,7 +2771,7 @@ impl ResolverState= exclude_newer.timestamp_millis() + upload_time >= exclude_newer.as_millisecond() }) }) }; @@ -2794,7 +2794,7 @@ impl ResolverState= exclude_newer.timestamp_millis() + upload_time >= exclude_newer.as_millisecond() }) }) }; diff --git a/crates/uv-resolver/src/resolver/provider.rs b/crates/uv-resolver/src/resolver/provider.rs index e4d0d60bedb35..82bb5009d4ec5 100644 --- a/crates/uv-resolver/src/resolver/provider.rs +++ b/crates/uv-resolver/src/resolver/provider.rs @@ -154,7 +154,7 @@ impl<'a, Context: BuildContext> DefaultResolverProvider<'a, Context> { &self, package_name: &PackageName, index: &uv_distribution_types::IndexUrl, - ) -> Option { + ) -> Option { self.exclude_newer.exclude_newer_package_for_index( package_name, self.index_locations.exclude_newer_for(index), diff --git a/crates/uv-resolver/src/version_map.rs b/crates/uv-resolver/src/version_map.rs index 522b9e78c5ab0..e489c8aa07cea 100644 --- a/crates/uv-resolver/src/version_map.rs +++ b/crates/uv-resolver/src/version_map.rs @@ -3,6 +3,7 @@ use std::collections::btree_map::{BTreeMap, Entry}; use std::ops::RangeBounds; use std::sync::OnceLock; +use jiff::Timestamp; use pubgrub::Ranges; use rustc_hash::FxHashMap; use tracing::{instrument, trace}; @@ -23,7 +24,7 @@ use uv_types::HashStrategy; use uv_warnings::warn_user_once; use crate::flat_index::FlatDistributions; -use crate::{ExcludeNewerValue, yanks::AllowedYanks}; +use crate::yanks::AllowedYanks; /// A map from versions to distributions. #[derive(Debug)] @@ -51,7 +52,7 @@ impl VersionMap { requires_python: &RequiresPython, allowed_yanks: &AllowedYanks, hasher: &HashStrategy, - exclude_newer: Option, + exclude_newer: Option, flat_index: Option, build_options: &BuildOptions, ) -> Self { @@ -189,7 +190,7 @@ impl VersionMap { } /// Return the effective `exclude-newer` cutoff for this version map, if any. - pub(crate) fn exclude_newer(&self) -> Option<&ExcludeNewerValue> { + pub(crate) fn exclude_newer(&self) -> Option<&Timestamp> { match &self.inner { VersionMapInner::Eager(_) => None, VersionMapInner::Lazy(lazy) => lazy.exclude_newer.as_ref(), @@ -398,7 +399,7 @@ struct VersionMapLazy { /// in the current environment. tags: Option, /// Whether files newer than this timestamp should be excluded or not. - exclude_newer: Option, + exclude_newer: Option, /// Which yanked versions are allowed allowed_yanks: AllowedYanks, /// The hashes of allowed distributions. @@ -455,7 +456,7 @@ impl VersionMapLazy { // upload time information. let (excluded, upload_time) = if let Some(exclude_newer) = &self.exclude_newer { match file.upload_time_utc_ms.as_ref() { - Some(&upload_time) if upload_time >= exclude_newer.timestamp_millis() => { + Some(&upload_time) if upload_time >= exclude_newer.as_millisecond() => { trace!( "Excluding `{}` (uploaded {upload_time}) due to exclude-newer ({exclude_newer})", file.filename diff --git a/crates/uv-settings/src/settings.rs b/crates/uv-settings/src/settings.rs index fc5c735019ae9..76c9a53082a72 100644 --- a/crates/uv-settings/src/settings.rs +++ b/crates/uv-settings/src/settings.rs @@ -525,25 +525,6 @@ pub struct ResolverInstallerOptions { pub no_binary_package: Option>, } -impl ResolverInstallerOptions { - /// Recompute any relative exclude-newer values against the current time. - #[must_use] - pub fn recompute_exclude_newer(mut self) -> Self { - let exclude_newer = ExcludeNewer::new( - self.exclude_newer.take(), - self.exclude_newer_package.take().unwrap_or_default(), - ) - .recompute(); - self.exclude_newer = exclude_newer.global; - self.exclude_newer_package = if exclude_newer.package.is_empty() { - None - } else { - Some(exclude_newer.package) - }; - self - } -} - impl From for ResolverInstallerOptions { fn from(value: ResolverInstallerSchema) -> Self { let ResolverInstallerSchema { @@ -2254,8 +2235,10 @@ impl From for ToolOptions { impl From for ToolOptions { fn from(value: ToolOptionsWire) -> Self { let exclude_newer = value.exclude_newer.map(|exclude_newer| { - if exclude_newer.span().is_none() { - ExcludeNewerValue::new(exclude_newer.timestamp(), value.exclude_newer_span) + if let Some(span) = value.exclude_newer_span + && exclude_newer.span().is_none() + { + ExcludeNewerValue::relative(span) } else { exclude_newer } @@ -2295,12 +2278,14 @@ impl From for ToolOptions { impl From for ToolOptionsWire { fn from(value: ToolOptions) -> Self { - let (exclude_newer, exclude_newer_span) = value - .exclude_newer - .map(ExcludeNewerValue::into_parts) - .map_or((None, None), |(timestamp, span)| { - (Some(ExcludeNewerValue::from(timestamp)), span) - }); + let (exclude_newer, exclude_newer_span) = match &value.exclude_newer { + Some(value @ ExcludeNewerValue::Absolute(_)) => (Some(value.clone()), None), + Some(value @ ExcludeNewerValue::Relative(span)) => ( + Some(ExcludeNewerValue::absolute(value.timestamp())), + Some(*span), + ), + None => (None, None), + }; Self { index: value.index, diff --git a/crates/uv/src/commands/pip/latest.rs b/crates/uv/src/commands/pip/latest.rs index f20a3a33c16a7..49ff6ad3faa59 100644 --- a/crates/uv/src/commands/pip/latest.rs +++ b/crates/uv/src/commands/pip/latest.rs @@ -31,7 +31,7 @@ impl LatestClient<'_> { &self, package: &PackageName, index: &IndexUrl, - ) -> Option { + ) -> Option { self.exclude_newer .exclude_newer_package_for_index(package, self.index_locations.exclude_newer_for(index)) } @@ -85,9 +85,7 @@ impl LatestClient<'_> { // Skip distributions uploaded after the cutoff. if let Some(exclude_newer) = &exclude_newer { match file.upload_time_utc_ms.as_ref() { - Some(&upload_time) - if upload_time >= exclude_newer.timestamp_millis() => - { + Some(&upload_time) if upload_time >= exclude_newer.as_millisecond() => { continue; } None => { diff --git a/crates/uv/src/commands/project/tree.rs b/crates/uv/src/commands/project/tree.rs index 15c0541f6c000..7f6d7617c76cc 100644 --- a/crates/uv/src/commands/project/tree.rs +++ b/crates/uv/src/commands/project/tree.rs @@ -229,10 +229,7 @@ pub(crate) async fn tree( .build()?; let download_concurrency = concurrency.downloads_semaphore.clone(); - // Recompute the exclude-newer timestamps from relative spans so that - // `--outdated` judges outdatedness relative to the current moment, - // not the time the lock was originally generated. - let exclude_newer = lock.exclude_newer().recompute(); + let exclude_newer = lock.exclude_newer(); // Initialize the client to fetch the latest version of each package. let client = LatestClient { diff --git a/crates/uv/src/commands/tool/list.rs b/crates/uv/src/commands/tool/list.rs index 9e96223dea25c..9bbd9d1da74e7 100644 --- a/crates/uv/src/commands/tool/list.rs +++ b/crates/uv/src/commands/tool/list.rs @@ -130,13 +130,9 @@ pub(crate) async fn list( let filesystem = filesystem.clone(); async move { let capabilities = IndexCapabilities::default(); - let settings = ResolverInstallerSettings::from( - args.combine( - ResolverInstallerOptions::from(tool.options().clone()) - .recompute_exclude_newer() - .combine(filesystem), - ), - ); + let settings = ResolverInstallerSettings::from(args.combine( + ResolverInstallerOptions::from(tool.options().clone()).combine(filesystem), + )); let interpreter = tool_env.environment().interpreter(); let client = RegistryClientBuilder::new( diff --git a/crates/uv/src/commands/tool/upgrade.rs b/crates/uv/src/commands/tool/upgrade.rs index 0f4206b2a9a18..1e987fdf21e19 100644 --- a/crates/uv/src/commands/tool/upgrade.rs +++ b/crates/uv/src/commands/tool/upgrade.rs @@ -319,7 +319,6 @@ async fn upgrade_tool( // Resolve the appropriate settings, preferring: CLI > receipt > user. let options = args.clone().combine( ResolverInstallerOptions::from(existing_tool_receipt.options().clone()) - .recompute_exclude_newer() .combine(filesystem.clone()), ); let settings = ResolverInstallerSettings::from(options.clone()); diff --git a/crates/uv/tests/it/lock_exclude_newer_relative.rs b/crates/uv/tests/it/lock_exclude_newer_relative.rs index 023b316b89448..bfbe703887b99 100644 --- a/crates/uv/tests/it/lock_exclude_newer_relative.rs +++ b/crates/uv/tests/it/lock_exclude_newer_relative.rs @@ -482,7 +482,7 @@ fn lock_exclude_newer_package_relative() -> Result<()> { Resolved 2 packages in [TIME] "); - // And the `exclude-newer-package` timestamp value in the lockfile should be changed + // The `exclude-newer-package` span is unchanged; the timestamp is a placeholder let lock = context.read("uv.lock"); assert_snapshot!(lock, @r#" version = 1 @@ -492,7 +492,7 @@ fn lock_exclude_newer_package_relative() -> Result<()> { [options] [options.exclude-newer-package] - idna = { timestamp = "2024-05-18T00:00:00Z", span = "P2W" } + idna = { timestamp = "2024-04-17T00:00:00Z", span = "P2W" } [[package]] name = "idna" @@ -1265,8 +1265,9 @@ fn lock_exclude_newer_relative_no_timestamp_in_lockfile() -> Result<()> { let lock = lock.replace("exclude-newer = \"0001-01-01T00:00:00Z\" # This has no effect and is included for backwards compatibility when using relative exclude-newer values.\n", ""); context.temp_dir.child("uv.lock").write_str(&lock)?; - // The lockfile now has no exclude-newer, but `pyproject.toml` still configures one, - // so the resolver detects "addition of global exclude newer" and re-resolves. + // The lockfile now has no exclude-newer timestamp, but the span is still present. + // Since the span matches the `pyproject.toml` configuration, the lockfile is still + // treated as valid — the missing timestamp alone does not trigger re-resolution. uv_snapshot!(context.filters(), context .lock() .env_remove(EnvVars::UV_EXCLUDE_NEWER) From cb3c3108cb1cd0fb75ee02af398ebf4814813567 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 21 Apr 2026 10:51:03 -0400 Subject: [PATCH 43/70] Bump idna version in snapshot (#19097) I'll fix this for real separately, but let's unblock CI. --- crates/uv/tests/it/lock.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index 5455fd6e222c6..f6c06dcaf9516 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -33478,11 +33478,11 @@ fn lock_exclude_newer_package_disable() -> Result<()> { [[package]] name = "idna" - version = "3.11" + version = "3.12" source = { registry = "https://pypi.org/simple" } - sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } + sdist = { url = "https://files.pythonhosted.org/packages/22/12/2948fbe5513d062169bd91f7d7b1cd97bc8894f32946b71fa39f6e63ca0c/idna-3.12.tar.gz", hash = "sha256:724e9952cc9e2bd7550ea784adb098d837ab5267ef67a1ab9cf7846bdbdd8254", size = 194350, upload-time = "2026-04-21T13:32:48.916Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, + { url = "https://files.pythonhosted.org/packages/53/b2/acc33950394b3becb2b664741a0c0889c7ef9f9ffbfa8d47eddb53a50abd/idna-3.12-py3-none-any.whl", hash = "sha256:60ffaa1858fac94c9c124728c24fcde8160f3fb4a7f79aa8cdd33a9d1af60a67", size = 68634, upload-time = "2026-04-21T13:32:47.403Z" }, ] [[package]] From 45bf2f9ea6131c90f85678e7b393f7be97e19239 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 21 Apr 2026 12:12:00 -0400 Subject: [PATCH 44/70] Use the newly stabilized `<[T]>::as_array::()` (#19098) Co-authored-by: Claude --- crates/uv-client/src/cached_client.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/uv-client/src/cached_client.rs b/crates/uv-client/src/cached_client.rs index eda7c7fc6782f..8a85d4b5b69f5 100644 --- a/crates/uv-client/src/cached_client.rs +++ b/crates/uv-client/src/cached_client.rs @@ -981,9 +981,10 @@ impl DataWithCachePolicy { ); return Err(ErrorKind::ArchiveRead(msg).into()); }; - let cache_policy_len_bytes = <[u8; 8]>::try_from(&bytes[cache_policy_len_start..]) + let cache_policy_len_bytes = bytes[cache_policy_len_start..] + .as_array::<8>() .expect("cache policy length is 8 bytes"); - let len_u64 = u64::from_le_bytes(cache_policy_len_bytes); + let len_u64 = u64::from_le_bytes(*cache_policy_len_bytes); let Ok(len_usize) = usize::try_from(len_u64) else { let msg = format!( "data-with-cache-policy has cache policy length of {len_u64}, \ From 732ecd8828d43227e1929b54ccf9ef012aef01c7 Mon Sep 17 00:00:00 2001 From: Aria Desires Date: Tue, 21 Apr 2026 13:12:35 -0400 Subject: [PATCH 45/70] Support `uv lock` on a `pyproject.toml` that only contains dependency-groups (#19087) ## Summary These special kinds of pyproject.tomls are blessed by the standards gods. Initially we only supported it in `uv pip install` but at some point `uv sync` started supporting it. However `uv lock` would still hard error which is definitely "everything is wrong". ## Test Plan Included is 3 tests: * showing sync always worked * showing lock was broken and now works * showing that `conflicts` currently doesn't work (because internally we always expect a Conflict to include a PackageName) --- crates/uv/src/commands/project/lock.rs | 9 ++- crates/uv/tests/it/lock.rs | 98 +++++++++++++++++++++++++- crates/uv/tests/it/lock_conflict.rs | 63 +++++++++++++++++ crates/uv/tests/it/sync.rs | 81 +++++++++++++++++++++ 4 files changed, 247 insertions(+), 4 deletions(-) diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index c44703d0eef65..e2d23c0d2b024 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -40,7 +40,9 @@ use uv_types::{ BuildContext, BuildIsolation, EmptyInstalledPackages, HashStrategy, SourceTreeEditablePolicy, }; use uv_warnings::{warn_user, warn_user_once}; -use uv_workspace::{DiscoveryOptions, Editability, Workspace, WorkspaceCache, WorkspaceMember}; +use uv_workspace::{ + DiscoveryOptions, Editability, VirtualProject, WorkspaceCache, WorkspaceMember, +}; use crate::commands::pip::loggers::{DefaultResolveLogger, ResolveLogger, SummaryResolveLogger}; use crate::commands::project::lock_target::LockTarget; @@ -130,8 +132,9 @@ pub(crate) async fn lock( LockTarget::Script(script) } else { workspace = - Workspace::discover(project_dir, &DiscoveryOptions::default(), workspace_cache).await?; - LockTarget::Workspace(&workspace) + VirtualProject::discover(project_dir, &DiscoveryOptions::default(), workspace_cache) + .await?; + LockTarget::Workspace(workspace.workspace()) }; // Determine the lock mode. diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index f6c06dcaf9516..cc4ca268c175f 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -18630,6 +18630,101 @@ fn lock_non_project_group() -> Result<()> { Ok(()) } +/// Lock a non-project workspace root with `dependency-groups`. +/// +/// Here instead of leaning on `tool.uv.workspace` we use the +/// officially blessed "pyproject.toml with no `[project]` that +/// declares `[dependency-groups]`". +#[test] +fn lock_non_project_group_standard() -> Result<()> { + let context = uv_test::test_context!("3.10"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [dependency-groups] + lint = ["iniconfig"] + dev = ["typing-extensions"] + "#, + )?; + + uv_snapshot!(context.filters(), context.lock(), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: No `requires-python` value found in the workspace. Defaulting to `>=3.10`. + Resolved 2 packages in [TIME] + "); + + let lock = context.read("uv.lock"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + lock, @r#" + version = 1 + revision = 3 + requires-python = ">=3.10" + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [manifest] + + [manifest.dependency-groups] + dev = [{ name = "typing-extensions" }] + lint = [{ name = "iniconfig" }] + + [[package]] + name = "iniconfig" + version = "2.0.0" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646, upload-time = "2023-01-07T11:08:11.254Z" } + wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892, upload-time = "2023-01-07T11:08:09.864Z" }, + ] + + [[package]] + name = "typing-extensions" + version = "4.10.0" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/16/3a/0d26ce356c7465a19c9ea8814b960f8a36c3b0d07c323176620b7b483e44/typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb", size = 77558, upload-time = "2024-02-25T22:12:49.693Z" } + wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/de/dc04a3ea60b22624b51c703a84bbe0184abcd1d0b9bc8074b5d6b7ab90bb/typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475", size = 33926, upload-time = "2024-02-25T22:12:47.72Z" }, + ] + "# + ); + }); + + // Re-run with `--locked`. + uv_snapshot!(context.filters(), context.lock().arg("--locked"), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: No `requires-python` value found in the workspace. Defaulting to `>=3.10`. + Resolved 2 packages in [TIME] + "); + + // Re-run with `--offline`. We shouldn't need a network connection to validate an + // already-correct lockfile with immutable metadata. + uv_snapshot!(context.filters(), context.lock().arg("--locked").arg("--offline").arg("--no-cache"), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: No `requires-python` value found in the workspace. Defaulting to `>=3.10`. + Resolved 2 packages in [TIME] + "); + + Ok(()) +} + /// Lock a non-project workspace root with `tool.uv.sources`. #[test] fn lock_non_project_sources() -> Result<()> { @@ -19875,7 +19970,8 @@ fn lock_explicit_default_index() -> Result<()> { ----- stderr ----- DEBUG uv [VERSION] ([COMMIT] DATE) - DEBUG Found workspace root: `[TEMP_DIR]/` + DEBUG Found project root: `[TEMP_DIR]/` + DEBUG No workspace root found, using project root DEBUG No Python version file found in workspace: [TEMP_DIR]/ DEBUG Using Python request `>=3.12` from `requires-python` metadata DEBUG Checking for Python environment at: `.venv` diff --git a/crates/uv/tests/it/lock_conflict.rs b/crates/uv/tests/it/lock_conflict.rs index 2a029001702c8..4120066f2c109 100644 --- a/crates/uv/tests/it/lock_conflict.rs +++ b/crates/uv/tests/it/lock_conflict.rs @@ -2259,6 +2259,69 @@ fn group_default() -> Result<()> { Ok(()) } +/// This tests conflicting groups in a virtual pyproject.toml +/// +/// (One with no `[project]` which is allowed for specifically dependency-groups). +/// Currently this isn't supported, as we require a `PackageName` when representing +/// Conflicts internally. +#[test] +fn group_virtual() -> Result<()> { + let context = uv_test::test_context!("3.12"); + + // First we test that resolving with two groups that have + // conflicting dependencies fails. + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [dependency-groups] + group1 = ["sortedcontainers==2.3.0"] + group2 = ["sortedcontainers==2.4.0"] + "#, + )?; + + uv_snapshot!(context.filters(), context.lock(), @" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + warning: No `requires-python` value found in the workspace. Defaulting to `>=3.12`. + × No solution found when resolving dependencies: + ╰─▶ Because you require sortedcontainers==2.3.0 and sortedcontainers==2.4.0, we can conclude that your requirements are unsatisfiable. + "); + + // And now with the same group configuration, we tell uv about + // the conflicting groups, which forces it to resolve each in + // their own fork. + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [tool.uv] + conflicts = [ + [ + { group = "group1" }, + { group = "group2" }, + ], + ] + + [dependency-groups] + group1 = ["sortedcontainers==2.3.0"] + group2 = ["sortedcontainers==2.4.0"] + "#, + )?; + + uv_snapshot!(context.filters(), context.lock(), @r#" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Expected `package` field in conflicting entry: { group = "group1" } + "#); + + Ok(()) +} + /// Ref: #[test] fn groups_respect_supported_environments_when_filtering_wheels() -> Result<()> { diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index ef4b0f3417803..f304ae0189a44 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -1329,6 +1329,87 @@ fn sync_non_project_frozen() -> Result<()> { Ok(()) } +/// Sync dependency groups in a non-project workspace root. +/// +/// Here instead of leaning on `tool.uv.workspace` we use the +/// officially blessed "pyproject.toml with no `[project]` that +/// declares `[dependency-groups]`". +#[test] +fn sync_non_project_group_standard() -> Result<()> { + let context = uv_test::test_context!("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [dependency-groups] + dev = ["anyio"] + bar = ["typing-extensions"] + "#, + )?; + + context + .temp_dir + .child("src") + .child("albatross") + .child("__init__.py") + .touch()?; + + uv_snapshot!(context.filters(), context.sync(), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: No `requires-python` value found in the workspace. Defaulting to `>=3.12`. + Resolved 4 packages in [TIME] + Prepared 3 packages in [TIME] + Installed 3 packages in [TIME] + + anyio==4.3.0 + + idna==3.6 + + sniffio==1.3.1 + "); + + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("bar"), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: No `requires-python` value found in the workspace. Defaulting to `>=3.12`. + Resolved 4 packages in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + typing-extensions==4.10.0 + "); + + uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("bar"), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: No `requires-python` value found in the workspace. Defaulting to `>=3.12`. + Resolved 4 packages in [TIME] + Uninstalled 3 packages in [TIME] + - anyio==4.3.0 + - idna==3.6 + - sniffio==1.3.1 + "); + + uv_snapshot!(context.filters(), context.sync().arg("--no-default-groups"), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: No `requires-python` value found in the workspace. Defaulting to `>=3.12`. + Resolved 4 packages in [TIME] + Uninstalled 1 package in [TIME] + - typing-extensions==4.10.0 + "); + Ok(()) +} + /// Sync dependency groups in a non-project workspace root. #[test] fn sync_non_project_group() -> Result<()> { From 83d15da7d4fcf8f016d631e4ed7685231bddb5ee Mon Sep 17 00:00:00 2001 From: Tomasz Kramkowski Date: Tue, 21 Apr 2026 13:43:22 -0400 Subject: [PATCH 46/70] Ensure uv invocations of git do not inherit repository location environment variables (#19088) ## Summary These variables can break uv's use of git when set. Fixes #19008. Closes #19042. ## Test Plan Added a test + existing coverage. --- crates/uv-configuration/src/vcs.rs | 5 +- crates/uv-git/src/git.rs | 76 +++++++++++++++----------- crates/uv-static/src/env_vars.rs | 6 ++ crates/uv/src/commands/project/init.rs | 11 ++-- crates/uv/tests/it/pip_install.rs | 30 ++++++++++ 5 files changed, 91 insertions(+), 37 deletions(-) diff --git a/crates/uv-configuration/src/vcs.rs b/crates/uv-configuration/src/vcs.rs index b40261a6054fc..1a41a7fb0438b 100644 --- a/crates/uv-configuration/src/vcs.rs +++ b/crates/uv-configuration/src/vcs.rs @@ -1,6 +1,6 @@ use std::io::Write; use std::path::{Path, PathBuf}; -use std::process::{Command, Stdio}; +use std::process::Stdio; use serde::Deserialize; use uv_git::GIT; @@ -39,7 +39,8 @@ impl VersionControlSystem { return Err(VersionControlError::GitNotInstalled); }; - let output = Command::new(git) + let output = git + .build_command() .arg("init") .current_dir(path) .stdout(Stdio::piped()) diff --git a/crates/uv-git/src/git.rs b/crates/uv-git/src/git.rs index db18b58e20562..36c498f3ed0db 100644 --- a/crates/uv-git/src/git.rs +++ b/crates/uv-git/src/git.rs @@ -37,12 +37,32 @@ pub enum GitError { TransportNotAllowed, } -/// A global cache of the result of `which git`. -pub static GIT: LazyLock> = LazyLock::new(|| { - which::which("git").map_err(|err| match err { +/// A global cache of the result of `which git` as a command +/// +/// Caching the command allows us to avoid needing to remove environment +/// variables everywhere. +pub static GIT: LazyLock> = LazyLock::new(|| { + let path = which::which("git").map_err(|err| match err { which::Error::CannotFindBinaryPath => GitError::GitNotFound, err => GitError::Other(err), - }) + })?; + + let mut cmd = ProcessBuilder::new(path); + + // Certain git environment variables never make sense to inherit because + // they affect what the current command will act on. + + // This can cause problems if for example uv is ran by git (for example, the + // `exec` command in `git rebase`), the GIT_DIR is set by git and will point + // to the wrong location (this takes precedence over the cwd). + cmd.env_remove(EnvVars::GIT_DIR) + .env_remove(EnvVars::GIT_WORK_TREE) + .env_remove(EnvVars::GIT_INDEX_FILE) + .env_remove(EnvVars::GIT_OBJECT_DIRECTORY) + .env_remove(EnvVars::GIT_ALTERNATE_OBJECT_DIRECTORIES) + .env_remove(EnvVars::GIT_COMMON_DIR); + + Ok(cmd) }); /// Strategy when fetching refspecs for a [`GitReference`] @@ -166,7 +186,8 @@ impl GitRepository { /// Opens an existing Git repository at `path`. pub(crate) fn open(path: &Path) -> Result { // Make sure there is a Git repository at the specified path. - ProcessBuilder::new(GIT.as_ref()?) + GIT.as_ref() + .cloned()? .arg("rev-parse") .cwd(path) .exec_with_output()?; @@ -185,7 +206,8 @@ impl GitRepository { // opts.external_template(false); // Initialize the repository. - ProcessBuilder::new(GIT.as_ref()?) + GIT.as_ref() + .cloned()? .arg("init") .cwd(path) .exec_with_output()?; @@ -197,7 +219,9 @@ impl GitRepository { /// Parses the object ID of the given `refname`. fn rev_parse(&self, refname: &str) -> Result { - let result = ProcessBuilder::new(GIT.as_ref()?) + let result = GIT + .as_ref() + .cloned()? .arg("rev-parse") .arg(refname) .cwd(&self.path) @@ -359,7 +383,9 @@ impl GitDatabase { /// Get a short OID for a `revision`, usually 7 chars or more if ambiguous. pub(crate) fn to_short_id(&self, revision: GitOid) -> Result { - let output = ProcessBuilder::new(GIT.as_ref()?) + let output = GIT + .as_ref() + .cloned()? .arg("rev-parse") .arg("--short") .arg(revision.as_str()) @@ -416,7 +442,9 @@ impl GitCheckout { // Perform a local clone of the repository, which will attempt to use // hardlinks to set up the repository. This should speed up the clone operation // quite a bit if it works. - let res = ProcessBuilder::new(GIT.as_ref()?) + let res = GIT + .as_ref() + .cloned()? .arg("clone") .arg("--local") // Make sure to pass the local file path and not a file://... url. If given a url, @@ -429,7 +457,8 @@ impl GitCheckout { if let Err(e) = res { debug!("Cloning git repo with --local failed, retrying without hardlinks: {e}"); - ProcessBuilder::new(GIT.as_ref()?) + GIT.as_ref() + .cloned()? .arg("clone") .arg("--no-hardlinks") .arg(database.repo.path.simplified_display().to_string()) @@ -490,7 +519,8 @@ impl GitCheckout { debug!("Reset {} to {}", self.repo.path.display(), self.revision); // Perform the hard reset. - ProcessBuilder::new(GIT.as_ref()?) + GIT.as_ref() + .cloned()? .arg("reset") .arg("--hard") .arg(self.revision.as_str()) @@ -499,7 +529,8 @@ impl GitCheckout { .exec_with_output()?; // Update submodules (`git submodule update --recursive`). - ProcessBuilder::new(GIT.as_ref()?) + GIT.as_ref() + .cloned()? .arg("submodule") .arg("update") .arg("--recursive") @@ -690,7 +721,7 @@ fn fetch_with_cli( disable_ssl: bool, offline: bool, ) -> Result<()> { - let mut cmd = ProcessBuilder::new(GIT.as_ref()?); + let mut cmd = GIT.as_ref().cloned()?; // Disable interactive prompts in the terminal, as they'll be erased by the progress bar // animation and the process will "hang". Interactive prompts via the GUI like `SSH_ASKPASS` // are still usable. @@ -712,17 +743,6 @@ fn fetch_with_cli( .arg("--update-head-ok") // see discussion in #2078 .arg(url.as_str()) .args(refspecs) - // If cargo is run by git (for example, the `exec` command in `git - // rebase`), the GIT_DIR is set by git and will point to the wrong - // location (this takes precedence over the cwd). Make sure this is - // unset so git will look at cwd for the repo. - .env_remove(EnvVars::GIT_DIR) - // The reset of these may not be necessary, but I'm including them - // just to be extra paranoid and avoid any issues. - .env_remove(EnvVars::GIT_WORK_TREE) - .env_remove(EnvVars::GIT_INDEX_FILE) - .env_remove(EnvVars::GIT_OBJECT_DIRECTORY) - .env_remove(EnvVars::GIT_ALTERNATE_OBJECT_DIRECTORIES) .cwd(&repo.path); // We capture the output to avoid streaming it to the user's console during clones. @@ -754,7 +774,7 @@ pub static GIT_LFS: LazyLock> = LazyLock::new(|| { return Err(anyhow!("Git LFS extension has been forcefully disabled.")); } - let mut cmd = ProcessBuilder::new(GIT.as_ref()?); + let mut cmd = GIT.as_ref()?.clone(); cmd.arg("lfs"); // Run a simple command to verify LFS is installed @@ -786,12 +806,6 @@ fn fetch_lfs( cmd.arg("fetch") .arg(url.as_str()) .arg(revision.as_str()) - // These variables are unset for the same reason as in `fetch_with_cli`. - .env_remove(EnvVars::GIT_DIR) - .env_remove(EnvVars::GIT_WORK_TREE) - .env_remove(EnvVars::GIT_INDEX_FILE) - .env_remove(EnvVars::GIT_OBJECT_DIRECTORY) - .env_remove(EnvVars::GIT_ALTERNATE_OBJECT_DIRECTORIES) // We should not support requesting LFS artifacts with skip smudge being set. // While this may not be necessary, it's added to avoid any potential future issues. .env_remove(EnvVars::GIT_LFS_SKIP_SMUDGE) diff --git a/crates/uv-static/src/env_vars.rs b/crates/uv-static/src/env_vars.rs index 391424416729e..13ffa8bb89ead 100644 --- a/crates/uv-static/src/env_vars.rs +++ b/crates/uv-static/src/env_vars.rs @@ -929,6 +929,12 @@ impl EnvVars { #[attr_added_in("0.4.29")] pub const GIT_CEILING_DIRECTORIES: &'static str = "GIT_CEILING_DIRECTORIES"; + /// Cleared for uv's git invocations to ensure git behaves correctly in + /// spite of an odd environment. + #[attr_hidden] + #[attr_added_in("next release")] + pub const GIT_COMMON_DIR: &'static str = "GIT_COMMON_DIR"; + /// Indicates that the current process is running in GitHub Actions. /// /// `uv publish` may attempt trusted publishing flows when set diff --git a/crates/uv/src/commands/project/init.rs b/crates/uv/src/commands/project/init.rs index bf3d21f3f9254..64db72d0e580a 100644 --- a/crates/uv/src/commands/project/init.rs +++ b/crates/uv/src/commands/project/init.rs @@ -1,7 +1,7 @@ use std::fmt::Write; use std::iter; use std::path::{Path, PathBuf}; -use std::process::{Command, Stdio}; +use std::process::Stdio; use std::str::FromStr; use anyhow::{Context, Result, anyhow, bail}; @@ -1299,7 +1299,8 @@ fn detect_git_repository(path: &Path) -> GitDiscoveryResult { let Ok(git) = GIT.as_ref() else { return GitDiscoveryResult::NoGit; }; - let Ok(output) = Command::new(git) + let Ok(output) = git + .build_command() .arg("rev-parse") .arg("--is-inside-work-tree") .env(EnvVars::LC_ALL, "C") @@ -1402,7 +1403,8 @@ fn get_author_from_git(path: &Path) -> Result { let mut name = None; let mut email = None; - let output = Command::new(git) + let output = git + .build_command() .arg("config") .arg("--get") .arg("user.name") @@ -1414,7 +1416,8 @@ fn get_author_from_git(path: &Path) -> Result { name = Some(String::from_utf8_lossy(&output.stdout).trim().to_string()); } - let output = Command::new(git) + let output = git + .build_command() .arg("config") .arg("--get") .arg("user.email") diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index aec16cc03f397..1fad6fde9e617 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -11512,6 +11512,36 @@ fn pip_install_no_sources_package() -> Result<()> { Ok(()) } +/// Certain git environment variables should not be forwarded to git +#[test] +#[cfg(feature = "test-git")] +fn install_git_with_git_envs_set() { + let context = uv_test::test_context!(DEFAULT_PYTHON_VERSION); + + uv_snapshot!(context.filters(), context + .pip_install() + .arg("uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage") + .env(EnvVars::GIT_DIR, "/nonexistent") + .env(EnvVars::GIT_COMMON_DIR, "/nonexistent") + .env(EnvVars::GIT_WORK_TREE, "/nonexistent") + .env(EnvVars::GIT_INDEX_FILE, "/nonexistent") + .env(EnvVars::GIT_OBJECT_DIRECTORY, "/nonexistent") + .env(EnvVars::GIT_ALTERNATE_OBJECT_DIRECTORIES, "/nonexistent"), + @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + uv-public-pypackage==0.1.0 (from git+https://github.com/astral-test/uv-public-pypackage@b270df1a2fb5d012294e9aaf05e7e0bab1e6a389) + "); + + context.assert_installed("uv_public_pypackage", "0.1.0"); +} + #[test] fn unsupported_git_scheme() { let context = uv_test::test_context!("3.12"); From 8aec20646093221b42ac080efe13365ce20b4eaf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 13:43:59 -0400 Subject: [PATCH 47/70] Update pypa/gh-action-pypi-publish action to v1.14.0 (#19067) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [pypa/gh-action-pypi-publish](https://redirect.github.com/pypa/gh-action-pypi-publish) | action | minor | `v1.13.0` → `v1.14.0` | --- ### Release Notes
pypa/gh-action-pypi-publish (pypa/gh-action-pypi-publish) ### [`v1.14.0`](https://redirect.github.com/pypa/gh-action-pypi-publish/releases/tag/v1.14.0) [Compare Source](https://redirect.github.com/pypa/gh-action-pypi-publish/compare/v1.13.0...v1.14.0)

Audit your supply chain regularly!

#### ✨ What's Changed The main change in this release is that `verbose` and `print-hash` inputs are now on by default. This was contributed by [@​whitequark](https://redirect.github.com/whitequark)[💰](https://redirect.github.com/sponsors/whitequark) in [#​397](https://redirect.github.com/pypa/gh-action-pypi-publish/issues/397). #### 📝 Docs [@​woodruffw](https://redirect.github.com/woodruffw)[💰](https://redirect.github.com/sponsors/woodruffw) updated the mentions of PEP 740 to stop implying that it might be experimental (it hasn't been for quite a while!) in [#​388](https://redirect.github.com/pypa/gh-action-pypi-publish/issues/388) and [@​him2him2](https://redirect.github.com/him2him2)[💰](https://redirect.github.com/sponsors/him2him2) brushed up some grammar in the README and SECURITY docs via [#​395](https://redirect.github.com/pypa/gh-action-pypi-publish/issues/395). #### 🛠️ Internal Updates [@​woodruffw](https://redirect.github.com/woodruffw)[💰](https://redirect.github.com/sponsors/woodruffw) bumped `sigstore` and `pypi-attestations` in the lock file ([#​391](https://redirect.github.com/pypa/gh-action-pypi-publish/issues/391)) and [@​webknjaz](https://redirect.github.com/webknjaz)[💰][GH Sponsors URL] added infra for using type annotations in the project ([#​381](https://redirect.github.com/pypa/gh-action-pypi-publish/issues/381)). #### 💪 New Contributors - [@​him2him2](https://redirect.github.com/him2him2) made their first contribution in [#​395](https://redirect.github.com/pypa/gh-action-pypi-publish/issues/395) - [@​whitequark](https://redirect.github.com/whitequark) made their first contribution in [#​397](https://redirect.github.com/pypa/gh-action-pypi-publish/issues/397) **🪞 Full Diff**: **🧔‍♂️ Release Manager:** [@​webknjaz](https://redirect.github.com/sponsors/webknjaz) [🇺🇦](https://stand-with-ukraine.pp.ua) **🙏 Special Thanks** to [@​facutuesca](https://redirect.github.com/facutuesca)[💰](https://redirect.github.com/sponsors/facutuesca) and [@​woodruffw](https://redirect.github.com/woodruffw)[💰](https://redirect.github.com/sponsors/woodruffw) for helping maintain this project when [I][GH Sponsors URL] can't! **💬 Discuss** [on Bluesky 🦋](https://bsky.app/profile/webknjaz.me/post/3mivwsz3qzk2e), [on Mastodon 🐘](https://mastodon.social/@​webknjaz/116363779997051422) and [on GitHub][release discussion]. [![GH Sponsors badge]][GH Sponsors URL] [GH Sponsors badge]: https://img.shields.io/badge/%40webknjaz-transparent?logo=githubsponsors&logoColor=%23EA4AAA&label=Sponsor&color=2a313c [GH Sponsors URL]: https://redirect.github.com/sponsors/webknjaz [release discussion]: https://redirect.github.com/pypa/gh-action-pypi-publish/discussions/404
--- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/astral-sh/uv). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7be34842b729e..81abf30ebd794 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -320,7 +320,7 @@ jobs: ../uv build - name: "Publish astral-test-pypa-gh-action" - uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 with: # With this GitHub action, we can't do as rigid checks as with our custom Python script, so we publish more # leniently From 95cbb999e6286c20927b4bce991b60dac18d9615 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 21 Apr 2026 13:45:14 -0400 Subject: [PATCH 48/70] Hoist `WorkspacePython` resolution out of `ProjectInterpreter::discover` (#19103) Claude actually did fine on the commit here ("hoist" is my new favorite refactor term) > `ProjectInterpreter::discover` previously called `WorkspacePython::from_request` internally, so `ProjectEnvironment::get_or_init` had to either resolve the request a second time to decide upgradeability or accept a stale check against the CLI argument only. > > Make callers resolve `WorkspacePython` once and pass it in. `discover` now takes the resolved struct directly and no longer needs `project_dir` or `no_config`. Needed for https://github.com/astral-sh/uv/pull/19102#discussion_r3119209117 Co-authored-by: Claude --- crates/uv/src/commands/project/add.rs | 14 ++++-- crates/uv/src/commands/project/audit.rs | 46 +++++++++++-------- crates/uv/src/commands/project/export.rs | 48 ++++++++++++-------- crates/uv/src/commands/project/lock.rs | 47 +++++++++++-------- crates/uv/src/commands/project/mod.rs | 27 +++++------ crates/uv/src/commands/project/remove.rs | 14 ++++-- crates/uv/src/commands/project/tree.rs | 47 +++++++++++-------- crates/uv/src/commands/project/version.rs | 30 ++++++++---- crates/uv/src/commands/workspace/metadata.rs | 19 +++++--- 9 files changed, 178 insertions(+), 114 deletions(-) diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index 60ffe0665a8c1..94a13cd5be466 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -51,7 +51,7 @@ use crate::commands::project::lock::LockMode; use crate::commands::project::lock_target::LockTarget; use crate::commands::project::{ PlatformState, ProjectEnvironment, ProjectError, ProjectInterpreter, ScriptInterpreter, - UniversalState, default_dependency_groups, init_script_python_requirement, + UniversalState, WorkspacePython, default_dependency_groups, init_script_python_requirement, }; use crate::commands::reporters::{PythonDownloadReporter, ResolverReporter}; use crate::commands::{ExitStatus, ScriptPath, diagnostics, project}; @@ -273,17 +273,23 @@ pub(crate) async fn add( if frozen.is_some() || no_sync { // Discover the interpreter. + let workspace_python = WorkspacePython::from_request( + python.as_deref().map(PythonRequest::parse), + Some(project.workspace()), + &defaulted_groups, + project_dir, + no_config, + ) + .await?; let interpreter = ProjectInterpreter::discover( project.workspace(), - project_dir, &defaulted_groups, - python.as_deref().map(PythonRequest::parse), + workspace_python, &client_builder, python_preference, python_downloads, &install_mirrors, false, - no_config, active, cache, printer, diff --git a/crates/uv/src/commands/project/audit.rs b/crates/uv/src/commands/project/audit.rs index 1ec5aec7ae515..ffbb409039e01 100644 --- a/crates/uv/src/commands/project/audit.rs +++ b/crates/uv/src/commands/project/audit.rs @@ -11,7 +11,7 @@ use crate::commands::project::default_dependency_groups; use crate::commands::project::lock::{LockMode, LockOperation}; use crate::commands::project::lock_target::LockTarget; use crate::commands::project::{ - ProjectError, ProjectInterpreter, ScriptInterpreter, UniversalState, + ProjectError, ProjectInterpreter, ScriptInterpreter, UniversalState, WorkspacePython, }; use crate::commands::reporters::AuditReporter; use crate::printer::Printer; @@ -114,24 +114,32 @@ pub(crate) async fn audit( ) .await? .into_interpreter(), - LockTarget::Workspace(workspace) => ProjectInterpreter::discover( - workspace, - project_dir, - &groups, - None, - &client_builder, - python_preference, - python_downloads, - &install_mirrors, - false, - no_config, - Some(false), - &cache, - printer, - preview, - ) - .await? - .into_interpreter(), + LockTarget::Workspace(workspace) => { + let workspace_python = WorkspacePython::from_request( + None, + Some(workspace), + &groups, + project_dir, + no_config, + ) + .await?; + ProjectInterpreter::discover( + workspace, + &groups, + workspace_python, + &client_builder, + python_preference, + python_downloads, + &install_mirrors, + false, + Some(false), + &cache, + printer, + preview, + ) + .await? + .into_interpreter() + } }) }; diff --git a/crates/uv/src/commands/project/export.rs b/crates/uv/src/commands/project/export.rs index c1a1e2aee60ba..11d989dfa1292 100644 --- a/crates/uv/src/commands/project/export.rs +++ b/crates/uv/src/commands/project/export.rs @@ -27,8 +27,8 @@ use crate::commands::project::install_target::InstallTarget; use crate::commands::project::lock::{LockMode, LockOperation}; use crate::commands::project::lock_target::LockTarget; use crate::commands::project::{ - ProjectError, ProjectInterpreter, ScriptInterpreter, UniversalState, default_dependency_groups, - detect_conflicts, + ProjectError, ProjectInterpreter, ScriptInterpreter, UniversalState, WorkspacePython, + default_dependency_groups, detect_conflicts, }; use crate::commands::{ExitStatus, OutputWriter, diagnostics}; use crate::printer::Printer; @@ -163,24 +163,32 @@ pub(crate) async fn export( ) .await? .into_interpreter(), - ExportTarget::Project(project) => ProjectInterpreter::discover( - project.workspace(), - project_dir, - &groups, - python.as_deref().map(PythonRequest::parse), - &client_builder, - python_preference, - python_downloads, - &install_mirrors, - false, - no_config, - Some(false), - cache, - printer, - preview, - ) - .await? - .into_interpreter(), + ExportTarget::Project(project) => { + let workspace_python = WorkspacePython::from_request( + python.as_deref().map(PythonRequest::parse), + Some(project.workspace()), + &groups, + project_dir, + no_config, + ) + .await?; + ProjectInterpreter::discover( + project.workspace(), + &groups, + workspace_python, + &client_builder, + python_preference, + python_downloads, + &install_mirrors, + false, + Some(false), + cache, + printer, + preview, + ) + .await? + .into_interpreter() + } }) }; diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index e2d23c0d2b024..9730099ae4c45 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -48,7 +48,7 @@ use crate::commands::pip::loggers::{DefaultResolveLogger, ResolveLogger, Summary use crate::commands::project::lock_target::LockTarget; use crate::commands::project::{ MissingLockfileSource, ProjectError, ProjectInterpreter, ScriptInterpreter, UniversalState, - init_script_python_requirement, script_extra_build_requires, + WorkspacePython, init_script_python_requirement, script_extra_build_requires, }; use crate::commands::reporters::{PythonDownloadReporter, ResolverReporter}; use crate::commands::{ExitStatus, ScriptPath, diagnostics, pip}; @@ -143,25 +143,34 @@ pub(crate) async fn lock( LockMode::Frozen(frozen_source.into()) } else { interpreter = match target { - LockTarget::Workspace(workspace) => ProjectInterpreter::discover( - workspace, - project_dir, + LockTarget::Workspace(workspace) => { // Don't enable any groups' requires-python for interpreter discovery - &DependencyGroupsWithDefaults::none(), - python.as_deref().map(PythonRequest::parse), - &client_builder, - python_preference, - python_downloads, - &install_mirrors, - false, - no_config, - Some(false), - cache, - printer, - preview, - ) - .await? - .into_interpreter(), + let groups = DependencyGroupsWithDefaults::none(); + let workspace_python = WorkspacePython::from_request( + python.as_deref().map(PythonRequest::parse), + Some(workspace), + &groups, + project_dir, + no_config, + ) + .await?; + ProjectInterpreter::discover( + workspace, + &groups, + workspace_python, + &client_builder, + python_preference, + python_downloads, + &install_mirrors, + false, + Some(false), + cache, + printer, + preview, + ) + .await? + .into_interpreter() + } LockTarget::Script(script) => ScriptInterpreter::discover( script.into(), python.as_deref().map(PythonRequest::parse), diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index 49d46116b6a2c..8bda9e3c04ea2 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -953,33 +953,23 @@ impl ProjectInterpreter { /// Discover the interpreter to use in the current [`Workspace`]. pub(crate) async fn discover( workspace: &Workspace, - project_dir: &Path, groups: &DependencyGroupsWithDefaults, - python_request: Option, + workspace_python: WorkspacePython, client_builder: &BaseClientBuilder<'_>, python_preference: PythonPreference, python_downloads: PythonDownloads, install_mirrors: &PythonInstallMirrors, keep_incompatible: bool, - no_config: bool, active: Option, cache: &Cache, printer: Printer, preview: Preview, ) -> Result { - // Resolve the Python request and requirement for the workspace. let WorkspacePython { source, python_request, requires_python, - } = WorkspacePython::from_request( - python_request, - Some(workspace), - groups, - project_dir, - no_config, - ) - .await?; + } = workspace_python; // Read from the virtual environment first. let root = workspace.venv(active); @@ -1426,17 +1416,24 @@ impl ProjectEnvironment { .as_ref() .is_none_or(|request| !request.includes_patch()); + let workspace_python = WorkspacePython::from_request( + python, + Some(workspace), + groups, + workspace.install_path().as_ref(), + no_config, + ) + .await?; + match ProjectInterpreter::discover( workspace, - workspace.install_path().as_ref(), groups, - python, + workspace_python, client_builder, python_preference, python_downloads, install_mirrors, no_sync, - no_config, active, cache, printer, diff --git a/crates/uv/src/commands/project/remove.rs b/crates/uv/src/commands/project/remove.rs index 04c01f4e9c0f9..9901448ef499d 100644 --- a/crates/uv/src/commands/project/remove.rs +++ b/crates/uv/src/commands/project/remove.rs @@ -32,7 +32,7 @@ use crate::commands::project::lock::LockMode; use crate::commands::project::lock_target::LockTarget; use crate::commands::project::{ ProjectEnvironment, ProjectError, ProjectInterpreter, ScriptInterpreter, UniversalState, - default_dependency_groups, + WorkspacePython, default_dependency_groups, }; use crate::commands::{ExitStatus, diagnostics, project}; use crate::printer::Printer; @@ -212,17 +212,23 @@ pub(crate) async fn remove( RemoveTarget::Project(project) => { if no_sync { // Discover the interpreter. + let workspace_python = WorkspacePython::from_request( + python.as_deref().map(PythonRequest::parse), + Some(project.workspace()), + &groups, + project_dir, + no_config, + ) + .await?; let interpreter = ProjectInterpreter::discover( project.workspace(), - project_dir, &groups, - python.as_deref().map(PythonRequest::parse), + workspace_python, &client_builder, python_preference, python_downloads, &install_mirrors, false, - no_config, active, cache, printer, diff --git a/crates/uv/src/commands/project/tree.rs b/crates/uv/src/commands/project/tree.rs index 7f6d7617c76cc..05d3d71e80b3b 100644 --- a/crates/uv/src/commands/project/tree.rs +++ b/crates/uv/src/commands/project/tree.rs @@ -23,7 +23,8 @@ use crate::commands::pip::resolution_markers; use crate::commands::project::lock::{LockMode, LockOperation}; use crate::commands::project::lock_target::LockTarget; use crate::commands::project::{ - ProjectError, ProjectInterpreter, ScriptInterpreter, UniversalState, default_dependency_groups, + ProjectError, ProjectInterpreter, ScriptInterpreter, UniversalState, WorkspacePython, + default_dependency_groups, }; use crate::commands::reporters::LatestVersionReporter; use crate::commands::{ExitStatus, diagnostics}; @@ -102,24 +103,32 @@ pub(crate) async fn tree( ) .await? .into_interpreter(), - LockTarget::Workspace(workspace) => ProjectInterpreter::discover( - workspace, - project_dir, - &groups, - python.as_deref().map(PythonRequest::parse), - client_builder, - python_preference, - python_downloads, - &install_mirrors, - false, - no_config, - Some(false), - cache, - printer, - preview, - ) - .await? - .into_interpreter(), + LockTarget::Workspace(workspace) => { + let workspace_python = WorkspacePython::from_request( + python.as_deref().map(PythonRequest::parse), + Some(workspace), + &groups, + project_dir, + no_config, + ) + .await?; + ProjectInterpreter::discover( + workspace, + &groups, + workspace_python, + client_builder, + python_preference, + python_downloads, + &install_mirrors, + false, + Some(false), + cache, + printer, + preview, + ) + .await? + .into_interpreter() + } }) }; diff --git a/crates/uv/src/commands/project/version.rs b/crates/uv/src/commands/project/version.rs index 97e9919ac0d08..c85da31c206da 100644 --- a/crates/uv/src/commands/project/version.rs +++ b/crates/uv/src/commands/project/version.rs @@ -34,7 +34,8 @@ use crate::commands::project::add::{AddTarget, PythonTarget}; use crate::commands::project::install_target::InstallTarget; use crate::commands::project::lock::LockMode; use crate::commands::project::{ - ProjectEnvironment, ProjectError, ProjectInterpreter, UniversalState, default_dependency_groups, + ProjectEnvironment, ProjectError, ProjectInterpreter, UniversalState, WorkspacePython, + default_dependency_groups, }; use crate::commands::{ExitStatus, diagnostics, project}; use crate::printer::Printer; @@ -465,17 +466,24 @@ async fn print_frozen_version( preview: Preview, ) -> Result { // Discover the interpreter (this is the same interpreter --no-sync uses). + let groups = DependencyGroupsWithDefaults::none(); + let workspace_python = WorkspacePython::from_request( + python.as_deref().map(PythonRequest::parse), + Some(project.workspace()), + &groups, + project_dir, + no_config, + ) + .await?; let interpreter = ProjectInterpreter::discover( project.workspace(), - project_dir, - &DependencyGroupsWithDefaults::none(), - python.as_deref().map(PythonRequest::parse), + &groups, + workspace_python, &client_builder, python_preference, python_downloads, &install_mirrors, false, - no_config, active, cache, printer, @@ -577,17 +585,23 @@ async fn lock_and_sync( // Convert to an `AddTarget` by attaching the appropriate interpreter or environment. let target = if no_sync { // Discover the interpreter. + let workspace_python = WorkspacePython::from_request( + python.as_deref().map(PythonRequest::parse), + Some(project.workspace()), + &groups, + project_dir, + no_config, + ) + .await?; let interpreter = ProjectInterpreter::discover( project.workspace(), - project_dir, &groups, - python.as_deref().map(PythonRequest::parse), + workspace_python, &client_builder, python_preference, python_downloads, &install_mirrors, false, - no_config, active, cache, printer, diff --git a/crates/uv/src/commands/workspace/metadata.rs b/crates/uv/src/commands/workspace/metadata.rs index f82bd409dbe94..a212c70facd30 100644 --- a/crates/uv/src/commands/workspace/metadata.rs +++ b/crates/uv/src/commands/workspace/metadata.rs @@ -17,7 +17,7 @@ use uv_workspace::{DiscoveryOptions, VirtualProject, Workspace, WorkspaceCache}; use crate::commands::pip::loggers::DefaultResolveLogger; use crate::commands::project::lock::{LockMode, LockOperation}; use crate::commands::project::lock_target::LockTarget; -use crate::commands::project::{ProjectError, ProjectInterpreter, UniversalState}; +use crate::commands::project::{ProjectError, ProjectInterpreter, UniversalState, WorkspacePython}; use crate::commands::{ExitStatus, diagnostics}; use crate::printer::Printer; use crate::settings::{FrozenSource, LockCheck, ResolverSettings}; @@ -59,18 +59,25 @@ pub(crate) async fn metadata( let mode = if let Some(frozen_source) = frozen { LockMode::Frozen(frozen_source.into()) } else { + // Don't enable any groups' requires-python for interpreter discovery + let groups = DependencyGroupsWithDefaults::none(); + let workspace_python = WorkspacePython::from_request( + python.as_deref().map(PythonRequest::parse), + Some(virtual_project.workspace()), + &groups, + project_dir, + no_config, + ) + .await?; interpreter = ProjectInterpreter::discover( virtual_project.workspace(), - project_dir, - // Don't enable any groups' requires-python for interpreter discovery - &DependencyGroupsWithDefaults::none(), - python.as_deref().map(PythonRequest::parse), + &groups, + workspace_python, &client_builder, python_preference, python_downloads, &install_mirrors, false, - no_config, Some(false), cache, printer, From b8b3073ba38e85d09ba1266a0cca74a7dc764a88 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 21 Apr 2026 14:02:05 -0400 Subject: [PATCH 49/70] Update PyTorch documentation for PyTorch 2.11 (#19095) ## Summary Motivated by: https://github.com/astral-sh/uv/issues/10712#issuecomment-4287063561. --- crates/uv-distribution-types/src/index.rs | 4 +- crates/uv-resolver/src/resolver/indexes.rs | 4 +- crates/uv-settings/src/settings.rs | 4 +- crates/uv-torch/src/backend.rs | 4 + crates/uv-workspace/src/pyproject.rs | 4 +- docs/concepts/indexes.md | 12 +-- docs/concepts/projects/dependencies.md | 4 +- docs/guides/integration/pytorch.md | 96 +++++++++++----------- uv.schema.json | 6 +- 9 files changed, 71 insertions(+), 67 deletions(-) diff --git a/crates/uv-distribution-types/src/index.rs b/crates/uv-distribution-types/src/index.rs index 37f29cde1e114..41d84b1264cab 100644 --- a/crates/uv-distribution-types/src/index.rs +++ b/crates/uv-distribution-types/src/index.rs @@ -136,7 +136,7 @@ pub struct Index { /// ```toml /// [[tool.uv.index]] /// name = "pytorch" - /// url = "https://download.pytorch.org/whl/cu121" + /// url = "https://download.pytorch.org/whl/cu130" /// /// [tool.uv.sources] /// torch = { index = "pytorch" } @@ -154,7 +154,7 @@ pub struct Index { /// ```toml /// [[tool.uv.index]] /// name = "pytorch" - /// url = "https://download.pytorch.org/whl/cu121" + /// url = "https://download.pytorch.org/whl/cu130" /// explicit = true /// /// [tool.uv.sources] diff --git a/crates/uv-resolver/src/resolver/indexes.rs b/crates/uv-resolver/src/resolver/indexes.rs index a4b7c7b6a7a44..0d9f8eda154ea 100644 --- a/crates/uv-resolver/src/resolver/indexes.rs +++ b/crates/uv-resolver/src/resolver/indexes.rs @@ -9,13 +9,13 @@ use uv_normalize::PackageName; /// ```toml /// [[tool.uv.index]] /// name = "pytorch" -/// url = "https://download.pytorch.org/whl/cu121" +/// url = "https://download.pytorch.org/whl/cu130" /// /// [tool.uv.sources] /// torch = { index = "pytorch" } /// ``` /// -/// [`Indexes`] would contain a single entry mapping `torch` to `https://download.pytorch.org/whl/cu121`. +/// [`Indexes`] would contain a single entry mapping `torch` to `https://download.pytorch.org/whl/cu130`. #[derive(Debug, Default, Clone)] pub(crate) struct Indexes(ForkMap); diff --git a/crates/uv-settings/src/settings.rs b/crates/uv-settings/src/settings.rs index 76c9a53082a72..16e4b32f9f2a3 100644 --- a/crates/uv-settings/src/settings.rs +++ b/crates/uv-settings/src/settings.rs @@ -668,7 +668,7 @@ pub struct ResolverInstallerSchema { /// ```toml /// [[tool.uv.index]] /// name = "pytorch" - /// url = "https://download.pytorch.org/whl/cu121" + /// url = "https://download.pytorch.org/whl/cu130" /// explicit = true /// /// [tool.uv.sources] @@ -684,7 +684,7 @@ pub struct ResolverInstallerSchema { example = r#" [[tool.uv.index]] name = "pytorch" - url = "https://download.pytorch.org/whl/cu121" + url = "https://download.pytorch.org/whl/cu130" "# )] pub index: Option>, diff --git a/crates/uv-torch/src/backend.rs b/crates/uv-torch/src/backend.rs index 75f1140314043..775faf3749efd 100644 --- a/crates/uv-torch/src/backend.rs +++ b/crates/uv-torch/src/backend.rs @@ -344,6 +344,8 @@ impl TorchStrategy { | "torchtune" | "torchvision" | "triton" + | "triton-rocm" + | "triton-xpu" | "xformers" ) } @@ -379,6 +381,8 @@ impl TorchStrategy { | "torchtune" | "torchvision" | "triton" + | "triton-rocm" + | "triton-xpu" | "vllm" | "xformers" ) diff --git a/crates/uv-workspace/src/pyproject.rs b/crates/uv-workspace/src/pyproject.rs index fe9aa08612435..7bf6133836218 100644 --- a/crates/uv-workspace/src/pyproject.rs +++ b/crates/uv-workspace/src/pyproject.rs @@ -349,7 +349,7 @@ pub struct ToolUv { /// ```toml /// [[tool.uv.index]] /// name = "pytorch" - /// url = "https://download.pytorch.org/whl/cu121" + /// url = "https://download.pytorch.org/whl/cu130" /// explicit = true /// /// [tool.uv.sources] @@ -365,7 +365,7 @@ pub struct ToolUv { example = r#" [[tool.uv.index]] name = "pytorch" - url = "https://download.pytorch.org/whl/cu121" + url = "https://download.pytorch.org/whl/cu130" "# )] #[serde(deserialize_with = "deserialize_index_vec", default)] diff --git a/docs/concepts/indexes.md b/docs/concepts/indexes.md index 68f4ec8baa4d1..afe5d4376e7c2 100644 --- a/docs/concepts/indexes.md +++ b/docs/concepts/indexes.md @@ -74,17 +74,17 @@ dependencies = ["torch"] [tool.uv.sources] torch = [ - { index = "pytorch-cu118", marker = "sys_platform == 'darwin'"}, - { index = "pytorch-cu124", marker = "sys_platform != 'darwin'"}, + { index = "pytorch-cpu", marker = "sys_platform == 'darwin'"}, + { index = "pytorch-cu130", marker = "sys_platform != 'darwin'"}, ] [[tool.uv.index]] -name = "pytorch-cu118" -url = "https://download.pytorch.org/whl/cu118" +name = "pytorch-cpu" +url = "https://download.pytorch.org/whl/cpu" [[tool.uv.index]] -name = "pytorch-cu124" -url = "https://download.pytorch.org/whl/cu124" +name = "pytorch-cu130" +url = "https://download.pytorch.org/whl/cu130" ``` An index can be marked as `explicit = true` to prevent packages from being installed from that index diff --git a/docs/concepts/projects/dependencies.md b/docs/concepts/projects/dependencies.md index 8ddd396bab939..37c72a3eaaf09 100644 --- a/docs/concepts/projects/dependencies.md +++ b/docs/concepts/projects/dependencies.md @@ -542,7 +542,7 @@ explicit = true [[tool.uv.index]] name = "torch-gpu" -url = "https://download.pytorch.org/whl/cu124" +url = "https://download.pytorch.org/whl/cu130" explicit = true ``` @@ -630,7 +630,7 @@ url = "https://download.pytorch.org/whl/cpu" [[tool.uv.index]] name = "torch-gpu" -url = "https://download.pytorch.org/whl/cu124" +url = "https://download.pytorch.org/whl/cu130" ``` ## Development dependencies diff --git a/docs/guides/integration/pytorch.md b/docs/guides/integration/pytorch.md index 47e41e6d83f00..9ba4d638342c0 100644 --- a/docs/guides/integration/pytorch.md +++ b/docs/guides/integration/pytorch.md @@ -25,10 +25,10 @@ From a packaging perspective, PyTorch has a few uncommon characteristics: - PyTorch produces distinct builds for each accelerator (e.g., CPU-only, CUDA). Since there's no standardized mechanism for specifying these accelerators when publishing or installing, PyTorch encodes them in the local version specifier. As such, PyTorch versions will often look like - `2.5.1+cpu`, `2.5.1+cu121`, etc. + `2.11.0+cpu`, `2.11.0+cu130`, etc. - Builds for different accelerators are published to different indexes. For example, the `+cpu` - builds are published on https://download.pytorch.org/whl/cpu, while the `+cu121` builds are - published on https://download.pytorch.org/whl/cu121. + builds are published on https://download.pytorch.org/whl/cpu, while the `+cu130` builds are + published on https://download.pytorch.org/whl/cu130. As such, the necessary packaging configuration will vary depending on both the platforms you need to support and the accelerators you want to enable. @@ -37,7 +37,7 @@ To start, consider the following (default) configuration, which would be generat `uv init --python 3.14` followed by `uv add torch torchvision`. In this case, PyTorch would be installed from PyPI, which hosts CPU-only wheels for Windows and -macOS, and GPU-accelerated wheels on Linux (targeting CUDA 12.8, as of PyTorch 2.9.1): +macOS, and GPU-accelerated wheels on Linux (targeting CUDA 13.0, as of PyTorch 2.11.0): ```toml [project] @@ -45,8 +45,8 @@ name = "project" version = "0.1.0" requires-python = ">=3.14" dependencies = [ - "torch>=2.9.1", - "torchvision>=0.24.1", + "torch>=2.11.0", + "torchvision>=0.26.0", ] ``` @@ -106,12 +106,12 @@ In such cases, the first step is to add the relevant PyTorch index to your `pypr explicit = true ``` -=== "ROCm6" +=== "ROCm 7.2" ```toml [[tool.uv.index]] name = "pytorch-rocm" - url = "https://download.pytorch.org/whl/rocm6.4" + url = "https://download.pytorch.org/whl/rocm7.2" explicit = true ``` @@ -202,9 +202,9 @@ Next, update the `pyproject.toml` to point `torch` and `torchvision` to the desi ] ``` -=== "ROCm6" +=== "ROCm 7.2" - PyTorch doesn't publish ROCm6 builds for macOS or Windows. As such, we gate on `sys_platform` to instruct uv + PyTorch doesn't publish ROCm builds for macOS or Windows. As such, we gate on `sys_platform` to instruct uv to limit the PyTorch index to Linux, falling back to PyPI on macOS and Windows: ```toml @@ -215,9 +215,9 @@ Next, update the `pyproject.toml` to point `torch` and `torchvision` to the desi torchvision = [ { index = "pytorch-rocm", marker = "sys_platform == 'linux'" }, ] - # ROCm6 support relies on `pytorch-triton-rocm`, which should also be installed from the PyTorch index + # ROCm support relies on `triton-rocm`, which should also be installed from the PyTorch index # (and included in `project.dependencies`). - pytorch-triton-rocm = [ + triton-rocm = [ { index = "pytorch-rocm", marker = "sys_platform == 'linux'" }, ] ``` @@ -235,9 +235,9 @@ Next, update the `pyproject.toml` to point `torch` and `torchvision` to the desi torchvision = [ { index = "pytorch-xpu", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, ] - # Intel GPU support relies on `pytorch-triton-xpu`, which should also be installed from the PyTorch index + # Intel GPU support relies on `triton-xpu`, which should also be installed from the PyTorch index # (and included in `project.dependencies`). - pytorch-triton-xpu = [ + triton-xpu = [ { index = "pytorch-xpu", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, ] ``` @@ -250,8 +250,8 @@ name = "project" version = "0.1.0" requires-python = ">=3.14.0" dependencies = [ - "torch>=2.9.1", - "torchvision>=0.24.1", + "torch>=2.11.0", + "torchvision>=0.26.0", ] [tool.uv.sources] @@ -283,18 +283,18 @@ name = "project" version = "0.1.0" requires-python = ">=3.14.0" dependencies = [ - "torch>=2.9.1", - "torchvision>=0.24.1", + "torch>=2.11.0", + "torchvision>=0.26.0", ] [tool.uv.sources] torch = [ { index = "pytorch-cpu", marker = "sys_platform != 'linux'" }, - { index = "pytorch-cu128", marker = "sys_platform == 'linux'" }, + { index = "pytorch-cu130", marker = "sys_platform == 'linux'" }, ] torchvision = [ { index = "pytorch-cpu", marker = "sys_platform != 'linux'" }, - { index = "pytorch-cu128", marker = "sys_platform == 'linux'" }, + { index = "pytorch-cu130", marker = "sys_platform == 'linux'" }, ] [[tool.uv.index]] @@ -303,8 +303,8 @@ url = "https://download.pytorch.org/whl/cpu" explicit = true [[tool.uv.index]] -name = "pytorch-cu128" -url = "https://download.pytorch.org/whl/cu128" +name = "pytorch-cu130" +url = "https://download.pytorch.org/whl/cu130" explicit = true ``` @@ -317,9 +317,9 @@ name = "project" version = "0.1.0" requires-python = ">=3.14.0" dependencies = [ - "torch>=2.9.1", - "torchvision>=0.24.1", - "pytorch-triton-rocm>=3.5.1 ; sys_platform == 'linux'", + "torch>=2.11.0", + "torchvision>=0.26.0", + "triton-rocm>=3.6.0 ; sys_platform == 'linux'", ] [tool.uv.sources] @@ -329,13 +329,13 @@ torch = [ torchvision = [ { index = "pytorch-rocm", marker = "sys_platform == 'linux'" }, ] -pytorch-triton-rocm = [ +triton-rocm = [ { index = "pytorch-rocm", marker = "sys_platform == 'linux'" }, ] [[tool.uv.index]] name = "pytorch-rocm" -url = "https://download.pytorch.org/whl/rocm6.4" +url = "https://download.pytorch.org/whl/rocm7.2" explicit = true ``` @@ -347,9 +347,9 @@ name = "project" version = "0.1.0" requires-python = ">=3.14.0" dependencies = [ - "torch>=2.9.1", - "torchvision>=0.24.1", - "pytorch-triton-xpu>=3.5.0 ; sys_platform == 'win32' or sys_platform == 'linux'", + "torch>=2.11.0", + "torchvision>=0.26.0", + "triton-xpu>=3.7.0 ; sys_platform == 'win32' or sys_platform == 'linux'", ] [tool.uv.sources] @@ -359,7 +359,7 @@ torch = [ torchvision = [ { index = "pytorch-xpu", marker = "sys_platform == 'win32' or sys_platform == 'linux'" }, ] -pytorch-triton-xpu = [ +triton-xpu = [ { index = "pytorch-xpu", marker = "sys_platform == 'win32' or sys_platform == 'linux'" }, ] @@ -373,11 +373,11 @@ explicit = true In some cases, you may want to use CPU-only builds in some cases, but CUDA-enabled builds in others, with the choice toggled by a user-provided extra (e.g., `uv sync --extra cpu` vs. -`uv sync --extra cu128`). +`uv sync --extra cu130`). With `tool.uv.sources`, you can use extra markers to specify the desired index for each enabled extra. For example, the following configuration would use PyTorch's CPU-only for -`uv sync --extra cpu` and CUDA-enabled builds for `uv sync --extra cu128`: +`uv sync --extra cpu` and CUDA-enabled builds for `uv sync --extra cu130`: ```toml [project] @@ -388,30 +388,30 @@ dependencies = [] [project.optional-dependencies] cpu = [ - "torch>=2.9.1", - "torchvision>=0.24.1", + "torch>=2.11.0", + "torchvision>=0.26.0", ] -cu128 = [ - "torch>=2.9.1", - "torchvision>=0.24.1", +cu130 = [ + "torch>=2.11.0", + "torchvision>=0.26.0", ] [tool.uv] conflicts = [ [ { extra = "cpu" }, - { extra = "cu128" }, + { extra = "cu130" }, ], ] [tool.uv.sources] torch = [ { index = "pytorch-cpu", extra = "cpu" }, - { index = "pytorch-cu128", extra = "cu128" }, + { index = "pytorch-cu130", extra = "cu130" }, ] torchvision = [ { index = "pytorch-cpu", extra = "cpu" }, - { index = "pytorch-cu128", extra = "cu128" }, + { index = "pytorch-cu130", extra = "cu130" }, ] [[tool.uv.index]] @@ -420,15 +420,15 @@ url = "https://download.pytorch.org/whl/cpu" explicit = true [[tool.uv.index]] -name = "pytorch-cu128" -url = "https://download.pytorch.org/whl/cu128" +name = "pytorch-cu130" +url = "https://download.pytorch.org/whl/cu130" explicit = true ``` !!! note Since GPU-accelerated builds aren't available on macOS, the above configuration will fail to install - on macOS when the `cu128` extra is enabled. + on macOS when the `cu130` extra is enabled. ## The `uv pip` interface @@ -467,15 +467,15 @@ then use the most-compatible PyTorch index for all relevant packages (e.g., `tor etc.). If no such GPU is found, uv will fall back to the CPU-only index. uv will continue to respect existing index configuration for any packages outside the PyTorch ecosystem. -You can also select a specific backend (e.g., CUDA 12.8) with `--torch-backend=cu126` (or -`UV_TORCH_BACKEND=cu126`): +You can also select a specific backend (e.g., CUDA 13.0) with `--torch-backend=cu130` (or +`UV_TORCH_BACKEND=cu130`): ```shell $ # With a command-line argument. -$ uv pip install torch torchvision --torch-backend=cu126 +$ uv pip install torch torchvision --torch-backend=cu130 $ # With an environment variable. -$ UV_TORCH_BACKEND=cu128 uv pip install torch torchvision +$ UV_TORCH_BACKEND=cu130 uv pip install torch torchvision ``` At present, `--torch-backend` is only available in the `uv pip` interface. diff --git a/uv.schema.json b/uv.schema.json index 2b73a40cbdd49..3d96bae4f3842 100644 --- a/uv.schema.json +++ b/uv.schema.json @@ -276,7 +276,7 @@ ] }, "index": { - "description": "The indexes to use when resolving dependencies.\n\nAccepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/)\n(the simple repository API), or a local directory laid out in the same format.\n\nIndexes are considered in the order in which they're defined, such that the first-defined\nindex has the highest priority. Further, the indexes provided by this setting are given\nhigher priority than any indexes specified via [`index_url`](#index-url) or\n[`extra_index_url`](#extra-index-url). uv will only consider the first index that contains\na given package, unless an alternative [index strategy](#index-strategy) is specified.\n\nIf an index is marked as `explicit = true`, it will be used exclusively for the\ndependencies that select it explicitly via `[tool.uv.sources]`, as in:\n\n```toml\n[[tool.uv.index]]\nname = \"pytorch\"\nurl = \"https://download.pytorch.org/whl/cu121\"\nexplicit = true\n\n[tool.uv.sources]\ntorch = { index = \"pytorch\" }\n```\n\nIf an index is marked as `default = true`, it will be moved to the end of the prioritized list, such that it is\ngiven the lowest priority when resolving packages. Additionally, marking an index as default will disable the\nPyPI default index.", + "description": "The indexes to use when resolving dependencies.\n\nAccepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/)\n(the simple repository API), or a local directory laid out in the same format.\n\nIndexes are considered in the order in which they're defined, such that the first-defined\nindex has the highest priority. Further, the indexes provided by this setting are given\nhigher priority than any indexes specified via [`index_url`](#index-url) or\n[`extra_index_url`](#extra-index-url). uv will only consider the first index that contains\na given package, unless an alternative [index strategy](#index-strategy) is specified.\n\nIf an index is marked as `explicit = true`, it will be used exclusively for the\ndependencies that select it explicitly via `[tool.uv.sources]`, as in:\n\n```toml\n[[tool.uv.index]]\nname = \"pytorch\"\nurl = \"https://download.pytorch.org/whl/cu130\"\nexplicit = true\n\n[tool.uv.sources]\ntorch = { index = \"pytorch\" }\n```\n\nIf an index is marked as `default = true`, it will be moved to the end of the prioritized list, such that it is\ngiven the lowest priority when resolving packages. Additionally, marking an index as default will disable the\nPyPI default index.", "type": ["array", "null"], "default": null, "items": { @@ -980,7 +980,7 @@ ] }, "explicit": { - "description": "Mark the index as explicit.\n\nExplicit indexes will _only_ be used when explicitly requested via a `[tool.uv.sources]`\ndefinition, as in:\n\n```toml\n[[tool.uv.index]]\nname = \"pytorch\"\nurl = \"https://download.pytorch.org/whl/cu121\"\nexplicit = true\n\n[tool.uv.sources]\ntorch = { index = \"pytorch\" }\n```", + "description": "Mark the index as explicit.\n\nExplicit indexes will _only_ be used when explicitly requested via a `[tool.uv.sources]`\ndefinition, as in:\n\n```toml\n[[tool.uv.index]]\nname = \"pytorch\"\nurl = \"https://download.pytorch.org/whl/cu130\"\nexplicit = true\n\n[tool.uv.sources]\ntorch = { index = \"pytorch\" }\n```", "type": "boolean", "default": false }, @@ -1002,7 +1002,7 @@ } }, "name": { - "description": "The name of the index.\n\nIndex names can be used to reference indexes elsewhere in the configuration. For example,\nyou can pin a package to a specific index by name:\n\n```toml\n[[tool.uv.index]]\nname = \"pytorch\"\nurl = \"https://download.pytorch.org/whl/cu121\"\n\n[tool.uv.sources]\ntorch = { index = \"pytorch\" }\n```", + "description": "The name of the index.\n\nIndex names can be used to reference indexes elsewhere in the configuration. For example,\nyou can pin a package to a specific index by name:\n\n```toml\n[[tool.uv.index]]\nname = \"pytorch\"\nurl = \"https://download.pytorch.org/whl/cu130\"\n\n[tool.uv.sources]\ntorch = { index = \"pytorch\" }\n```", "anyOf": [ { "$ref": "#/definitions/IndexName" From 4e8b562813dfc3b1c2f18c3a8b2f23c33333f628 Mon Sep 17 00:00:00 2001 From: Zsolt Dollenstein Date: Tue, 21 Apr 2026 19:03:05 +0100 Subject: [PATCH 50/70] Add `--python-downloads-json-url` to `python pin` (#19092) --- crates/uv-cli/src/lib.rs | 4 ++++ crates/uv/src/settings.rs | 12 +++++++++--- crates/uv/tests/it/python_pin.rs | 21 +++++++++++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index a1f3d4b80029a..655cdf86b3d68 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -6718,6 +6718,10 @@ pub struct PythonPinArgs { /// Remove the Python version pin. #[arg(long, conflicts_with = "request", conflicts_with = "resolved")] pub rm: bool, + + /// URL pointing to JSON of custom Python installations. + #[arg(long, value_hint = ValueHint::Other)] + pub python_downloads_json_url: Option, } #[derive(Args)] diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 53afa8d5a83b9..139e9829dacad 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -1587,21 +1587,27 @@ impl PythonPinSettings { no_project, global, rm, + python_downloads_json_url, } = args; let filesystem_install_mirrors = filesystem .map(|fs| fs.install_mirrors.clone()) .unwrap_or_default(); + let install_mirrors = PythonInstallMirrors { + python_downloads_json_url, + ..Default::default() + } + .combine(environment.install_mirrors) + .combine(filesystem_install_mirrors); + Self { request, resolved: flag(resolved, no_resolved, "resolved").unwrap_or(false), no_project, global, rm, - install_mirrors: environment - .install_mirrors - .combine(filesystem_install_mirrors), + install_mirrors, } } } diff --git a/crates/uv/tests/it/python_pin.rs b/crates/uv/tests/it/python_pin.rs index f879369a7a2c2..81a3dee3b69c9 100644 --- a/crates/uv/tests/it/python_pin.rs +++ b/crates/uv/tests/it/python_pin.rs @@ -182,6 +182,27 @@ fn python_pin() { } } +#[test] +fn python_pin_uses_python_downloads_json_url() { + let context = uv_test::test_context_with_versions!(&[]).with_filtered_python_sources(); + let metadata = context.temp_dir.child("empty-download-metadata.json"); + metadata.write_str("{}").unwrap(); + + uv_snapshot!(context.filters(), context + .python_pin() + .arg("--resolved") + .arg("3.12") + .arg("--python-downloads-json-url") + .arg(metadata.path()), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: No interpreter found for Python 3.12 in [PYTHON SOURCES] + "); +} + // If there is no project-level `.python-version` file, respect the global pin. #[test] fn python_pin_global_if_no_local() -> Result<()> { From c875b024412ddf4965dc8abdb028fa12e697130c Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 21 Apr 2026 14:29:38 -0400 Subject: [PATCH 51/70] Use sentinel timestamp for relative exclude-newer-package spans (#19101) Closes #19089 Applies the same change as https://github.com/astral-sh/uv/pull/19022 to per-package exclude-newer spans. --------- Co-authored-by: Claude --- crates/uv-resolver/src/lock/mod.rs | 10 +-- .../tests/it/lock_exclude_newer_relative.rs | 77 +++++++++++++++++-- 2 files changed, 74 insertions(+), 13 deletions(-) diff --git a/crates/uv-resolver/src/lock/mod.rs b/crates/uv-resolver/src/lock/mod.rs index b250862137383..292258fd70000 100644 --- a/crates/uv-resolver/src/lock/mod.rs +++ b/crates/uv-resolver/src/lock/mod.rs @@ -1183,12 +1183,12 @@ impl Lock { match setting { ExcludeNewerOverride::Enabled(exclude_newer_value) => { if let Some(span) = exclude_newer_value.span() { - // Serialize as inline table with timestamp and span + // When a relative span is present, write a no-op timestamp + // for backwards compatibility. This matches treatment for + // the global `exclude-newer`. let mut inline = toml_edit::InlineTable::new(); - inline.insert( - "timestamp", - exclude_newer_value.timestamp().to_string().into(), - ); + inline + .insert("timestamp", ExcludeNewerValue::PLACEHOLDER.into()); inline.insert("span", span.to_string().into()); package_table.insert(name.as_ref(), Item::Value(inline.into())); } else { diff --git a/crates/uv/tests/it/lock_exclude_newer_relative.rs b/crates/uv/tests/it/lock_exclude_newer_relative.rs index bfbe703887b99..147c0047c99c2 100644 --- a/crates/uv/tests/it/lock_exclude_newer_relative.rs +++ b/crates/uv/tests/it/lock_exclude_newer_relative.rs @@ -373,7 +373,7 @@ fn lock_exclude_newer_package_relative() -> Result<()> { [options] [options.exclude-newer-package] - idna = { timestamp = "2024-04-10T00:00:00Z", span = "P3W" } + idna = { timestamp = "0001-01-01T00:00:00Z", span = "P3W" } [[package]] name = "idna" @@ -442,7 +442,7 @@ fn lock_exclude_newer_package_relative() -> Result<()> { [options] [options.exclude-newer-package] - idna = { timestamp = "2024-04-17T00:00:00Z", span = "P2W" } + idna = { timestamp = "0001-01-01T00:00:00Z", span = "P2W" } [[package]] name = "idna" @@ -492,7 +492,7 @@ fn lock_exclude_newer_package_relative() -> Result<()> { [options] [options.exclude-newer-package] - idna = { timestamp = "2024-04-17T00:00:00Z", span = "P2W" } + idna = { timestamp = "0001-01-01T00:00:00Z", span = "P2W" } [[package]] name = "idna" @@ -635,7 +635,7 @@ fn lock_exclude_newer_package_relative_pyproject() -> Result<()> { [options] [options.exclude-newer-package] - idna = { timestamp = "2024-04-10T00:00:00Z", span = "P3W" } + idna = { timestamp = "0001-01-01T00:00:00Z", span = "P3W" } [[package]] name = "idna" @@ -720,7 +720,7 @@ fn lock_exclude_newer_relative_global_and_package() -> Result<()> { exclude-newer-span = "P3W" [options.exclude-newer-package] - typing-extensions = { timestamp = "2024-04-17T00:00:00Z", span = "P2W" } + typing-extensions = { timestamp = "0001-01-01T00:00:00Z", span = "P2W" } [[package]] name = "idna" @@ -844,7 +844,7 @@ fn lock_exclude_newer_relative_global_and_package() -> Result<()> { exclude-newer = "2024-05-20T00:00:00Z" [options.exclude-newer-package] - typing-extensions = { timestamp = "2024-04-17T00:00:00Z", span = "P2W" } + typing-extensions = { timestamp = "0001-01-01T00:00:00Z", span = "P2W" } [[package]] name = "idna" @@ -1353,7 +1353,7 @@ fn lock_exclude_newer_package_relative_no_timestamp_in_lockfile() -> Result<()> [options] [options.exclude-newer-package] - idna = { timestamp = "2024-04-10T00:00:00Z", span = "P3W" } + idna = { timestamp = "0001-01-01T00:00:00Z", span = "P3W" } [[package]] name = "idna" @@ -1378,7 +1378,7 @@ fn lock_exclude_newer_package_relative_no_timestamp_in_lockfile() -> Result<()> // Manually remove the per-package exclude-newer timestamp from the lockfile, leaving the span. let lock = lock.replace( - "idna = { timestamp = \"2024-04-10T00:00:00Z\", span = \"P3W\" }", + "idna = { timestamp = \"0001-01-01T00:00:00Z\", span = \"P3W\" }", "idna = { span = \"P3W\" }", ); context.temp_dir.child("uv.lock").write_str(&lock)?; @@ -1532,3 +1532,64 @@ fn lock_exclude_newer_relative_values_pyproject() -> Result<()> { Ok(()) } + +/// When a relative span is configured for `exclude-newer-package`, the lockfile +/// should use a fixed no-op sentinel for the stored timestamp. +#[test] +fn lock_exclude_newer_package_relative_noop_timestamp() -> Result<()> { + let context = uv_test::test_context!("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 = ["idna"] + "#, + )?; + + let current_timestamp = "2024-05-01T00:00:00Z"; + uv_snapshot!(context.filters(), context + .lock() + .env_remove(EnvVars::UV_EXCLUDE_NEWER) + .env(EnvVars::UV_TEST_CURRENT_TIMESTAMP, current_timestamp) + .arg("--exclude-newer-package") + .arg("idna=3 weeks"), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + "); + + // The lockfile stores the no-op sentinel timestamp alongside the span. + let lock = context.read("uv.lock"); + assert!( + lock.contains(r#"idna = { timestamp = "0001-01-01T00:00:00Z", span = "P3W" }"#), + "expected no-op sentinel in lockfile, got:\n{lock}" + ); + + // Locking again at a later time should yield an identical lockfile, even + // without `--locked`, because the span is unchanged and the stored + // timestamp is a fixed sentinel. + let later_timestamp = "2024-06-01T00:00:00Z"; + uv_snapshot!(context.filters(), context + .lock() + .env_remove(EnvVars::UV_EXCLUDE_NEWER) + .env(EnvVars::UV_TEST_CURRENT_TIMESTAMP, later_timestamp) + .arg("--exclude-newer-package") + .arg("idna=3 weeks"), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + "); + + assert_eq!(context.read("uv.lock"), lock); + + Ok(()) +} From 1f749a160892794f2a99ccf8fa4523c9e79ba93d Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 21 Apr 2026 14:41:16 -0400 Subject: [PATCH 52/70] Disable transparent Python upgrades in projects when a patch version is requested via `.python-version` (#19102) Fixes #19100 This was an underlying bug surfaced outside of preview by https://github.com/astral-sh/uv/pull/18961 We were not computing the resolved Python version request, so we were not taking into account `.python-version` values. --------- Co-authored-by: Claude --- crates/uv/src/commands/project/mod.rs | 9 +-- crates/uv/tests/it/python_upgrade.rs | 88 ++++++++++++++++++++++++++- 2 files changed, 92 insertions(+), 5 deletions(-) diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index 8bda9e3c04ea2..2fa657c1e3891 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -1412,10 +1412,6 @@ impl ProjectEnvironment { }) .ok(); - let upgradeable = python - .as_ref() - .is_none_or(|request| !request.includes_patch()); - let workspace_python = WorkspacePython::from_request( python, Some(workspace), @@ -1425,6 +1421,11 @@ impl ProjectEnvironment { ) .await?; + let upgradeable = workspace_python + .python_request + .as_ref() + .is_none_or(|request| !request.includes_patch()); + match ProjectInterpreter::discover( workspace, groups, diff --git a/crates/uv/tests/it/python_upgrade.rs b/crates/uv/tests/it/python_upgrade.rs index 00bad7e89237f..33411f0fb89df 100644 --- a/crates/uv/tests/it/python_upgrade.rs +++ b/crates/uv/tests/it/python_upgrade.rs @@ -1,6 +1,6 @@ use anyhow::Result; use assert_cmd::assert::OutputAssertExt; -use assert_fs::fixture::FileTouch; +use assert_fs::fixture::{FileTouch, FileWriteStr}; use assert_fs::prelude::PathChild; use uv_python::managed::platform_key_from_env; use uv_static::EnvVars; @@ -838,3 +838,89 @@ fn python_upgrade_build_version() { Python 3.12 is already on the latest supported patch release "); } + +// Regression test for . +// +// A virtual environment created by `uv sync` should pin to a patch version +// recorded in `.python-version` rather than symlinking to the mutable +// minor-version directory, which would be silently upgraded by a later +// `uv python install` of a newer patch release. +#[test] +fn python_sync_honors_pinned_patch_version() -> Result<()> { + let context = uv_test::test_context_with_versions!(&[]) + .with_python_download_cache() + .with_filtered_python_keys() + .with_filtered_exe_suffix() + .with_managed_python_dirs() + .with_filtered_latest_python_versions(); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.10" + "#, + )?; + + // Install an earlier patch version + uv_snapshot!(context.filters(), context.python_install().arg("3.10.17"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.10.17 in [TIME] + + cpython-3.10.17-[PLATFORM] (python3.10) + "); + + // Pin the project to the older patch version before the environment exists + uv_snapshot!(context.filters(), context.python_pin().arg("3.10.17"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Pinned `.python-version` to `3.10.17` + + ----- stderr ----- + "); + + // Create the environment via `uv sync` + uv_snapshot!(context.filters(), context.sync(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.10.17 + Creating virtual environment at: .venv + Resolved 1 package in [TIME] + Checked in [TIME] + "); + + // Install a newer patch version, which moves the mutable `cpython-3.10` + // minor-version link to the newer release. + uv_snapshot!(context.filters(), context.python_upgrade().arg("3.10"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.10.[LATEST] in [TIME] + + cpython-3.10.[LATEST]-[PLATFORM] (python3.10) + "); + + // The environment should continue to report the pinned patch version. + uv_snapshot!(context.filters(), context.run().arg("python").arg("--version"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.10.17 + + ----- stderr ----- + Resolved 1 package in [TIME] + Checked in [TIME] + "); + + Ok(()) +} From 9c844ef48b54a63b899d6586a166ed294e143630 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 21 Apr 2026 14:47:07 -0400 Subject: [PATCH 53/70] Use the newly stabilized `std::fmt::from_fn` (#19096) Co-authored-by: Claude --- crates/uv-pep440/src/version.rs | 49 ++++++------- crates/uv-pep508/src/lib.rs | 89 ++++++++++------------- crates/uv-pep508/src/marker/mod.rs | 6 +- crates/uv-pep508/src/marker/tree.rs | 43 ++--------- crates/uv-resolver/src/pubgrub/report.rs | 90 ++++++++---------------- 5 files changed, 100 insertions(+), 177 deletions(-) diff --git a/crates/uv-pep440/src/version.rs b/crates/uv-pep440/src/version.rs index 488e95c11d1c8..4faa803b7dbc9 100644 --- a/crates/uv-pep440/src/version.rs +++ b/crates/uv-pep440/src/version.rs @@ -4218,36 +4218,29 @@ mod tests { ); } - /// Wraps a `Version` and provides a more "bloated" debug but standard - /// representation. - /// - /// We don't do this by default because it takes up a ton of space, and - /// just printing out the display version of the version is quite a bit - /// simpler. - /// - /// Nevertheless, when *testing* version parsing, you really want to - /// be able to peek at all of its constituent parts. So we use this in - /// assertion failure messages. - struct VersionBloatedDebug<'a>(&'a Version); - - impl std::fmt::Debug for VersionBloatedDebug<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Version") - .field("epoch", &self.0.epoch()) - .field("release", &&*self.0.release()) - .field("pre", &self.0.pre()) - .field("post", &self.0.post()) - .field("dev", &self.0.dev()) - .field("local", &self.0.local()) - .field("min", &self.0.min()) - .field("max", &self.0.max()) - .finish() - } - } - impl Version { + /// Returns a more "bloated" debug representation of this [`Version`]. + /// + /// We don't do this by default because it takes up a ton of space, and + /// just printing out the display version of the version is quite a bit + /// simpler. + /// + /// Nevertheless, when *testing* version parsing, you really want to + /// be able to peek at all of its constituent parts. So we use this in + /// assertion failure messages. pub(crate) fn as_bloated_debug(&self) -> impl std::fmt::Debug + '_ { - VersionBloatedDebug(self) + std::fmt::from_fn(|f| { + f.debug_struct("Version") + .field("epoch", &self.epoch()) + .field("release", &&*self.release()) + .field("pre", &self.pre()) + .field("post", &self.post()) + .field("dev", &self.dev()) + .field("local", &self.local()) + .field("min", &self.min()) + .field("max", &self.max()) + .finish() + }) } } diff --git a/crates/uv-pep508/src/lib.rs b/crates/uv-pep508/src/lib.rs index 56416afb609bf..7965e954914f1 100644 --- a/crates/uv-pep508/src/lib.rs +++ b/crates/uv-pep508/src/lib.rs @@ -151,69 +151,56 @@ impl Requirement { /// Returns a [`Display`] implementation that doesn't mask credentials. pub fn displayable_with_credentials(&self) -> impl Display { - RequirementDisplay { - requirement: self, - display_credentials: true, - } + std::fmt::from_fn(|f| fmt_requirement(self, f, true)) } } impl Display for Requirement { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - RequirementDisplay { - requirement: self, - display_credentials: false, - } - .fmt(f) + fmt_requirement(self, f, false) } } -struct RequirementDisplay<'a, T> -where - T: Pep508Url + Display, -{ - requirement: &'a Requirement, +fn fmt_requirement( + requirement: &Requirement, + f: &mut Formatter<'_>, display_credentials: bool, -} - -impl Display for RequirementDisplay<'_, T> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.requirement.name)?; - if !self.requirement.extras.is_empty() { - write!( - f, - "[{}]", - self.requirement - .extras - .iter() - .map(ToString::to_string) - .collect::>() - .join(",") - )?; - } - if let Some(version_or_url) = &self.requirement.version_or_url { - match version_or_url { - VersionOrUrl::VersionSpecifier(version_specifier) => { - let version_specifier: Vec = - version_specifier.iter().map(ToString::to_string).collect(); - write!(f, "{}", version_specifier.join(","))?; - } - VersionOrUrl::Url(url) => { - let url_string = if self.display_credentials { - url.displayable_with_credentials().to_string() - } else { - url.to_string() - }; - // We add the space for markers later if necessary - write!(f, " @ {url_string}")?; - } +) -> std::fmt::Result { + write!(f, "{}", requirement.name)?; + if !requirement.extras.is_empty() { + write!( + f, + "[{}]", + requirement + .extras + .iter() + .map(ToString::to_string) + .collect::>() + .join(",") + )?; + } + if let Some(version_or_url) = &requirement.version_or_url { + match version_or_url { + VersionOrUrl::VersionSpecifier(version_specifier) => { + let version_specifier: Vec = + version_specifier.iter().map(ToString::to_string).collect(); + write!(f, "{}", version_specifier.join(","))?; + } + VersionOrUrl::Url(url) => { + let url_string = if display_credentials { + url.displayable_with_credentials().to_string() + } else { + url.to_string() + }; + // We add the space for markers later if necessary + write!(f, " @ {url_string}")?; } } - if let Some(marker) = self.requirement.marker.contents() { - write!(f, " ; {marker}")?; - } - Ok(()) } + if let Some(marker) = requirement.marker.contents() { + write!(f, " ; {marker}")?; + } + Ok(()) } /// diff --git a/crates/uv-pep508/src/marker/mod.rs b/crates/uv-pep508/src/marker/mod.rs index 55d21c69f9de5..1f4e311f94d7b 100644 --- a/crates/uv-pep508/src/marker/mod.rs +++ b/crates/uv-pep508/src/marker/mod.rs @@ -22,9 +22,9 @@ pub use lowering::{ }; pub use tree::{ ContainsMarkerTree, ExtraMarkerTree, ExtraOperator, InMarkerTree, MarkerExpression, - MarkerOperator, MarkerTree, MarkerTreeContents, MarkerTreeDebugGraph, MarkerTreeKind, - MarkerValue, MarkerValueExtra, MarkerValueList, MarkerValueString, MarkerValueVersion, - MarkerWarningKind, StringMarkerTree, StringVersion, VersionMarkerTree, + MarkerOperator, MarkerTree, MarkerTreeContents, MarkerTreeKind, MarkerValue, MarkerValueExtra, + MarkerValueList, MarkerValueString, MarkerValueVersion, MarkerWarningKind, StringMarkerTree, + StringVersion, VersionMarkerTree, }; /// `serde` helpers for [`MarkerTree`]. diff --git a/crates/uv-pep508/src/marker/tree.rs b/crates/uv-pep508/src/marker/tree.rs index 37cd72f642af8..4cae3563fd206 100644 --- a/crates/uv-pep508/src/marker/tree.rs +++ b/crates/uv-pep508/src/marker/tree.rs @@ -1456,8 +1456,8 @@ impl MarkerTree { /// This is useful for debugging when one wants to look at a /// representation of a `MarkerTree` that is more faithful to its /// internal representation. - pub fn debug_graph(&self) -> MarkerTreeDebugGraph<'_> { - MarkerTreeDebugGraph { marker: self } + pub fn debug_graph(&self) -> impl fmt::Debug + '_ { + fmt::from_fn(|f| self.fmt_graph(f, 0)) } /// Formats a [`MarkerTree`] in its "raw" representation. @@ -1465,8 +1465,11 @@ impl MarkerTree { /// This is useful for debugging when one wants to look at a /// representation of a `MarkerTree` that is precisely identical /// to its internal representation. - pub fn debug_raw(&self) -> MarkerTreeDebugRaw<'_> { - MarkerTreeDebugRaw { marker: self } + pub fn debug_raw(&self) -> impl fmt::Debug + '_ { + fmt::from_fn(|f| { + let node = INTERNER.shared.node(self.0); + f.debug_tuple("MarkerTreeDebugRaw").field(node).finish() + }) } fn fmt_graph(self, f: &mut Formatter<'_>, level: usize) -> fmt::Result { @@ -1573,38 +1576,6 @@ impl Ord for MarkerTree { } } -/// Formats a [`MarkerTree`] as a graph. -/// -/// This type is created by the [`MarkerTree::debug_graph`] routine. -#[derive(Clone)] -pub struct MarkerTreeDebugGraph<'a> { - marker: &'a MarkerTree, -} - -impl fmt::Debug for MarkerTreeDebugGraph<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - self.marker.fmt_graph(f, 0) - } -} - -/// Formats a [`MarkerTree`] using its raw internals. -/// -/// This is very verbose and likely only useful if you're working -/// on the internals of this crate. -/// -/// This type is created by the [`MarkerTree::debug_raw`] routine. -#[derive(Clone)] -pub struct MarkerTreeDebugRaw<'a> { - marker: &'a MarkerTree, -} - -impl fmt::Debug for MarkerTreeDebugRaw<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let node = INTERNER.shared.node(self.marker.0); - f.debug_tuple("MarkerTreeDebugRaw").field(node).finish() - } -} - /// The underlying kind of an arbitrary node in a [`MarkerTree`]. /// /// A marker tree is represented as an algebraic decision tree with two terminal nodes diff --git a/crates/uv-resolver/src/pubgrub/report.rs b/crates/uv-resolver/src/pubgrub/report.rs index 1a4fbc70a433f..7712d98c77cd9 100644 --- a/crates/uv-resolver/src/pubgrub/report.rs +++ b/crates/uv-resolver/src/pubgrub/report.rs @@ -125,7 +125,7 @@ impl ReportFormatter, UnavailableReason> match reason { UnavailableReason::Package(reason) => { let message = reason.singular_message(); - format!("{}{}", package, Padded::new(" ", &message, "")) + format!("{}{}", package, padded(" ", &message, "")) } UnavailableReason::Version(reason) => { let range = self.compatible_range(package, set); @@ -139,9 +139,9 @@ impl ReportFormatter, UnavailableReason> self.python_requirement.target().abi_tag(), ); if let Some(context) = context { - format!("{}{}{}", range, Padded::new(" ", &message, " "), context) + format!("{}{}{}", range, padded(" ", &message, " "), context) } else { - format!("{}{}", range, Padded::new(" ", &message, "")) + format!("{}{}", range, padded(" ", &message, "")) } } } @@ -244,8 +244,8 @@ impl ReportFormatter, UnavailableReason> format!( "Because {}we can conclude that {}", - Padded::from_string("", &external, ", "), - Padded::from_string("", &terms, "."), + padded("", &external, ", "), + padded("", &terms, "."), ) } @@ -267,10 +267,10 @@ impl ReportFormatter, UnavailableReason> format!( "Because we know from ({}) that {}and we know from ({}) that {}{}", ref_id1, - Padded::new("", &derived1_terms, " "), + padded("", &derived1_terms, " "), ref_id2, - Padded::new("", &derived2_terms, ", "), - Padded::new("", ¤t_terms, "."), + padded("", &derived2_terms, ", "), + padded("", ¤t_terms, "."), ) } @@ -293,9 +293,9 @@ impl ReportFormatter, UnavailableReason> format!( "Because we know from ({}) that {}and {}we can conclude that {}", ref_id, - Padded::new("", &derived_terms, " "), - Padded::new("", &external, ", "), - Padded::new("", ¤t_terms, "."), + padded("", &derived_terms, " "), + padded("", &external, ", "), + padded("", ¤t_terms, "."), ) } @@ -310,8 +310,8 @@ impl ReportFormatter, UnavailableReason> format!( "And because {}we can conclude that {}", - Padded::from_string("", &external, ", "), - Padded::from_string("", &terms, "."), + padded("", &external, ", "), + padded("", &terms, "."), ) } @@ -328,8 +328,8 @@ impl ReportFormatter, UnavailableReason> format!( "And because we know from ({}) that {}we can conclude that {}", ref_id, - Padded::from_string("", &derived, ", "), - Padded::from_string("", ¤t, "."), + padded("", &derived, ", "), + padded("", ¤t, "."), ) } @@ -345,8 +345,8 @@ impl ReportFormatter, UnavailableReason> format!( "And because {}we can conclude that {}", - Padded::from_string("", &external, ", "), - Padded::from_string("", &terms, "."), + padded("", &external, ", "), + padded("", &terms, "."), ) } } @@ -487,7 +487,7 @@ impl PubGrubReportFormatter<'_> { if let Some(root) = self.format_root_requires(package1) { return format!( "{root} {}and {}", - Padded::new("", &dependency1, " "), + padded("", &dependency1, " "), dependency2, ); } @@ -515,11 +515,7 @@ impl PubGrubReportFormatter<'_> { let external1 = self.format_external(external1); let external2 = self.format_external(external2); - format!( - "{}and {}", - Padded::from_string("", &external1, " "), - &external2, - ) + format!("{}and {}", padded("", &external1, " "), &external2,) } } } @@ -2429,7 +2425,7 @@ impl<'a> DependsOn<'a> { impl std::fmt::Display for DependsOn<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", Padded::new("", self.package, " "))?; + write!(f, "{}", padded("", self.package, " "))?; if self.package.plural() { write!(f, "depend on ")?; } else { @@ -2440,8 +2436,8 @@ impl std::fmt::Display for DependsOn<'_> { Some(ref dependency2) => write!( f, "{}and{}", - Padded::new("", &self.dependency1, " "), - Padded::new(" ", &dependency2, "") + padded("", &self.dependency1, " "), + padded(" ", &dependency2, "") )?, None => write!(f, "{}", self.dependency1)?, } @@ -2452,52 +2448,28 @@ impl std::fmt::Display for DependsOn<'_> { /// Inserts the given padding on the left and right sides of the content if /// the content does not start and end with whitespace respectively. -#[derive(Debug)] -struct Padded<'a, T: std::fmt::Display> { +fn padded<'a, T: std::fmt::Display + ?Sized>( left: &'a str, content: &'a T, right: &'a str, -} - -impl<'a, T: std::fmt::Display> Padded<'a, T> { - fn new(left: &'a str, content: &'a T, right: &'a str) -> Self { - Padded { - left, - content, - right, - } - } -} - -impl<'a> Padded<'a, String> { - fn from_string(left: &'a str, content: &'a String, right: &'a str) -> Self { - Padded { - left, - content, - right, - } - } -} - -impl std::fmt::Display for Padded<'_, T> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut result = String::new(); - let content = self.content.to_string(); +) -> impl std::fmt::Display + 'a { + std::fmt::from_fn(move |f| { + let content = content.to_string(); if let Some(char) = content.chars().next() { if !char.is_whitespace() { - result.push_str(self.left); + f.write_str(left)?; } } - result.push_str(&content); + f.write_str(&content)?; if let Some(char) = content.chars().last() { if !char.is_whitespace() { - result.push_str(self.right); + f.write_str(right)?; } } - write!(f, "{result}") - } + Ok(()) + }) } From 3a6abe92a69a46e9070446a5ee5c4bb5b6740cbf Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 21 Apr 2026 15:25:09 -0400 Subject: [PATCH 54/70] Apply the available version cutoff for tests to resolution (#19099) This resolves the root issue encountered in #19097 --- crates/uv-resolver/src/resolver/mod.rs | 6 ++- crates/uv-resolver/src/resolver/provider.rs | 57 +++++++++++++-------- crates/uv-resolver/src/version_map.rs | 44 ++++++++++++---- crates/uv/tests/it/lock.rs | 12 +++-- 4 files changed, 80 insertions(+), 39 deletions(-) diff --git a/crates/uv-resolver/src/resolver/mod.rs b/crates/uv-resolver/src/resolver/mod.rs index 46e35cf19edeb..fd69b71abaf68 100644 --- a/crates/uv-resolver/src/resolver/mod.rs +++ b/crates/uv-resolver/src/resolver/mod.rs @@ -2763,7 +2763,9 @@ impl ResolverState ResolverState= exclude_newer.as_millisecond() + upload_time >= included_version_cutoff.as_millisecond() }) }) }; diff --git a/crates/uv-resolver/src/resolver/provider.rs b/crates/uv-resolver/src/resolver/provider.rs index 82bb5009d4ec5..51ca63123613b 100644 --- a/crates/uv-resolver/src/resolver/provider.rs +++ b/crates/uv-resolver/src/resolver/provider.rs @@ -10,6 +10,7 @@ use uv_distribution_types::{ use uv_normalize::PackageName; use uv_pep440::{Version, VersionSpecifiers}; use uv_platform_tags::Tags; +use uv_static::EnvVars; use uv_types::{BuildContext, HashStrategy}; use crate::ExcludeNewer; @@ -117,6 +118,7 @@ pub struct DefaultResolverProvider<'a, Context: BuildContext> { allowed_yanks: AllowedYanks, hasher: HashStrategy, exclude_newer: ExcludeNewer, + available_version_cutoff: Option, index_locations: &'a IndexLocations, build_options: &'a BuildOptions, capabilities: &'a IndexCapabilities, @@ -144,6 +146,9 @@ impl<'a, Context: BuildContext> DefaultResolverProvider<'a, Context> { allowed_yanks, hasher: hasher.clone(), exclude_newer, + available_version_cutoff: std::env::var(EnvVars::UV_TEST_AVAILABLE_VERSION_CUTOFF) + .ok() + .and_then(|value| value.parse().ok()), index_locations, build_options, capabilities, @@ -189,27 +194,37 @@ impl ResolverProvider for DefaultResolverProvider<'_, Con Ok(results) => Ok(VersionsResponse::Found( results .into_iter() - .map(|(index, metadata)| match metadata { - MetadataFormat::Simple(metadata) => VersionMap::from_simple_metadata( - metadata, - package_name, - index, - self.tags.as_ref(), - &self.requires_python, - &self.allowed_yanks, - &self.hasher, - self.effective_exclude_newer(package_name, index), - flat_index - .and_then(|flat_index| flat_index.get(package_name)) - .cloned(), - self.build_options, - ), - MetadataFormat::Flat(metadata) => VersionMap::from_flat_metadata( - metadata, - self.tags.as_ref(), - &self.hasher, - self.build_options, - ), + .map(|(index, metadata)| { + let included_version_cutoff = + self.effective_exclude_newer(package_name, index); + let available_version_cutoff = included_version_cutoff + .is_none() + .then_some(self.available_version_cutoff) + .flatten(); + + match metadata { + MetadataFormat::Simple(metadata) => VersionMap::from_simple_metadata( + metadata, + package_name, + index, + self.tags.as_ref(), + &self.requires_python, + &self.allowed_yanks, + &self.hasher, + included_version_cutoff, + available_version_cutoff, + flat_index + .and_then(|flat_index| flat_index.get(package_name)) + .cloned(), + self.build_options, + ), + MetadataFormat::Flat(metadata) => VersionMap::from_flat_metadata( + metadata, + self.tags.as_ref(), + &self.hasher, + self.build_options, + ), + } }) .collect(), )), diff --git a/crates/uv-resolver/src/version_map.rs b/crates/uv-resolver/src/version_map.rs index e489c8aa07cea..815e2d45891e1 100644 --- a/crates/uv-resolver/src/version_map.rs +++ b/crates/uv-resolver/src/version_map.rs @@ -52,7 +52,8 @@ impl VersionMap { requires_python: &RequiresPython, allowed_yanks: &AllowedYanks, hasher: &HashStrategy, - exclude_newer: Option, + included_version_cutoff: Option, + available_version_cutoff: Option, flat_index: Option, build_options: &BuildOptions, ) -> Self { @@ -128,7 +129,8 @@ impl VersionMap { allowed_yanks: allowed_yanks.clone(), hasher: hasher.clone(), requires_python: requires_python.clone(), - exclude_newer, + included_version_cutoff, + available_version_cutoff, }), } } @@ -189,11 +191,11 @@ impl VersionMap { } } - /// Return the effective `exclude-newer` cutoff for this version map, if any. - pub(crate) fn exclude_newer(&self) -> Option<&Timestamp> { + /// Return the included-version cutoff for this version map, if any. + pub(crate) fn included_version_cutoff(&self) -> Option<&Timestamp> { match &self.inner { VersionMapInner::Eager(_) => None, - VersionMapInner::Lazy(lazy) => lazy.exclude_newer.as_ref(), + VersionMapInner::Lazy(lazy) => lazy.included_version_cutoff.as_ref(), } } @@ -398,8 +400,11 @@ struct VersionMapLazy { /// The set of compatibility tags that determines whether a wheel is usable /// in the current environment. tags: Option, - /// Whether files newer than this timestamp should be excluded or not. - exclude_newer: Option, + /// Files newer than this timestamp are considered excluded, i.e., that they cannot be selected by the + /// resolver. + included_version_cutoff: Option, + /// Files newer than this timestamp are considered unavailable, i.e., that they do not exist. + available_version_cutoff: Option, /// Which yanked versions are allowed allowed_yanks: AllowedYanks, /// The hashes of allowed distributions. @@ -454,24 +459,41 @@ impl VersionMapLazy { for (filename, file) in files.all() { // Support resolving as if it were an earlier timestamp, at least as long files have // upload time information. - let (excluded, upload_time) = if let Some(exclude_newer) = &self.exclude_newer { + let (excluded, upload_time) = if let Some(included_version_cutoff) = + &self.included_version_cutoff + { match file.upload_time_utc_ms.as_ref() { - Some(&upload_time) if upload_time >= exclude_newer.as_millisecond() => { + Some(&upload_time) + if upload_time >= included_version_cutoff.as_millisecond() => + { trace!( - "Excluding `{}` (uploaded {upload_time}) due to exclude-newer ({exclude_newer})", + "Excluding `{}` (uploaded {upload_time}) due to exclude-newer ({included_version_cutoff})", file.filename ); (true, Some(upload_time)) } None => { warn_user_once!( - "{} is missing an upload date, but user provided: {exclude_newer}", + "{} is missing an upload date, but user provided: {included_version_cutoff}", file.filename, ); (true, None) } _ => (false, None), } + } else if let Some(available_version_cutoff) = &self.available_version_cutoff { + match file.upload_time_utc_ms.as_ref() { + Some(&upload_time) + if upload_time >= available_version_cutoff.as_millisecond() => + { + trace!( + "Excluding `{}` (uploaded {upload_time}) due to available version cutoff ({available_version_cutoff})", + file.filename + ); + (true, Some(upload_time)) + } + _ => (false, None), + } } else { (false, None) }; diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index cc4ca268c175f..2e6b7cd405422 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -23385,6 +23385,7 @@ async fn lock_keyring_explicit_always() -> Result<()> { .join("keyring_test_plugin"), ) .env_remove(EnvVars::UV_EXCLUDE_NEWER) + .env_remove(EnvVars::UV_TEST_AVAILABLE_VERSION_CUTOFF) // (from `echo "keyring==v25.6.0" | uv pip compile - --no-annotate --no-header -q`) .arg("jaraco-classes==3.4.0") .arg("jaraco-context==6.0.1") @@ -23471,9 +23472,10 @@ async fn lock_keyring_credentials_always_authenticate_fetches_username() -> Resu .join("packages") .join("keyring_test_plugin"), ) - // We need a newer version of keyring that supports `--mode`, so unset `EXCLUDE_NEWER` and - // pin the dependencies + // We need a newer version of keyring that supports `--mode`, so unset the timestamp + // cutoffs and pin the dependencies. .env_remove(EnvVars::UV_EXCLUDE_NEWER) + .env_remove(EnvVars::UV_TEST_AVAILABLE_VERSION_CUTOFF) // (from `echo "keyring==v25.6.0" | uv pip compile - --no-annotate --no-header -q`) .arg("jaraco-classes==3.4.0") .arg("jaraco-context==6.0.1") @@ -33574,11 +33576,11 @@ fn lock_exclude_newer_package_disable() -> Result<()> { [[package]] name = "idna" - version = "3.12" + version = "3.6" source = { registry = "https://pypi.org/simple" } - sdist = { url = "https://files.pythonhosted.org/packages/22/12/2948fbe5513d062169bd91f7d7b1cd97bc8894f32946b71fa39f6e63ca0c/idna-3.12.tar.gz", hash = "sha256:724e9952cc9e2bd7550ea784adb098d837ab5267ef67a1ab9cf7846bdbdd8254", size = 194350, upload-time = "2026-04-21T13:32:48.916Z" } + sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", size = 175426, upload-time = "2023-11-25T15:40:54.902Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/53/b2/acc33950394b3becb2b664741a0c0889c7ef9f9ffbfa8d47eddb53a50abd/idna-3.12-py3-none-any.whl", hash = "sha256:60ffaa1858fac94c9c124728c24fcde8160f3fb4a7f79aa8cdd33a9d1af60a67", size = 68634, upload-time = "2026-04-21T13:32:47.403Z" }, + { url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f", size = 61567, upload-time = "2023-11-25T15:40:52.604Z" }, ] [[package]] From 6e3cadefead078519b0184f4b3e45f53feeb2e83 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 14:28:27 -0500 Subject: [PATCH 55/70] Update Rust crate rustls to v0.23.38 (#19059) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [rustls](https://redirect.github.com/rustls/rustls) | workspace.dependencies | patch | `0.23.37` → `0.23.38` | --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/astral-sh/uv). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8984bd3b3b063..784d8bfb1ee9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4199,9 +4199,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.37" +version = "0.23.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" dependencies = [ "aws-lc-rs", "once_cell", From 51dc3ffd7b7a65fd28091ee140088f8414cccb8e Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 21 Apr 2026 23:14:08 -0400 Subject: [PATCH 56/70] Disable Python version updates in `test-integration.yml` (#19107) These should not be bumped by automation. Co-authored-by: Claude --- .github/renovate.json5 | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index e9844d088d106..b62949e0c8e83 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -80,6 +80,14 @@ description: "Disable Python version updates in test-system.yml", enabled: false, }, + { + // Exclude test-integration.yml from Python version updates, as it intentionally + // tests specific Python versions and should not be auto-updated. + matchFileNames: [".github/workflows/test-integration.yml"], + matchDepNames: ["python"], + description: "Disable Python version updates in test-integration.yml", + enabled: false, + }, { matchFileNames: [".github/workflows/test-system.yml"], extends: ["schedule:monthly"], From 22d071e17b70555d986fc8fbef78f920391682ca Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 03:24:33 +0000 Subject: [PATCH 57/70] Update Rust crate fastrand to v2.4.1 (#19068) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [fastrand](https://redirect.github.com/smol-rs/fastrand) | workspace.dependencies | minor | `2.3.0` → `2.4.1` | --- ### Release Notes
smol-rs/fastrand (fastrand) ### [`v2.4.1`](https://redirect.github.com/smol-rs/fastrand/blob/HEAD/CHANGELOG.md#Version-241) [Compare Source](https://redirect.github.com/smol-rs/fastrand/compare/v2.4.0...v2.4.1) - Fix build failure with `js` feature. ([#​125](https://redirect.github.com/smol-rs/fastrand/issues/125)) ### [`v2.4.0`](https://redirect.github.com/smol-rs/fastrand/blob/HEAD/CHANGELOG.md#Version-240) [Compare Source](https://redirect.github.com/smol-rs/fastrand/compare/v2.3.0...v2.4.0) - Bump MSRV to 1.63. ([#​104](https://redirect.github.com/smol-rs/fastrand/issues/104)) - Improve quality of f32/f64 generation. ([#​103](https://redirect.github.com/smol-rs/fastrand/issues/103)) - Add `f{32,64}_inclusive` and `Rng::f{32,64}_inclusive`. ([#​103](https://redirect.github.com/smol-rs/fastrand/issues/103)) - Make `Rng::with_seed` const. ([#​107](https://redirect.github.com/smol-rs/fastrand/issues/107)) - Update `getrandom` to 0.3. ([#​104](https://redirect.github.com/smol-rs/fastrand/issues/104))
--- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/astral-sh/uv). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 784d8bfb1ee9e..0caf8292d51e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1542,9 +1542,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "fdeflate" From 641d7d0e174d3cf5ce0256973fdc2476ee5c9c21 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 21:13:42 +0000 Subject: [PATCH 58/70] Bump rand from 0.8.5 to 0.8.6 (#19119) Bumps [rand](https://github.com/rust-random/rand) from 0.8.5 to 0.8.6.
Changelog

Sourced from rand's changelog.

[0.8.6] - 2026-04-14

This release back-ports a fix from v0.10. See also #1763.

Changes

  • Deprecate feature log (#1772)

#1763: rust-random/rand#1763 #1772: rust-random/rand#1772

  • Drop the experimental simd_support feature.
Commits
  • 5309f25 0.8.6 (#1772): update for recent nightly rustc and backport #1764
  • 1126d03 When testing rustc 1.36, use compatible dependencies.
  • 143b602 Add Cargo.lock.msrv.
  • 9be86f2 Fix cross build test.
  • 5e0d50d Drop simd_support.
  • 8ff02f0 Upgrade cache action.
  • 4ad0cc3 Don't test for unsupported target architecture.
  • 258e6d0 Address warning.
  • 9f0e676 Mark some internal traits as potentially unused.
  • 6f123c1 Workaround never constructed and never used warning.
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=rand&package-manager=cargo&previous-version=0.8.5&new-version=0.8.6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/astral-sh/uv/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0caf8292d51e4..ea1fb8ddf20f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2859,7 +2859,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" dependencies = [ - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -2946,7 +2946,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand 0.8.5", + "rand 0.8.6", "smallvec", "zeroize", ] @@ -3666,9 +3666,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", "rand_chacha 0.3.1", From ca5560c4675ae269042dc49337f8a14e03e3aeee Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 22 Apr 2026 17:31:35 -0400 Subject: [PATCH 59/70] Remove Ubuntu snapshot pinning from CI workflows (#19120) Reverts https://github.com/astral-sh/uv/pull/19032/changes Alas, the snapshot server appears to be incredibly unreliable :( Co-authored-by: Claude --- .github/workflows/test-integration.yml | 3 +-- .github/workflows/test.yml | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test-integration.yml b/.github/workflows/test-integration.yml index d821f01313d2d..6bc035bd62e1b 100644 --- a/.github/workflows/test-integration.yml +++ b/.github/workflows/test-integration.yml @@ -14,7 +14,6 @@ env: CARGO_TERM_COLOR: always PYTHON_VERSION: "3.12" RUSTUP_MAX_RETRIES: 10 - UBUNTU_SNAPSHOT: "20260301T000000Z" jobs: integration-test-nushell: @@ -199,7 +198,7 @@ jobs: run: | sudo dpkg --add-architecture armhf echo 'Acquire::Retries "3";' | sudo tee /etc/apt/apt.conf.d/80-retries > /dev/null - sudo apt install -y --update --snapshot ${UBUNTU_SNAPSHOT} libc6:armhf libgcc-s1:armhf + sudo apt install -y --update libc6:armhf libgcc-s1:armhf - name: "Prepare binary" run: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 25f8fd214305f..d1e1fe9b1e840 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,6 @@ env: CARGO_TERM_COLOR: always PYTHON_VERSION: "3.12" RUSTUP_MAX_RETRIES: 10 - UBUNTU_SNAPSHOT: "20260301T000000Z" jobs: # We use the large GitHub actions runners @@ -54,7 +53,7 @@ jobs: - name: "Install secret service" run: | echo 'Acquire::Retries "3";' | sudo tee /etc/apt/apt.conf.d/80-retries > /dev/null - sudo apt install -y --update --snapshot ${UBUNTU_SNAPSHOT} gnome-keyring + sudo apt install -y --update gnome-keyring - name: "Start gnome-keyring" # run gnome-keyring with 'foobar' as password for the login keyring @@ -64,7 +63,7 @@ jobs: - name: "Create btrfs filesystem" run: | - sudo apt install -y --update --snapshot ${UBUNTU_SNAPSHOT} btrfs-progs + sudo apt install -y --update btrfs-progs truncate -s 1G /tmp/btrfs.img mkfs.btrfs /tmp/btrfs.img sudo mkdir /btrfs From 1641a884410540528cfba82836273867cf116c31 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 23 Apr 2026 06:48:54 -0400 Subject: [PATCH 60/70] Pin the platform during reproducible trampoline builds (#18811) This allows builds to succeed on an aarch64 host, though it's going to be very slow :/ Closes https://github.com/astral-sh/uv/issues/18809 --- .../uv-trampoline-aarch64-console.exe | Bin 46592 -> 46592 bytes .../trampolines/uv-trampoline-aarch64-gui.exe | Bin 47616 -> 47616 bytes .../uv-trampoline-i686-console.exe | Bin 39424 -> 39424 bytes .../trampolines/uv-trampoline-i686-gui.exe | Bin 39936 -> 39936 bytes .../uv-trampoline-x86_64-console.exe | Bin 46592 -> 46592 bytes .../trampolines/uv-trampoline-x86_64-gui.exe | Bin 47104 -> 47104 bytes scripts/build-trampolines.sh | 6 ++++++ 7 files changed, 6 insertions(+) diff --git a/crates/uv-trampoline-builder/trampolines/uv-trampoline-aarch64-console.exe b/crates/uv-trampoline-builder/trampolines/uv-trampoline-aarch64-console.exe index a9cbc2d3bf36d0806e4676188e6cb33155783701..deecb3b5c9b096b5784db270309ba77d8776a58b 100755 GIT binary patch delta 576 zcmWNOT}YE*6vxkbUfXojx!1Wb&5z8cSdxWiMro9#rHEQ7sY@%15~_;`>n5fU1-TD6 zqeTUq7fHN=hL3$vdLejMRE7!N*rp>37b;}9@}uYC@c*B4IRD?paYUFS!k+(D;R9M$ zT|+-zP~XL){Fhpd=%eM>emp?EaTz#4V{wmhknEZq9HKj#R-+Ta8KKZmfjhUMSLtT zU}F{&ov*vdstxq6u7rJABvpL|+ndaD>iZk`-ApSjMXWG^cG^F%WFz0O zmm=o!(pCeqRUMsZYhtex_+Z;KVgq-byN#yxh#=}?mMU*-2%<$0t;&3avfABP$%oom zB0Kbxzr5ZdVHw}jxq@sgg{R*7XP{zdG1WLVWOAD6s?dP5LTE0{IVZ{Qw$P+2jV`@+I$>2@a*h%aOh=l zfKAlQrXv$E-*dyf)?c9B=1m6}71Pa`0NF3Zi?#xkKoH&wEfD=kHo$)Vw68hwKdQRg ABLDyZ delta 584 zcmWlSTS${}7{=fC|JkNf=jPn@O`9{(nJ1Dk(gL$GI)SMjEOjVmQ9?yXSvN9;h`?_% zUc;y;y-4B{YV}&#!3%?+6I2vJL9*${3za6MXo>c9@$fv)#gCtNT6m{L;I|G(Xij$q z-_bGsWjxC>dOhMRnu{L56Vw;u!X+AsxryJ&W5~iEbjeU}9YW|1Np=-uOL;*8HK=Xo zMMJD4UIXili4wh(v*H8Mr*5Oi>pHy35AAWtCms8ugk_nYJJW?>nZ}&W_=|U^w#H!tkL2{a zMQ0)%tvoIYtn|LJSimgls$8PN!dX@Ql2~n{j|YdXS0XDtt|e*qo)r!Rr6kRjqMqlz zL$N3_BXqyoEwUp#TD?hNC!1>~5esRt_LeA$r=Ge!Vla-@d91Whw}C=+b}I8c6M0Fz z;weG=z$Nt-6hrZ}zoAAPHu8gpF~oWHHMXHGdq%UGqGorUThXktW_75x8A@;R<1{~N z5((nSBtL0smav)Ev@W2?c5uqYzZR-i?3Aw<$fj(h(^|h(Qt6svqBdnKO)6jMw%j`nfio551;w zY5S)RcX+}GK~tzbEZV~p6I^kpK%Xf)0Wux~l)3ZW6u?fidxD_#gCX;b^;N^#)4Eb{On`VcS!Jh zIiaNqoiv!0)kCR$oK8~bl*ft=TBp+5vQ&b;MAUh#b>A)WkN((x_r1@3e)sp>`#jfo zR`Q*dUQf``JGwWeY(1^iPs1VFr60r(X`5jIHdCKrAKUFQtMCw+j89;#*k+7DJV@;$ z_Fxswi&gLv-4pvfeoG_bCg5?J99M?>NsUWFA$G-;Nq80M6ZYsn256zKg!iz4YDYbS z^Tg1oFOZr&W=hvB%D#xua!v++Izrb@$=V-~ll?(C$zMOE^|}2vXbG?#lr8=-61|`s zBR(>f#A1!Gk9}E^4qC*aD=s1?(#Pq)OJ^^Lx9w|X%o2Iy9eUk&iJY~k&L(`>n|0_E z@%fuj=Qc&0DPq^8eJC9tEtXDsTE-0OyXJYRca(U1S_R@p(dU?;M<1CANcwt^b{E{t z+kY)srHg;%Ot>|W>I?-(IYYaJcg7mT8|e5%Ss(6TWcwgzM+a<2LOz2)XC^19+g&#di;CE}hr4Tx(+sEE_%lD#+;JE)*|oY9u7 z+6_MU8uAtwrS<+6Zmqfk5|t59+YYcO9pH7Co1~V<_}q8Um&HY<0~`_YPB*JT-Zz8t z+-0~!G`hc#FpWwVE4Y;&SbVE-bE1m8zC;{d+$~Gr4v6ROkR(f^fs1`Yrv@cs!#O!A zdq}qVpQShMT$6YAK)Cfhw%O6A-$cUM*2Z#B+GS;|$Fpdub+DDO93ZFZQ#q+8N51p(R~{ zsdj*7`_y>KFSX!ga+NC5seZbvwAE;~sCJ{zJ(u)LZ^XUiT6$q>;aGJ@Q>P_MM|E!> zQ?dP`l-!_QoIU&Djl|@Fx|?dt6nueH{$E6Il(kK|ln`BPW3`^BS}$)5GEqRb zW*?TgIBeI)fEtv?`YY(+Ws2R#s*iDRBlP>@qYT?GGq~x)WvN(1$CqUy2rR!5rJr;( zqoN3Jqh~7S;9>fvBGc4;Ix2XW`vZz!u9)h63Ag^J-J5PbXH51_6a~xU5Z|TQl?(Gy zc#&oKH}KQ}gteMj%gOs?l+ z4(34UY$}B4QrH^d3Q&S484u8Sk1~E02W({}k%hU|f0O9dj2114wKLoep$_tT6kJMA zdA^qVPf|tIgE)tRRaZ*|r|9!SJ2|}Z{8U`t<88%Xg$FxsAXBXsr&CsKDoz%z+9sKw@X`DBT7G6#+V#@`m^D?J{Sj@ebLi3` z{fOwPdlGY3{>`QD)l&J~+Dy^n3_LBPGtcZlcS(EEK6j5cL7P1m+Oe+sTHlF?_8xnQ zrUujuBdD9$r`h#V65n~Vf8B6QL@~#(;(O_)^}Q*#^H~P1&d_O2_mE`lFXvGbSPp6- zEOBHF0EQJJzN5uDryuq8+F$@VBNONnCDzt}N**e}jBjze-Z zIcR=UEG0KtX(eM@(=Ch>=~UAZ+Ns*;NOKx_)oC=)yq?}|PN9A3m1NkGO1)~9ux~LU zxoJL`C`+`_K)h&cH={WRe4|a^gDd&>qu%wU?&QRuMAwTYF%NZ&2H5@6w?oo_etk7j4~cka2rd(Q7W z=iKYN%zc;nkrW;7@}%Ny(~4ORJ!>D6STGA%&jD^53`y2tSV{{v&k5}NGlR8<$PP=E zV1*uorgTi!_3(%eKhoU>pQ5T8fju~+Uk0nu5%&(+qj8OJ09VI92aCi|d^|t^M-AQJ zz|I62`tW$di|{&DB+h_0@tMR*ODnMUuw)BDl2cqE5h1d-2{o~jgM<$lyLIJ%MFMC@ zIR;huamv$>Ef!Du62u9UI}y$Zd*-X07g)uo)BgZU@p0C#Tvo-~_6-v35&5}AdVY5r z?kG$bzF9kUP%n%HTL3;3+Y9@FFENRgb9P9u6bEj7kvk`gXK$+kI4=f@X6T_CO%CMz zGQQ$iMCXPajXL8uzefV045u2NB_BfyGQS_FUO#AznNT6;yOO+E?Qzv69vKTwU3N1=?yw7z%_LY{Hek4ZJdtaIo zrgQT!-&F~ph*sB^9ClzunGC)7MA?0YOsfL4UoK9U^-5enB=$YPx#f>IYVC znQK&TU_MuYZu?7O%W=%Y{s3R8yxV%L3)w z0Xj_fL2hl)hW2Y(M^hB0D^bpDru1P`r3@!gq5rM;MrB9gQDbbggVcIrYTM~xm;%v2 z&iaf~ama3tXG&Pg47TD^RkD4-U?gysdK(RYe@ZOEOV=Vifyb*dU;}>PKs_z6| zs>k^?B`}81*DQf?JX4cn`sG4Q@I>s3Xsne@9Ty{kAGEpY)l)`maJg{QCIP&Mi&rhr zUr3wUJ=sKer--v2U~JfKn0~G38?P083!Yph!%aA_>TxK;>eVt&x+t1gyMTPB*W_3a z(`Sz4G4*l=Q}JrV7L9cerg*IIBj$SK++r!xwL0RqsFs|C)XStzp7nopR132SuE=-Z1Y%f|Ks(y zcT;?-&MCWF6UCHl15;ihpJq4nG#Y(#P}30QM1>QFgzv|B8~dj=(T z!Y_Vry$-P4Z^L|lB3k`L_z2-i|8(5#pNvoX=izz(SNO6r75m$!VV<%M2ixwz_uHo6 zJIeLQcgfhVnAtFnpgY7(+*Qes;%Agip9p!j0r`hZlaiPm}vZWJuZB4Y39AxUZSW9D|B``vTS zJ>PfF?b>6s_82|#M?%V^O{9)Of_H?@XClY#XsZa08x`8sFN|6(W;S^_$8GCy6PIbd zg#ng~fPSj~Mz~&7oL|@uPJB&BnTF$p!J>AH&@Jl4EYL&yfvJ0Br;)oCQyCw2fL$VCB+-K5`AE7S58(wpt zO;1_ifV-Kf;emU$$4dyX^MS&E@Yu|BQkKVcr;zSU$L@BtsrR)YBb+wQpu-9ne} zA7Q}E!k$Wpyk8-p@i{>Mk|{O$%p&A2+?>3cR6?3>B+YsZ8+>=ujC6_jYa}EBKJ`x} z7hu?bANgK-J7D2BTD?fR5){VMyd)S7i=!_LNHJ3lM=ckj$W%f!&|&JON3~MJ0$)l3pgAd>v`Q`uGYA<0OY$@tdlssbL!D=X ziDRqaQ1XgN(pR!vTcr)5>)p%e*_u^*apocTrzC1$J&(EDx1$P>qq$_g4>H%wmLR@7 z*4A97xTuG!lw5L0x{@MMI^#>&wK&OnxiS$Cmlt>Yd3VWjg3NK8dZ)h!$6*;%SOyKd zDzz^0BIHBpPW>GLNlx8K-P1c!a%tKqVUR_f1jv|5=}Gt^qmw*>+RQE=XCGX@OzUf} z(k>%i!Y~Tbja9>Pvw@VrUb8O#udT8yEW-e4+Cax)S%`p}CmfrL;MTjNeJ$E6*g3R` zyR~;PEqqU7C*Z01C6~wbCa$Ho9#-Uedo4eP8y9PMF-WwigyC|Ib->$MleL~>D_e0T zn>XHZQkU9~IqG9~YE~TVA9L_jQXCZVVxr=p2OX@0fvm~o2)Hf{bm>))&11+v{|3n^ zNSe_itzMc(^d%1h2rCIYVjmh?@^|pa3Dm4ZS9WBtMR9Cq2O&NuglvWU9KX058iZo?-7xsvPp;Y2*d9&0;lz1v(5L0$zPms;NQjCexi4uB+VGFwr-PV7Q zE%2vYpGjV*n*<&uzHtgJ^2Qyyu^wp7eKTwmawGJh9z}%Ij_?%=e<538qeb4Us3oZM z@UM$~&h6)7M@Rs9SyNoq9H&2g--^hD;6uN4K0&dKIXCm7 zNu_xI@u0(|!hpZnZbRrwFYimr4CZbT7b24v6>zIEK^mpsuiQYf%OCzjm8RzdS!Q3nugcT!Z3;-Bgemak$Hy|qFQxn^bK#}LmQk0I!LIi}#`H;Bs1lthzt0ycpaA_yslz@gHxq`Bn5g(Lw!52SKtosox$NMYTsKpt!RHec$TH4 zeelSUWQ_XrZlQ-g`5(SvzqBhK8Q%(2u<^nY?0jtIF!oBvuI@d=r)Mg80&klxIGxkn0aX-vAad^-;9)F&mCQh-L>rC84 zKNIJTzfqi@=qJ5d8A#yt=DDzca|k@%JRY*Elra<5Rs9?8SL@+awGKjRoFJ`w9gY`a z&X(EGP~!_f)aay=EeCuS#9#n_-85a2A>Cj#tc|IR*&cH*Hr|+SEHS=r+-`i=c+S{o z95K2l1SCWzB+TbRIL?S`w(9HjAM2a+UrWjF+$Et^`p@yj4qAd9(_FeWOPsT Vt!Ph!&JbtFHe4{MrS$z0`9C0DT(STF delta 2576 zcmZWqc~n%_8GrZskU?1n1q1|S8F52p1`G-V4hpy-4gv!Vn^+K?xFi*q8cvaMiKDD$iO>Xp=NQeZtMl2^mkm@^@KJJ6HzBM!}RTll~2nmOJ z|9Rw1==0x8cE~$MCJmySQ{-zwp(0&01NuUz(Xam}M~6iXB7c;ZhFb{T7caMssU`Gj zoV<8k&>;HNZTNje8C^M5ei-3SXkm=}d}1dh&Tug@i!{oidK(Rx-mR)RO7LA(?e)+) zv-VyOPeGccVKX(#P^gPClM?8QiXt`g#ONF+&wHOCI#){78L?kcIs2LX?zGv29y$lN zV*`jQ^vCX|z2{(8+$rKLTa3F2c>wq0i-;G@Nk}0E*qJcfdvmxT&^&WKIW9XVr4d5Gl02H; zJPVb{A%m}jNl;e6Uy_Txv(Kn%RfR5?u5q))S(`O`(DM}hQxbHZ7qNExW*w)fdMa7t zgLHQ*k4~$qEymhBnTOa56)8Dnn|v)rru6bD*gj|G;Dg+e09WU9`VBv?s_`mHz+(7kL8>mmp{*nYR3CRVH%3_F zhW0nmUaqV{n^dE_hh^b+s!{|$n1ADZY?n!Bd2bgK=XwoyYQn~)jiMAJSv0KACMaiM zNBRie(H6(bR$QsX4RHv2p}om5AG=c*4{&TCzYlt54V2$Bgd3j>^= zab(v3GVxbP%8|6USzf*{m*|7`ngn*8^|X9}yqJ~xMhr}DaU!ynTK-{o%0~|Z(j9;r zc#;tmxPp6L$vs)@a-ZYIw>yP9>@B15VOI8V zrHY>9sHY=EnfxIlgX}7fq#p$~u z_+Lj=^}f4`3QDv#FX2Mjh+dn)+RSq*iCCr0WNpsjAJI^s^&07bds)}$2kvm(5=tJ! zmzI^oSMyvojCDr4zxrS)WDO z3Z2;}NHzQ+$H#jZ>Ly-9iLVR9MPAsUt8xRHbGC#AA~&oH^|&1&RnNupq@T$yMOs>B z?d56-ay|UZVPA6nY1kKz0OY0z4)qWO{kiyg3yLy4lFurwSfTATYjZ8%Yqx<|CzEZU zw@x4e6j(Qq@o>XhOmKvGVMGUodH(cjBW%bs(gO`}EH9e80pI2&5F13~hmW0nh*Q^x zMFyfOf26MGN9u-gXJJvha_flO#ELjgj3Pu|9>(FU;;<7>Y%yg({Hn&N$ zMAjo|C6h=x&B~Mk*efCnf#L$ZG2rcj7<%p?v=oe_hY!lv3t9)!NBd!K@!0rN`+pt0 zf!hXxn?=c_;nCk8uxT*i&$ew4vUs>xIrm`g7AXmtyv)JH+(G8cQx>nK6*1rTOMZm*l4r+C8@@F*h7b#IZBSs1->`jf53Mb0^ND^Ex3-LX9#L?e_v-tP|2-%QS-GjEk`$S4LR6h@}Lx+iX0WE8-Y@*$j|X^tkAqF<-{@#JL!~jbn{*##KhU@k3*y@gK&E#ybf@uppQapB9-L z`L=$C{(!zg|4%vj-ET-pmA*xPTK}EC#IVe;#n5bMGu$-XHF!lCqT-^mqOL@F%Jbio G$^QXIm{}J9 diff --git a/crates/uv-trampoline-builder/trampolines/uv-trampoline-i686-gui.exe b/crates/uv-trampoline-builder/trampolines/uv-trampoline-i686-gui.exe index b95f3cc4bfb7970e89b122e0458a9927ff3270e7..219c5f6733fc79ec2c82705a0f393c0974f2cbc2 100755 GIT binary patch delta 2801 zcmZuzdsGzH8J|1M$`TfKk%xe=Agtg6Bg?K2V8KNJL5VKPV_A79_)1D+OoX()4#~O1)&_dC0mbPzOEppR$L`GizTfx$ z-Fv6{I9q+3J@jirg2gvUJqydY-;gtKlhcv|@R;i$kKw!`k+m)cw-FzbN3d_idi)P{ z7syRG=zfQM33bXtg=YAd|ry}*0f14(jlA$%V{XrX$s+55Ue)0xdMHD^_Cx2YT3 z+VySipx`E&Z!-+@c3XK^{1D%+o;j4CXQ8#jI3Y*p$M;bUVVJwD{u};!amk{h_Pm;Z zs`yU+68|0FXQ3TK;$FYk2&e;^SgS>B37ABP05?aiBwEM{9LL@tfc1fOL?OBb9V8?Q znuF((H82pogKQJGjZSrAjTz!KZ3M?^Qehw>p8f6z(HLoPBfG>EQF(;ToFTSFHxYJ! zlKAR)tsDF8EqE)Yl+`APcVoN>>m4sXoYct@ceoOpOIpMTy`2r2(Je_<8h%HT`qdhD zzP(@V!w9pL{46E)gaZZ(Sq1|J1E~`y8LbMRo-YtxAY|!GST08|d?CIYpF!Az7vWYy z2vNaMLIc}>5w<6uC7+0SrUpX3hu)+T;s=?@=_C&7lQaB{;~2(T(0h7FlJpn({#>d> z0Ti2pV8o2AWF~B%ahV}4T8#7`um>C)hAEhnv&p$6o)f;u;>^d)dqRpvnM9qE(PEXePT*2fWTXgb>YNnHQ z0XXh%dheVh*{9i@^|FgJ*qCl5zZI{gi!A&3S=gF6Ys6w%aSyI$b_V%glB6UFDWI+m z_GTFDgA)6oq1VKdb9@QOgwDAylLk?mTg!T7bx0BdV)%Y~`4qssNOs>TI6kj~{2r?3 zUkW%Cfc81MK*vU14!Oh+NaZ>ejex}#1Mz@HOKd{=NlD^!FhG_r#Ko;t1k{{x6&KB{ z^FsL>C@-h0Q6|*sdN3_qXVDUPY+33)xZTXOc5jCz1%AGYBUrewi4(L!o{}H1Gju&{ z%O0gW*6Ol6iI((+r;FGN97kOJaVVU!gPp?;_K32BVopew9dw}sKj_OIMRLGnL5O>| zjC8t?F)1$~5BuY|`EGsyJy&BS$8J{u+V2!sE+`=Sn0L(#|25y&+OxyVSZH$#h9WoR z(d5%6dErn*q6PthEirgmBlm z-}k^26j7Km-_<%69~RmuD}_Q`tWd6t!4?`_6%v;}nIZGRJ2ynL7F{`To~^QD3(bIp z+;9>Ni*keF>qlZ}oQDd*e?KNkz1|XXD#hmPkg4-BYVC10ryP^^p;x=v=CsO-9%#;; zN-Cfy_ZmA-11It#$bGnx_qwl~wxiC~r&f8878(g9`BChnW~j{{tFvl`<0<)mOj$*n zaK62z7!%19Beyz_)B9oFhBz;9FCh3P2s_1oaTHlEX#cAg%hPB*zeDDaR`uuSn_Y?QA8z*L!vF;pp%s669zwmIK&C>ptwYnIW*Gg& zq`C7;7OK-O&{yHHZM1g<`ms;7IZH7bwL|oxa6Ioa7EKK9#hLIMb$-1WcuY8OE)GRA z?&Q`jjdDhDw+=oZE?e~^+y`jdyZP}Gvn{e&iOVc>*9&HzPt6K|D_l=%VME~(cJBeW zQ5Zos11Sn7u@F*}MBKnuG==@_W7v+14)|+PRCL{bOad9#PeWy150k3z|D;+iD~n6o zX$piC=MW!QQ=G_N+Xo*P2Nyc`q0XU@`HZxF$wlkEvP_uG@qL0uFmt5SLK}u*&#-^f zFif7jlB(p9E5;7Hkzp2E47!qOcykn%gb)w$&63k@WDZPUI=(9W!xv+!WZ7^`3n!SB za?l_D=#nWhkEgO8S@v9gow5w;%M(&{O8#@%^wclF4!U6NdhyoM4J>=+eJEPKK~vN$ zNd-2(`>uy;zitJ(Jd#s;UbzCErDMnum{i&lb@ic~$7)Up6Y`aOZyxo*9$S&D-_szx ztnsG(1B=(jh!r*x21{1>>O9|*4g7R`7x_A$4(U|j?;c5N%-L) ztW_o&)_LHWp`m?yFb=*5DcpH3mPzN=DCurkRTf01L2cQXz>#>IVMz&iXYgO)F81Tk z$h&wsw3Vf2)!%m|X;dHU)K&~eF9kwmG+>jO^Cvn*j9#yU6-WXh57ik5+g@FMWEkRClr!F@O^5B{(9 z9$s0fnK0eo%-ow{W^gAm9R5D>F*CBvQg3FC2AY}U_!~m~(LnK?jUfciR8EJTmEka0 z`4TMLBtPfF+D%`=zpM1{X;mzQS1TZ^YArr9p`m&-lvIz0$yI@HuKEWU+C16yespuJ z81vS3|CCs{`ndkMM{)kfFynY*{j?j2eTg0>f73WqqG`3sVfw(-V*1o{#dJHF31=8H z;^)N{#BSDa)9=$C(*I3N+v(1Zi8a^_>kNAge#Rih-gTf4q2rg9+jmr@Btsj@D&pjv8y?Mbbv-(H0S2zdKYDP5+pA?|%1pzxDTh z_f^;P)%E=G*9ZxaUney@Bx|GN{d_p?&8E`ugg&v^dvc%6YN00=aom=(9#XbWD|XQ| z1a$KHzlk>r3g<6q29;15QLN!OvAdwzDz-^_DN7x5E#biizMxtEDBX$*GU1@mLJq4~87z_8Fx%}8xe9vq@rc!K9JeE1=h>0iuP9QfPD8$tGISAQ zw@oS4h0r)4?i1t*?s+`=GYaXuO=|bwd?Vn&pb_%_i^NP0jkIX6u$8J;XM#Rs2Eh zve2l0`H*%60gc~2J}XN;;TK29J-Fq+lDq*K0i*e>$FM$NC+U|51s)+J8jc0cB40sI z&^zS5{C4ovL44c{`D$2{z`LhGPgDXwxl1;VFbpCO<>FCR!cR8KP0{-azduD@91}K( zPyG%y#}@JaNpfeb7vbF!<=(h9o~Yr=_#E znx+<~sHq2hWU!DX=rI@wmE(-LDj)L|MCV}+O;|sH>aNIp5~dSgXocH}A*2}k66^TP zR;W!nOJvz?8kmuiP7F|!GJSabXpYOx>p0z~DEfIFz<%xO`qws>^i|e3@rSlD$E_8+Q&uP2Qd49|}*fC?u;KOX=K3tj67N~7ilw^hDIPYyiUL1#cP-7l6 z^qPEXrY|ACgSJ^O5s;Nx+j!5+7NlH;E>`TO(We1skKp;!P(QncJcQ~wt$ttlf}7S=zU!E+ovu7JGP@R935B*%yzLX%W*e!qY6k47#cp)jgFeCfb`+saBvUL) zwUz!I+H8L&<6u{=-*8`KQnG-g-w=vUYB53&>V$^ex1wIhYKczqF_jQf-7Az%eOCV1 zJ(xbPJ&ZqLMIXPI?2X9&fn-O^0C^iiFCTpo@`LA7NF7AmTQr|~bDaKS@~k<9^Ssk8 z(1p-%5B4fUJteQ$8;Y>Bv>2l2N8r4hK7Xt>4GSig>$DxyaZEU{E)Lap_-cO8%SnHD z(a;=^r-pj5kR6+F)DIjuhOOVYeK!js^x=^gkGVfP_D2x%*ONQ2F@F)y9f6zqQRG`7 z3xdc_2w9L!3cORg2HCH7eWfM z$x2vTki^FyhEEED^0jQwp<+x%LJx5fnqx9)njm&b8p$k>HVgg7?_IFxc<*E~HkrF6 z9L=oQs*wX~7|cQ&Kvy_{Y=Zp45b_)O^}^GGa2`!u5>vJDz^`oxU znHv3gCYv;H*3~Xg;~UFl7@t*|s#A+!v!en32PU{+&3gIvl8rncx(^mC+o*}zt0;MP zvAxsX)v4bDT{Z%|iz0E7=!;H_zTV5+S}jQ7l1(jkSSfZ3^2{mTr=fV+;`hQ{9*6Hx zP#4=LmZL7rCF^0)**F3p7Fx_?l2#`=$#omG4n+a diff --git a/crates/uv-trampoline-builder/trampolines/uv-trampoline-x86_64-console.exe b/crates/uv-trampoline-builder/trampolines/uv-trampoline-x86_64-console.exe index 3e3117c2bb698f67fe69a4ea1d93fff3c51259ac..7f6374e667f8928e6db82e955a168952b87052be 100755 GIT binary patch delta 239 zcmZp8!_@GGX@jmW(%-5bh}Uf-hE{*8#MJ>*n_0C0 delta 235 zcmZp8!_@GGX@jmWla|kBeP2#S#`4XUe%UOHPMeno^|N!T*)lNr^s?I9PPUDT;8Mz_IgkL-D5U+W$(txr~Bk4KkAj3JiE_e S@|`Zp$cs{;UPqF1~C diff --git a/crates/uv-trampoline-builder/trampolines/uv-trampoline-x86_64-gui.exe b/crates/uv-trampoline-builder/trampolines/uv-trampoline-x86_64-gui.exe index 97e6d5cab051fd7e4599d4991e421dd14bc08fd7..bf994a6075537ec6ea747b8ad94c230834468dbb 100755 GIT binary patch delta 278 zcmZqpz|`=8X@jmW(@;5v1qjD(^M^D^Ax4qS zT$M5+lQVk7Cdc)1O?K~9oUGO>KDiFazS%1``8<&B=yjj`z4yvwrhc`__7iv}m-d@X zzR;&JS)<>3@{)ef$wCw4CqM32ooqPa!{j?Xl9Lxq6aca%CWlWnn5;NaZu5qT0crpi CLu73L delta 296 zcmZqpz|`=8X@jmWla|kBeP2#S#`4XUekCl7mYdfF&0yygv1MTJ>1EZmo$MbK!7<4O z%v&>geN-fe0EDBpSs?lzBZsdYSgLRGx0rQI>Q>!qR9pm TBqt|LG?=V7QFHT#i2-T=8|G*| diff --git a/scripts/build-trampolines.sh b/scripts/build-trampolines.sh index 504dce009db15..6331e9f91b70f 100755 --- a/scripts/build-trampolines.sh +++ b/scripts/build-trampolines.sh @@ -13,8 +13,13 @@ OUTPUT_DIR="$REPO_ROOT/crates/uv-trampoline-builder/trampolines" # Read the nightly toolchain from rust-toolchain.toml TOOLCHAIN="$(grep '^channel' "$TRAMPOLINE_DIR/rust-toolchain.toml" | sed 's/.*"\(.*\)"/\1/')" +# Pin to linux/amd64 so the container always matches the x86_64-unknown-linux-gnu +# toolchain hardcoded in the Dockerfile, regardless of the host architecture. +PLATFORM="linux/amd64" + # Build the pinned toolchain image. docker buildx build -t uv-trampoline-builder --load \ + --platform "$PLATFORM" \ -f "$TRAMPOLINE_DIR/Dockerfile" "$TRAMPOLINE_DIR" \ "$@" @@ -22,6 +27,7 @@ docker buildx build -t uv-trampoline-builder --load \ # The working directory must be the trampoline crate so cargo picks up # .cargo/config.toml (which enables build-std). docker run --rm \ + --platform "$PLATFORM" \ -v "$REPO_ROOT:/workspace:ro" \ -v "$OUTPUT_DIR:/output" \ -w /workspace/crates/uv-trampoline \ From 282919c973775d1d1528f97db9d9a69520231c9a Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 23 Apr 2026 10:24:02 -0400 Subject: [PATCH 61/70] Share `pylock.toml` reading and resolution in pip commands (#19125) For some reason, the implementation is duplicated and the `pip sync` one does not support remote files. I think this was an oversight in #17119 since `pip sync` support predated that change (see #12992). This is precursor refactor to #19117 which would need this utility. Co-authored-by: Claude --- crates/uv/src/commands/mod.rs | 1 + crates/uv/src/commands/pip/install.rs | 60 ++++------------- crates/uv/src/commands/pip/sync.rs | 45 ++++--------- crates/uv/src/commands/pylock.rs | 94 +++++++++++++++++++++++++++ crates/uv/tests/it/pip_sync.rs | 60 +++++++++++++++++ 5 files changed, 179 insertions(+), 81 deletions(-) create mode 100644 crates/uv/src/commands/pylock.rs diff --git a/crates/uv/src/commands/mod.rs b/crates/uv/src/commands/mod.rs index 163c0178d2be3..476601ad33522 100644 --- a/crates/uv/src/commands/mod.rs +++ b/crates/uv/src/commands/mod.rs @@ -87,6 +87,7 @@ mod help; pub(crate) mod pip; mod project; mod publish; +mod pylock; mod python; pub(crate) mod reporters; #[cfg(feature = "self-update")] diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs index 38420c46b12cc..2db2176b8e761 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -1,9 +1,8 @@ use std::collections::BTreeSet; -use anyhow::Context; use itertools::Itertools; use owo_colors::OwoColorize; -use tracing::{Level, debug, enabled, info_span, warn}; +use tracing::{Level, debug, enabled, warn}; use uv_cache::Cache; use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder}; @@ -31,8 +30,8 @@ use uv_python::{ }; use uv_requirements::{GroupsSpecification, RequirementsSource, RequirementsSpecification}; use uv_resolver::{ - DependencyMode, ExcludeNewer, FlatIndex, OptionsBuilder, PrereleaseMode, PylockToml, - PythonRequirement, ResolutionMode, ResolverEnvironment, + DependencyMode, ExcludeNewer, FlatIndex, OptionsBuilder, PrereleaseMode, PythonRequirement, + ResolutionMode, ResolverEnvironment, }; use uv_settings::PythonInstallMirrors; use uv_torch::{TorchMode, TorchSource, TorchStrategy}; @@ -45,6 +44,7 @@ use crate::commands::pip::loggers::{DefaultInstallLogger, DefaultResolveLogger, use crate::commands::pip::operations::Modifications; use crate::commands::pip::operations::{report_interpreter, report_target_environment}; use crate::commands::pip::{operations, resolution_markers, resolution_tags}; +use crate::commands::pylock::{read_pylock_toml, resolve_pylock_toml}; use crate::commands::reporters::PythonDownloadReporter; use crate::commands::{ExitStatus, diagnostics}; use crate::printer::Printer; @@ -493,44 +493,7 @@ pub(crate) async fn pip_install( ); let (resolution, hasher) = if let Some(pylock) = pylock { - // Read the `pylock.toml` from disk or URL, and deserialize it from TOML. - let (install_path, content) = - if pylock.starts_with("http://") || pylock.starts_with("https://") { - // Fetch the `pylock.toml` over HTTP(S). - let url = uv_redacted::DisplaySafeUrl::parse(&pylock.to_string_lossy())?; - let client = client_builder.build()?; - let response = client - .for_host(&url) - .get(url::Url::from(url.clone())) - .send() - .await?; - response.error_for_status_ref()?; - let content = response.text().await?; - // Use the current working directory as the install path for remote lock files. - let install_path = std::env::current_dir()?; - (install_path, content) - } else { - let install_path = std::path::absolute(&pylock)?; - let install_path = install_path.parent().unwrap().to_path_buf(); - let content = fs_err::tokio::read_to_string(&pylock).await?; - (install_path, content) - }; - let lock = info_span!("toml::from_str pip install", path = %pylock.display()) - .in_scope(|| toml::from_str::(&content)) - .with_context(|| { - format!("Not a valid `pylock.toml` file: {}", pylock.user_display()) - })?; - - // Verify that the Python version is compatible with the lock file. - if let Some(requires_python) = lock.requires_python.as_ref() { - if !requires_python.contains(interpreter.python_version()) { - return Err(anyhow::anyhow!( - "The requested interpreter resolved to Python {}, which is incompatible with the `pylock.toml`'s Python requirement: `{}`", - interpreter.python_version(), - requires_python, - )); - } - } + let (install_path, lock) = read_pylock_toml(&pylock, &client_builder).await?; // Convert the extras and groups specifications into a concrete form. let extras = extras.with_defaults(DefaultExtras::default()); @@ -549,17 +512,16 @@ pub(crate) async fn pip_install( .cloned() .collect::>(); - let resolution = lock.to_resolution( + resolve_pylock_toml( + lock, &install_path, - marker_env.markers(), + interpreter, + python_version.as_ref(), + python_platform.as_ref(), &extras, &groups, - &tags, &build_options, - )?; - let hasher = HashStrategy::from_resolution(&resolution, HashCheckingMode::Verify)?; - - (resolution, hasher) + )? } else { // When resolving, don't take any external preferences into account. let preferences = Vec::default(); diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index f56f39e65e951..e38843af255fe 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -1,9 +1,9 @@ use std::collections::BTreeSet; use std::fmt::Write; -use anyhow::{Context, Result}; +use anyhow::Result; use owo_colors::OwoColorize; -use tracing::{debug, info_span, warn}; +use tracing::{debug, warn}; use uv_cache::Cache; use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder}; @@ -30,8 +30,8 @@ use uv_python::{ }; use uv_requirements::{GroupsSpecification, RequirementsSource, RequirementsSpecification}; use uv_resolver::{ - DependencyMode, ExcludeNewer, FlatIndex, OptionsBuilder, PrereleaseMode, PylockToml, - PythonRequirement, ResolutionMode, ResolverEnvironment, + DependencyMode, ExcludeNewer, FlatIndex, OptionsBuilder, PrereleaseMode, PythonRequirement, + ResolutionMode, ResolverEnvironment, }; use uv_settings::PythonInstallMirrors; use uv_torch::{TorchMode, TorchSource, TorchStrategy}; @@ -44,6 +44,7 @@ use crate::commands::pip::loggers::{DefaultInstallLogger, DefaultResolveLogger}; use crate::commands::pip::operations::Modifications; use crate::commands::pip::operations::{report_interpreter, report_target_environment}; use crate::commands::pip::{operations, resolution_markers, resolution_tags}; +use crate::commands::pylock::{read_pylock_toml, resolve_pylock_toml}; use crate::commands::reporters::PythonDownloadReporter; use crate::commands::{ExitStatus, diagnostics}; use crate::printer::Printer; @@ -403,26 +404,7 @@ pub(crate) async fn pip_sync( let site_packages = SitePackages::from_environment(&environment)?; let (resolution, hasher) = if let Some(pylock) = pylock { - // Read the `pylock.toml` from disk, and deserialize it from TOML. - let install_path = std::path::absolute(&pylock)?; - let install_path = install_path.parent().unwrap(); - let content = fs_err::tokio::read_to_string(&pylock).await?; - let lock = info_span!("toml::from_str pip sync", path = %pylock.display()) - .in_scope(|| toml::from_str::(&content)) - .with_context(|| { - format!("Not a valid `pylock.toml` file: {}", pylock.user_display()) - })?; - - // Verify that the Python version is compatible with the lock file. - if let Some(requires_python) = lock.requires_python.as_ref() { - if !requires_python.contains(interpreter.python_version()) { - return Err(anyhow::anyhow!( - "The requested interpreter resolved to Python {}, which is incompatible with the `pylock.toml`'s Python requirement: `{}`", - interpreter.python_version(), - requires_python, - )); - } - } + let (install_path, lock) = read_pylock_toml(&pylock, &client_builder).await?; // Convert the extras and groups specifications into a concrete form. let extras = extras.with_defaults(DefaultExtras::default()); @@ -441,17 +423,16 @@ pub(crate) async fn pip_sync( .cloned() .collect::>(); - let resolution = lock.to_resolution( - install_path, - marker_env.markers(), + resolve_pylock_toml( + lock, + &install_path, + interpreter, + python_version.as_ref(), + python_platform.as_ref(), &extras, &groups, - &tags, &build_options, - )?; - let hasher = HashStrategy::from_resolution(&resolution, HashCheckingMode::Verify)?; - - (resolution, hasher) + )? } else { // When resolving, don't take any external preferences into account. let preferences = Vec::default(); diff --git a/crates/uv/src/commands/pylock.rs b/crates/uv/src/commands/pylock.rs new file mode 100644 index 0000000000000..ab715e282f4a0 --- /dev/null +++ b/crates/uv/src/commands/pylock.rs @@ -0,0 +1,94 @@ +//! Shared helpers for reading `pylock.toml` (PEP 751) files and deriving a [`Resolution`] and +//! verifying [`HashStrategy`] from them, used by `uv pip install` and `uv pip sync`. + +use std::path::{Path, PathBuf}; + +use anyhow::Context; +use tracing::info_span; + +use uv_client::BaseClientBuilder; +use uv_configuration::{BuildOptions, HashCheckingMode, TargetTriple}; +use uv_distribution_types::Resolution; +use uv_fs::Simplified; +use uv_normalize::{ExtraName, GroupName}; +use uv_python::{Interpreter, PythonVersion}; +use uv_resolver::PylockToml; +use uv_types::HashStrategy; + +use crate::commands::pip::{resolution_markers, resolution_tags}; + +/// Read a `pylock.toml` from a local path or HTTP(S) URL and parse it. +/// +/// Returns the `install_path` (used to resolve relative package sources in the lock) alongside +/// the parsed [`PylockToml`]. For HTTP(S) sources, the current working directory is used as the +/// install path. +pub(crate) async fn read_pylock_toml( + pylock: &Path, + client_builder: &BaseClientBuilder<'_>, +) -> anyhow::Result<(PathBuf, PylockToml)> { + let (install_path, content) = if pylock.starts_with("http://") || pylock.starts_with("https://") + { + let url = uv_redacted::DisplaySafeUrl::parse(&pylock.to_string_lossy())?; + let client = client_builder.build()?; + let response = client + .for_host(&url) + .get(url::Url::from(url.clone())) + .send() + .await?; + response.error_for_status_ref()?; + let content = response.text().await?; + (std::env::current_dir()?, content) + } else { + let absolute = std::path::absolute(pylock)?; + let install_path = absolute + .parent() + .map(Path::to_path_buf) + .unwrap_or_else(PathBuf::new); + let content = fs_err::tokio::read_to_string(pylock).await?; + (install_path, content) + }; + + let lock = info_span!("toml::from_str pylock.toml", path = %pylock.display()) + .in_scope(|| toml::from_str::(&content)) + .with_context(|| format!("Not a valid `pylock.toml` file: {}", pylock.user_display()))?; + + Ok((install_path, lock)) +} + +/// Verify Python compatibility and convert a parsed [`PylockToml`] into a [`Resolution`] with a +/// verifying [`HashStrategy`]. +pub(crate) fn resolve_pylock_toml( + lock: PylockToml, + install_path: &Path, + interpreter: &Interpreter, + python_version: Option<&PythonVersion>, + python_platform: Option<&TargetTriple>, + extras: &[ExtraName], + groups: &[GroupName], + build_options: &BuildOptions, +) -> anyhow::Result<(Resolution, HashStrategy)> { + if let Some(requires_python) = lock.requires_python.as_ref() { + if !requires_python.contains(interpreter.python_version()) { + return Err(anyhow::anyhow!( + "The requested interpreter resolved to Python {}, which is incompatible with the `pylock.toml`'s Python requirement: `{}`", + interpreter.python_version(), + requires_python, + )); + } + } + + let tags = resolution_tags(python_version, python_platform, interpreter)?; + let marker_env = resolution_markers(python_version, python_platform, interpreter); + + let resolution = lock.to_resolution( + install_path, + marker_env.markers(), + extras, + groups, + &tags, + build_options, + )?; + let hasher = HashStrategy::from_resolution(&resolution, HashCheckingMode::Verify)?; + + Ok((resolution, hasher)) +} diff --git a/crates/uv/tests/it/pip_sync.rs b/crates/uv/tests/it/pip_sync.rs index c45345f805964..cdf3c4bc7393a 100644 --- a/crates/uv/tests/it/pip_sync.rs +++ b/crates/uv/tests/it/pip_sync.rs @@ -8,6 +8,8 @@ use fs_err as fs; use indoc::indoc; use predicates::Predicate; use url::Url; +use wiremock::matchers::{method, path}; +use wiremock::{Mock, MockServer, ResponseTemplate}; use uv_fs::{Simplified, copy_dir_all}; use uv_static::EnvVars; @@ -5975,6 +5977,64 @@ fn pep_751() -> Result<()> { Ok(()) } +#[tokio::test] +async fn pep_751_remote() -> Result<()> { + let context = uv_test::test_context!("3.12"); + + let server = MockServer::start().await; + Mock::given(method("GET")) + .and(path("/pylock.toml")) + .respond_with(ResponseTemplate::new(200).set_body_string(indoc! {r#" + lock-version = "1.0" + created-by = "uv" + requires-python = ">=3.12" + + [[packages]] + name = "anyio" + version = "4.3.0" + sdist = { url = "https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz", upload-time = 2024-02-19T08:36:28Z, size = 159642, hashes = { sha256 = "f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl", upload-time = 2024-02-19T08:36:26Z, size = 85584, hashes = { sha256 = "048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8" } }] + dependencies = [ + { name = "idna" }, + { name = "sniffio" }, + ] + + [[packages]] + name = "idna" + version = "3.6" + sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", upload-time = 2023-11-25T15:40:54Z, size = 175426, hashes = { sha256 = "9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", upload-time = 2023-11-25T15:40:52Z, size = 61567, hashes = { sha256 = "c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f" } }] + + [[packages]] + name = "sniffio" + version = "1.3.1" + sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", upload-time = 2024-02-25T23:20:04Z, size = 20372, hashes = { sha256 = "f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", upload-time = 2024-02-25T23:20:01Z, size = 10235, hashes = { sha256 = "2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2" } }] + "#})) + .mount(&server) + .await; + + let pylock_url = format!("{}/pylock.toml", server.uri()); + + uv_snapshot!(context.filters(), context.pip_sync() + .arg("--preview") + .arg(&pylock_url), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Prepared 3 packages in [TIME] + Installed 3 packages in [TIME] + + anyio==4.3.0 + + idna==3.6 + + sniffio==1.3.1 + " + ); + + Ok(()) +} + /// Avoid erroring for packages that only include wheels, and _don't_ include a wheel for the /// current platform, but are omitted by markers anyway. /// From d46a7b67b0dacdc4d8b989fa66fff90ae06e47fd Mon Sep 17 00:00:00 2001 From: Zsolt Dollenstein Date: Thu, 23 Apr 2026 17:17:32 +0100 Subject: [PATCH 62/70] Fix pip_compile test (#19129) --- crates/uv/tests/it/pip_compile.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/uv/tests/it/pip_compile.rs b/crates/uv/tests/it/pip_compile.rs index 464fa3dbb4d48..67dac87b0ec74 100644 --- a/crates/uv/tests/it/pip_compile.rs +++ b/crates/uv/tests/it/pip_compile.rs @@ -17848,8 +17848,8 @@ fn incompatible_cuda() -> Result<()> { let context = uv_test::test_context!("3.11"); let requirements_in = context.temp_dir.child("requirements.in"); requirements_in.write_str(indoc! {r" - torch==2.6.0+cu126 - torchvision==0.16.0+cu121 + torch==2.2.1+cu121 + torchvision==0.17.1+cu118 "})?; uv_snapshot!(context @@ -17869,8 +17869,8 @@ fn incompatible_cuda() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because torchvision==0.16.0+cu121 depends on system:cuda==12.1 and torch==2.6.0+cu126 depends on system:cuda==12.6, we can conclude that torch==2.6.0+cu126 and torchvision==0.16.0+cu121 are incompatible. - And because you require torch==2.6.0+cu126 and torchvision==0.16.0+cu121, we can conclude that your requirements are unsatisfiable. + ╰─▶ Because torchvision==0.17.1+cu118 depends on system:cuda==11.8 and torch==2.2.1+cu121 depends on system:cuda==12.1, we can conclude that torch==2.2.1+cu121 and torchvision==0.17.1+cu118 are incompatible. + And because you require torch==2.2.1+cu121 and torchvision==0.17.1+cu118, we can conclude that your requirements are unsatisfiable. "); Ok(()) From 992be06f6743c8a057e884050d2e074636b8c713 Mon Sep 17 00:00:00 2001 From: konsti Date: Thu, 23 Apr 2026 15:40:07 -0400 Subject: [PATCH 63/70] Add rust-toolchain.toml to uv-build sdist (#19131) This file was previously missing, causing errors when an older version was preinstalled. Renovate should also pick up the new file. --- .github/workflows/ci.yml | 2 +- crates/uv-build/pyproject.toml | 1 + crates/uv-build/rust-toolchain.toml | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 crates/uv-build/rust-toolchain.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 81abf30ebd794..c4e7ed8a95ccf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -73,7 +73,7 @@ jobs: [[ -z "$file" ]] && continue [[ "$file" =~ \.rs$ ]] && rust_code_changed=1 [[ "$file" == "Cargo.toml" || "$file" == "Cargo.lock" || "$file" =~ ^crates/.*/Cargo\.toml$ ]] && rust_deps_changed=1 - [[ "$file" == "rust-toolchain.toml" || "$file" =~ ^\.cargo/ ]] && rust_config_changed=1 + [[ "$file" == "rust-toolchain.toml" || "$file" == "crates/uv-build/rust-toolchain.toml" || "$file" =~ ^\.cargo/ ]] && rust_config_changed=1 [[ "$file" == "pyproject.toml" || "$file" =~ ^crates/.*/pyproject\.toml$ ]] && python_config_changed=1 [[ "$file" =~ ^\.github/workflows/.*\.yml$ ]] && workflow_changed=1 [[ "$file" == ".github/workflows/build-release-binaries.yml" || "$file" == ".github/workflows/release.yml" ]] && release_workflow_changed=1 diff --git a/crates/uv-build/pyproject.toml b/crates/uv-build/pyproject.toml index daea2b2a660d2..af4a3f60f60fc 100644 --- a/crates/uv-build/pyproject.toml +++ b/crates/uv-build/pyproject.toml @@ -48,6 +48,7 @@ strip = true include = [ { path = "LICENSE-APACHE", format = "sdist" }, { path = "LICENSE-MIT", format = "sdist" }, + { path = "rust-toolchain.toml", format = "sdist" }, ] [tool.uv] diff --git a/crates/uv-build/rust-toolchain.toml b/crates/uv-build/rust-toolchain.toml new file mode 100644 index 0000000000000..9cf4a67c474d4 --- /dev/null +++ b/crates/uv-build/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "1.94.1" From 15118d703d5146b3185d1dcbb928dd306a87b3e4 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Thu, 23 Apr 2026 16:26:39 -0400 Subject: [PATCH 64/70] Drop whoami dependency (#19132) ## Summary We were only using this crate in one place, in a Windows-specific test helper where accessing `%USERNAME%` should work just as well. (This isn't super critical, but it reduces our dependency footprint slightly.) ## Test Plan The existing tests should continue to pass with this change. Signed-off-by: William Woodruff --- Cargo.lock | 41 ------------------------------------ Cargo.toml | 1 - crates/uv/Cargo.toml | 1 - crates/uv/tests/it/export.rs | 2 +- 4 files changed, 1 insertion(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ea1fb8ddf20f7..e878ff473dd82 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3026,30 +3026,12 @@ dependencies = [ "objc2-encode", ] -[[package]] -name = "objc2-core-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" -dependencies = [ - "bitflags 2.11.0", -] - [[package]] name = "objc2-encode" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" -[[package]] -name = "objc2-system-configuration" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7216bd11cbda54ccabcab84d523dc93b858ec75ecfb3a7d89513fa22464da396" -dependencies = [ - "objc2-core-foundation", -] - [[package]] name = "oid-registry" version = "0.8.1" @@ -5888,7 +5870,6 @@ dependencies = [ "uv-workspace", "walkdir", "which", - "whoami", "windows", "wiremock", "zip", @@ -7562,15 +7543,6 @@ dependencies = [ "wit-bindgen", ] -[[package]] -name = "wasite" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66fe902b4a6b8028a753d5424909b764ccf79b7a209eac9bf97e59cda9f71a42" -dependencies = [ - "wasi 0.14.7+wasi-0.2.4", -] - [[package]] name = "wasm-bindgen" version = "0.2.114" @@ -7736,19 +7708,6 @@ dependencies = [ "regex", ] -[[package]] -name = "whoami" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6a5b12f9df4f978d2cfdb1bd3bac52433f44393342d7ee9c25f5a1c14c0f45d" -dependencies = [ - "libc", - "libredox", - "objc2-system-configuration", - "wasite", - "web-sys", -] - [[package]] name = "widestring" version = "1.2.1" diff --git a/Cargo.toml b/Cargo.toml index 083dae3305dce..4ad70d48de4ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -342,7 +342,6 @@ test-log = { version = "0.2.16", features = [ "trace", ], default-features = false } tokio-rustls = { version = "0.26.2", default-features = false } -whoami = { version = "2.0.0" } [workspace.lints.rust] unsafe_code = "warn" diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml index 8b32021571a4b..87853d6cadb05 100644 --- a/crates/uv/Cargo.toml +++ b/crates/uv/Cargo.toml @@ -152,7 +152,6 @@ tar = { workspace = true } tempfile = { workspace = true } tokio-stream = { workspace = true } tokio-util = { workspace = true } -whoami = { workspace = true } wiremock = { workspace = true } zip = { workspace = true } diff --git a/crates/uv/tests/it/export.rs b/crates/uv/tests/it/export.rs index 7516896d943f0..c463bc17e7f21 100644 --- a/crates/uv/tests/it/export.rs +++ b/crates/uv/tests/it/export.rs @@ -1373,7 +1373,7 @@ fn reduce_ssh_key_file_permissions(key_file: &Path) -> Result<()> { Command::new("icacls") .arg(key_file) .arg("/grant:r") - .arg(format!("{}:R", whoami::username()?)) + .arg(format!("{}:R", std::env::var("USERNAME")?)) .assert() .success(); } From dea98154be549f79ab9eac873c8eaf025eac6734 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Thu, 23 Apr 2026 17:18:41 -0400 Subject: [PATCH 65/70] uv-build: fixup classifiers (#19130) ## Summary Minor: drop some deprecated license classifiers, add a 3.14 classifier. ## Test Plan NFC, metadata only. Signed-off-by: William Woodruff --- crates/uv-build/pyproject.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/uv-build/pyproject.toml b/crates/uv-build/pyproject.toml index af4a3f60f60fc..edc668786a06f 100644 --- a/crates/uv-build/pyproject.toml +++ b/crates/uv-build/pyproject.toml @@ -13,8 +13,6 @@ classifiers = [ "Environment :: Console", "Intended Audience :: Developers", "Operating System :: OS Independent", - "License :: OSI Approved :: MIT License", - "License :: OSI Approved :: Apache Software License", "Programming Language :: Python", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", @@ -22,6 +20,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3 :: Only", "Topic :: Software Development :: Quality Assurance", "Topic :: Software Development :: Testing", From 5f461d69a2735c25807cb09939cf18766374c34d Mon Sep 17 00:00:00 2001 From: Tomasz Kramkowski Date: Thu, 23 Apr 2026 17:36:46 -0400 Subject: [PATCH 66/70] Allow `scripts/build-trampolines.sh` to try podman when it's available (#19134) ## Summary Opportunistically use `podman` instead of `docker` when `docker` is not available but `podman` is. This required a few small compatibility adjustments to the `Dockerfile`. ## Test Plan Manually tested locally... --- crates/uv-trampoline/Dockerfile | 6 +++--- scripts/build-trampolines.sh | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/uv-trampoline/Dockerfile b/crates/uv-trampoline/Dockerfile index 09128c626f722..5a5df94772fc3 100644 --- a/crates/uv-trampoline/Dockerfile +++ b/crates/uv-trampoline/Dockerfile @@ -12,7 +12,7 @@ ARG CARGO_XWIN_VERSION=0.21.4 ARG XWIN_SDK_VERSION=10.0.22621 ARG XWIN_CRT_VERSION=14.44.17.14 -FROM ubuntu:26.04@sha256:4095ef613201918336b5d7d00be15d8b09c72ddb77c80bca249c255887a64d87 AS base +FROM docker.io/ubuntu:26.04@sha256:4095ef613201918336b5d7d00be15d8b09c72ddb77c80bca249c255887a64d87 AS base ARG RUST_NIGHTLY ARG UBUNTU_SNAPSHOT @@ -62,6 +62,6 @@ RUN cargo xwin cache xwin \ FROM base -COPY --chmod=a+rwX --from=xwin-cache ${HOME}/xwin-cache ${HOME}/xwin-cache -RUN chmod a+w ${HOME}/xwin-cache +COPY --from=xwin-cache ${HOME}/xwin-cache ${HOME}/xwin-cache +RUN chmod a+rwX ${HOME}/xwin-cache COPY --from=xwin-cache ${CARGO_HOME}/bin/cargo-xwin ${CARGO_HOME}/bin/cargo-xwin diff --git a/scripts/build-trampolines.sh b/scripts/build-trampolines.sh index 6331e9f91b70f..439a326eb5297 100755 --- a/scripts/build-trampolines.sh +++ b/scripts/build-trampolines.sh @@ -5,6 +5,10 @@ set -euo pipefail +if ! command -v docker >/dev/null 2>&1 && command -v podman >/dev/null 2>&1; then + docker() { podman "$@"; } +fi + SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" TRAMPOLINE_DIR="$REPO_ROOT/crates/uv-trampoline" From 51448c2dce894c20054b4f52d5253aafc8e971c9 Mon Sep 17 00:00:00 2001 From: Zsolt Dollenstein Date: Fri, 24 Apr 2026 18:16:25 +0100 Subject: [PATCH 67/70] Use a single codepath for extracting a .tar.zst wheel (#19144) --- Cargo.lock | 2 - Cargo.toml | 1 - .../src/distribution_database.rs | 19 ++++--- crates/uv-extract/Cargo.toml | 2 - crates/uv-extract/src/stream.rs | 56 ------------------- 5 files changed, 11 insertions(+), 69 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e878ff473dd82..b9b7402986b41 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6508,7 +6508,6 @@ dependencies = [ "reqwest", "rustc-hash", "sha2", - "tar", "thiserror 2.0.18", "tokio", "tokio-util", @@ -6520,7 +6519,6 @@ dependencies = [ "uv-warnings", "xz2", "zip", - "zstd", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 4ad70d48de4ad..b13cd0fa49031 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -313,7 +313,6 @@ zip = { version = "8.1.0", default-features = false, features = [ "lzma", "xz", ] } -zstd = { version = "0.13.3" } # dev-dependencies assert_cmd = { version = "2.0.16" } diff --git a/crates/uv-distribution/src/distribution_database.rs b/crates/uv-distribution/src/distribution_database.rs index cff84342fd0d6..721e1ea7eb3f7 100644 --- a/crates/uv-distribution/src/distribution_database.rs +++ b/crates/uv-distribution/src/distribution_database.rs @@ -890,16 +890,19 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> { .await .map_err(Error::CacheWrite)?; - // If no hashes are required, parallelize the unzip operation. + // If no hashes are required, extract the wheel without hashing. let (files, hashes) = if hashes.is_none() { - // Unzip the wheel into a temporary directory. - let file = file.into_std().await; let target = temp_dir.path().to_owned(); - let files = tokio::task::spawn_blocking(move || match extension { - WheelExtension::Whl => uv_extract::unzip(file, &target), - WheelExtension::WhlZst => uv_extract::stream::untar_zst_file(file, &target), - }) - .await? + let files = match extension { + WheelExtension::Whl => { + let file = file.into_std().await; + tokio::task::spawn_blocking(move || uv_extract::unzip(file, &target)) + .await? + } + WheelExtension::WhlZst => { + uv_extract::stream::untar_zst(file, &target).await + } + } .map_err(|err| Error::Extract(filename.to_string(), err))?; (files, HashDigests::empty()) diff --git a/crates/uv-extract/Cargo.toml b/crates/uv-extract/Cargo.toml index efda3c41082be..a77ee06a8676b 100644 --- a/crates/uv-extract/Cargo.toml +++ b/crates/uv-extract/Cargo.toml @@ -34,14 +34,12 @@ regex = { workspace = true } reqwest = { workspace = true } rustc-hash = { workspace = true } sha2 = { workspace = true } -tar = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } tokio-util = { workspace = true, features = ["compat"] } tracing = { workspace = true } xz2 = { workspace = true } zip = { workspace = true } -zstd = { workspace = true } [features] default = [] diff --git a/crates/uv-extract/src/stream.rs b/crates/uv-extract/src/stream.rs index 9a5c91f8109cb..38295e6916cfd 100644 --- a/crates/uv-extract/src/stream.rs +++ b/crates/uv-extract/src/stream.rs @@ -731,62 +731,6 @@ pub async fn untar_zst( .map_err(Error::io_or_compression) } -/// Unpack a `.tar.zst` archive from a file on disk into the target directory. -/// -/// Returns the list of unpacked files and their sizes. -pub fn untar_zst_file( - reader: R, - target: impl AsRef, -) -> Result, Error> { - let reader = std::io::BufReader::with_capacity(DEFAULT_BUF_SIZE, reader); - let decompressed = zstd::Decoder::new(reader).map_err(Error::Io)?; - let mut archive = tar::Archive::new(decompressed); - archive.set_preserve_mtime(false); - - // The logic below is `Archive::unpack`, with slight simplifications as we know the target is - // a real directory, using our error handling and adding file recording. - let mut files = Vec::new(); - - // Canonicalizing the dst directory will prepend the path with '\\?\' - // on windows which will allow windows APIs to treat the path as an - // extended-length path with a 32,767 character limit. Otherwise all - // unpacked paths over 260 characters will fail on creation with a - // NotFound exception. - let dst = fs_err::canonicalize(&target).unwrap_or(target.as_ref().to_path_buf()); - - // Delay any directory entries until the end (they will be created if needed by - // descendants), to ensure that directory permissions do not interfere with descendant - // extraction. - let mut directories = Vec::new(); - for entry in archive.entries().map_err(Error::io_or_compression)? { - let mut file = entry.map_err(Error::io_or_compression)?; - if file.header().entry_type() == tar::EntryType::Directory { - directories.push(file); - } else { - let entry_type = file.header().entry_type(); - let path = file.path().map_err(Error::io_or_compression)?.into_owned(); - let size = file.header().size().map_err(Error::io_or_compression)?; - if entry_type.is_file() || entry_type.is_hard_link() { - files.push((path, size)); - } - file.unpack_in(&dst).map_err(Error::io_or_compression)?; - } - } - - // Apply the directories. - // - // Note: the order of application is important to permissions. That is, we must traverse - // the filesystem graph in topological ordering or else we risk not being able to create - // child directories within those of more restrictive permissions. See [0] for details. - // - // [0]: - directories.sort_by(|a, b| b.path_bytes().cmp(&a.path_bytes())); - for mut dir in directories { - dir.unpack_in(&dst).map_err(Error::io_or_compression)?; - } - Ok(files) -} - /// Unpack a `.tar.xz` archive into the target directory, without requiring `Seek`. /// /// This is useful for unpacking files as they're being downloaded. From 84a324414b2b7660b0591ced6b33ffd084411f97 Mon Sep 17 00:00:00 2001 From: Zsolt Dollenstein Date: Sat, 25 Apr 2026 00:48:55 +0100 Subject: [PATCH 68/70] Redact pre-signed upload URLs in verbose output (#19146) This teaches `DisplaySafeUrl` about sensitive query parameters (that are typically used to sign S3 requests) to avoid leaking them in verbose logs. Note: Errors might still contain these in two places: - reqwest errors contain a raw url - aws can return an error that itself contains the signature (this is probably fine to display unredacted, as it only happens when the signature is invalid anyway) This PR is part of a stack containing 2 PRs: 1. `main` 2. **"Redact pre-signed upload URLs" (this PR)** 3. #19147 --- crates/uv-redacted/src/lib.rs | 113 +++++++++++++++++++++++++++++++--- crates/uv/tests/it/publish.rs | 61 ++++++++++++++++++ 2 files changed, 167 insertions(+), 7 deletions(-) diff --git a/crates/uv-redacted/src/lib.rs b/crates/uv-redacted/src/lib.rs index 9f5905a018ed2..6573e89b84d7c 100644 --- a/crates/uv-redacted/src/lib.rs +++ b/crates/uv-redacted/src/lib.rs @@ -7,6 +7,12 @@ use std::str::FromStr; use thiserror::Error; use url::Url; +const SENSITIVE_QUERY_PARAMETERS: &[&str] = &[ + "X-Amz-Credential", + "X-Amz-Security-Token", + "X-Amz-Signature", +]; + #[derive(Error, Debug, Clone, PartialEq, Eq)] pub enum DisplaySafeUrlError { /// Failed to parse a URL. @@ -19,7 +25,7 @@ pub enum DisplaySafeUrlError { AmbiguousAuthority(String), } -/// A [`Url`] wrapper that redacts credentials when displaying the URL. +/// A [`Url`] wrapper that redacts credentials and sensitive query parameters when displaying the URL. /// /// `DisplaySafeUrl` wraps the standard [`url::Url`] type, providing functionality to mask /// secrets by default when the URL is displayed or logged. This helps prevent accidental @@ -246,7 +252,8 @@ impl Display for DisplaySafeUrl { impl Debug for DisplaySafeUrl { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let url = &self.0; + let url = url_with_redacted_sensitive_query_values(&self.0); + let url = url.as_ref(); // For URLs that use the `git` convention (i.e., `ssh://git@github.com/...`), avoid masking the // username. let (username, password) = if is_ssh_git_username(url) { @@ -301,17 +308,46 @@ fn is_ssh_git_username(url: &Url) -> bool { && url.password().is_none() } +fn is_sensitive_query_parameter(key: &str) -> bool { + SENSITIVE_QUERY_PARAMETERS + .iter() + .any(|sensitive| key.eq_ignore_ascii_case(sensitive)) +} + +fn url_with_redacted_sensitive_query_values(url: &Url) -> Cow<'_, Url> { + if !url + .query_pairs() + .any(|(key, _value)| is_sensitive_query_parameter(&key)) + { + return Cow::Borrowed(url); + } + + let query_pairs: Vec<_> = url + .query_pairs() + .map(|(key, value)| { + let value = if is_sensitive_query_parameter(&key) { + Cow::Borrowed("****") + } else { + value + }; + (key, value) + }) + .collect(); + + let mut url = url.clone(); + url.query_pairs_mut().clear().extend_pairs(query_pairs); + Cow::Owned(url) +} + fn display_with_redacted_credentials( url: &Url, f: &mut std::fmt::Formatter<'_>, ) -> std::fmt::Result { - if url.password().is_none() && url.username() == "" { - return write!(f, "{url}"); - } - + let url = url_with_redacted_sensitive_query_values(url); + let url = url.as_ref(); // For URLs that use the `git` convention (i.e., `ssh://git@github.com/...`), avoid dropping the // username. - if is_ssh_git_username(url) { + if is_ssh_git_username(url) || (url.username().is_empty() && url.password().is_none()) { return write!(f, "{url}"); } @@ -461,6 +497,69 @@ mod tests { ); } + #[test] + fn redact_aws_presigned_query_values() { + let log_safe_url = DisplaySafeUrl::parse( + "https://bucket.s3.amazonaws.com/dist.whl?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=credential&X-Amz-Date=20260424T120000Z&X-Amz-Expires=300&X-Amz-SignedHeaders=host&X-Amz-Signature=signature&X-Amz-Security-Token=token", + ) + .unwrap(); + + assert_eq!( + log_safe_url.to_string(), + "https://bucket.s3.amazonaws.com/dist.whl?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=****&X-Amz-Date=20260424T120000Z&X-Amz-Expires=300&X-Amz-SignedHeaders=host&X-Amz-Signature=****&X-Amz-Security-Token=****" + ); + } + + #[test] + fn redact_aws_presigned_query_values_case_insensitive() { + let log_safe_url = DisplaySafeUrl::parse( + "https://bucket.s3.amazonaws.com/dist.whl?x-amz-credential=credential&x-amz-signature=signature&x-amz-security-token=token", + ) + .unwrap(); + + assert_eq!( + log_safe_url.to_string(), + "https://bucket.s3.amazonaws.com/dist.whl?x-amz-credential=****&x-amz-signature=****&x-amz-security-token=****" + ); + } + + #[test] + fn redact_aws_presigned_query_values_with_percent_encoded_keys() { + let log_safe_url = DisplaySafeUrl::parse( + "https://bucket.s3.amazonaws.com/dist.whl?X-Amz%2DSignature=signature&safe=value", + ) + .unwrap(); + + assert_eq!( + log_safe_url.to_string(), + "https://bucket.s3.amazonaws.com/dist.whl?X-Amz-Signature=****&safe=value" + ); + } + + #[test] + fn redact_aws_presigned_query_values_in_debug() { + let log_safe_url = DisplaySafeUrl::parse( + "https://bucket.s3.amazonaws.com/dist.whl?X-Amz-Credential=credential&X-Amz-Signature=signature", + ) + .unwrap(); + + let debug = format!("{log_safe_url:?}"); + assert!(debug.contains(r#"query: Some("X-Amz-Credential=****&X-Amz-Signature=****")"#)); + assert!(!debug.contains("credential")); + assert!(!debug.contains("signature")); + } + + #[test] + fn does_not_redact_unknown_query_values() { + let log_safe_url = + DisplaySafeUrl::parse("https://bucket.s3.amazonaws.com/dist.whl?token=secret").unwrap(); + + assert_eq!( + log_safe_url.to_string(), + "https://bucket.s3.amazonaws.com/dist.whl?token=secret" + ); + } + #[test] fn url_join() { let url_str = "https://token@example.com/abc/"; diff --git a/crates/uv/tests/it/publish.rs b/crates/uv/tests/it/publish.rs index 4afba8af04025..60a3dcf06e2fc 100644 --- a/crates/uv/tests/it/publish.rs +++ b/crates/uv/tests/it/publish.rs @@ -623,6 +623,67 @@ async fn gitlab_trusted_publishing_testpypi_id_token() { ); } +#[tokio::test] +async fn direct_publish_redacts_presigned_upload_url() { + let context = uv_test::test_context!("3.12"); + let server = MockServer::start().await; + + let upload_url = format!( + "{}/s3/ok-1.0.0-py3-none-any.whl?X-Amz-Credential=credential&X-Amz-Signature=signature&X-Amz-Security-Token=token", + server.uri() + ); + + Mock::given(method("POST")) + .and(path("/upload/reserve")) + .respond_with(ResponseTemplate::new(201).set_body_json(json!({ + "upload_url": upload_url, + }))) + .mount(&server) + .await; + Mock::given(method("PUT")) + .and(path("/s3/ok-1.0.0-py3-none-any.whl")) + .respond_with(ResponseTemplate::new(200)) + .mount(&server) + .await; + Mock::given(method("POST")) + .and(path("/upload/finalize")) + .respond_with(ResponseTemplate::new(200)) + .mount(&server) + .await; + + uv_snapshot!(context.filters(), context.publish() + .arg("--preview-features") + .arg("direct-publish") + .arg("--direct") + .arg("-u") + .arg("dummy") + .arg("-p") + .arg("dummy") + .arg("--publish-url") + .arg(format!("{}/upload", server.uri())) + .arg(dummy_wheel()) + .env(EnvVars::RUST_LOG, "uv_publish=debug"), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Publishing 1 file to http://[LOCALHOST]/upload + Hashing ok-1.0.0-py3-none-any.whl ([SIZE]) + DEBUG Hashing [WORKSPACE]/test/links/ok-1.0.0-py3-none-any.whl + Uploading ok-1.0.0-py3-none-any.whl ([SIZE]) + DEBUG Reserving upload slot at http://[LOCALHOST]/upload/reserve + DEBUG Using HTTP Basic authentication + DEBUG Got pre-signed URL for upload: http://[LOCALHOST]/s3/ok-1.0.0-py3-none-any.whl?X-Amz-Credential=****&X-Amz-Signature=****&X-Amz-Security-Token=**** + DEBUG S3 upload complete for ok-1.0.0-py3-none-any.whl + DEBUG Finalizing upload at http://[LOCALHOST]/upload/finalize + DEBUG Using HTTP Basic authentication + DEBUG Response code for http://[LOCALHOST]/upload/finalize: 200 OK + DEBUG Upload finalized for ok-1.0.0-py3-none-any.whl + " + ); +} + /// PyPI returns `application/json` errors with a `code` field. #[tokio::test] async fn upload_error_pypi_json() { From 3e9c33358554168e6662007bdf533f9b9c1b40ac Mon Sep 17 00:00:00 2001 From: konsti Date: Sat, 25 Apr 2026 18:49:08 -0400 Subject: [PATCH 69/70] Configure logging before globals (#19155) We were missing configuration and workspace discovery in logging and tracing. --- crates/uv/src/lib.rs | 38 +++++++++++----------- crates/uv/src/settings.rs | 54 +++++++++++++++++-------------- crates/uv/tests/it/cache_clean.rs | 8 +++++ crates/uv/tests/it/cache_prune.rs | 10 ++++++ crates/uv/tests/it/lock.rs | 4 +++ 5 files changed, 71 insertions(+), 43 deletions(-) diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index fd8aeb74f3b8b..400f63d671b31 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -56,7 +56,7 @@ use crate::printer::Printer; use crate::settings::{ CacheSettings, GlobalSettings, PipCheckSettings, PipCompileSettings, PipFreezeSettings, PipInstallSettings, PipListSettings, PipShowSettings, PipSyncSettings, PipUninstallSettings, - PublishSettings, + PublishSettings, resolve_color, }; pub(crate) mod child; @@ -117,6 +117,24 @@ async fn run(cli: Cli) -> Result { // Make the early preview flags globally available. uv_preview::set(early_preview)?; + // Configure the `tracing` crate, which controls internal logging. + #[cfg(feature = "tracing-durations-export")] + let (durations_layer, _duration_guard) = + logging::setup_durations(environment.tracing_durations_file.as_ref())?; + #[cfg(not(feature = "tracing-durations-export"))] + let durations_layer = None::; + logging::setup_logging( + match cli.top_level.global_args.verbose { + 0 => logging::Level::Off, + 1 => logging::Level::DebugUv, + 2 => logging::Level::TraceUv, + 3.. => logging::Level::TraceAll, + }, + durations_layer, + resolve_color(&cli.top_level.global_args), + environment.log_context.unwrap_or_default(), + )?; + // Determine the project directory. // // If `--project` points to a `pyproject.toml` file, resolve to its parent directory, @@ -385,24 +403,6 @@ async fn run(cli: Cli) -> Result { uv_flags::init(EnvironmentFlags::from(&environment)) .map_err(|()| anyhow::anyhow!("Flags are already initialized"))?; - // Configure the `tracing` crate, which controls internal logging. - #[cfg(feature = "tracing-durations-export")] - let (durations_layer, _duration_guard) = - logging::setup_durations(environment.tracing_durations_file.as_ref())?; - #[cfg(not(feature = "tracing-durations-export"))] - let durations_layer = None::; - logging::setup_logging( - match globals.verbose { - 0 => logging::Level::Off, - 1 => logging::Level::DebugUv, - 2 => logging::Level::TraceUv, - 3.. => logging::Level::TraceAll, - }, - durations_layer, - globals.color, - environment.log_context.unwrap_or_default(), - )?; - debug!("uv {}", uv_cli::version::uv_self_version()); if let Some(config_file) = cli.top_level.config_file.as_ref() { debug!("Using configuration file: {}", config_file.user_display()); diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 139e9829dacad..9845caf05668c 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -95,35 +95,13 @@ impl GlobalSettings { ) -> Self { let network_settings = NetworkSettings::resolve(args, workspace, environment); let python_preference = resolve_python_preference(args, workspace, environment); + let color = resolve_color(args); Self { required_version: workspace .and_then(|workspace| workspace.globals.required_version.clone()), quiet: args.quiet, verbose: args.verbose, - color: if let Some(color_choice) = args.color { - // If `--color` is passed explicitly, use its value. - color_choice - } else if args.no_color { - // If `--no-color` is passed explicitly, disable color output. - ColorChoice::Never - } else if std::env::var_os(EnvVars::NO_COLOR) - .filter(|v| !v.is_empty()) - .is_some() - { - // If the `NO_COLOR` is set, disable color output. - ColorChoice::Never - } else if std::env::var_os(EnvVars::FORCE_COLOR) - .filter(|v| !v.is_empty()) - .is_some() - || std::env::var_os(EnvVars::CLICOLOR_FORCE) - .filter(|v| !v.is_empty()) - .is_some() - { - // If `FORCE_COLOR` or `CLICOLOR_FORCE` is set, always enable color output. - ColorChoice::Always - } else { - ColorChoice::Auto - }, + color, network_settings, concurrency: Concurrency::new( environment @@ -176,6 +154,34 @@ impl GlobalSettings { } } +/// Resolve the color choice from CLI arguments and environment variables. +pub(crate) fn resolve_color(args: &GlobalArgs) -> ColorChoice { + if let Some(color_choice) = args.color { + // If `--color` is passed explicitly, use its value. + color_choice + } else if args.no_color { + // If `--no-color` is passed explicitly, disable color output. + ColorChoice::Never + } else if std::env::var_os(EnvVars::NO_COLOR) + .filter(|v| !v.is_empty()) + .is_some() + { + // If the `NO_COLOR` is set, disable color output. + ColorChoice::Never + } else if std::env::var_os(EnvVars::FORCE_COLOR) + .filter(|v| !v.is_empty()) + .is_some() + || std::env::var_os(EnvVars::CLICOLOR_FORCE) + .filter(|v| !v.is_empty()) + .is_some() + { + // If `FORCE_COLOR` or `CLICOLOR_FORCE` is set, always enable color output. + ColorChoice::Always + } else { + ColorChoice::Auto + } +} + fn resolve_python_preference( args: &GlobalArgs, workspace: Option<&FilesystemOptions>, diff --git a/crates/uv/tests/it/cache_clean.rs b/crates/uv/tests/it/cache_clean.rs index e380a7ef271c3..f2887a427a840 100644 --- a/crates/uv/tests/it/cache_clean.rs +++ b/crates/uv/tests/it/cache_clean.rs @@ -28,6 +28,7 @@ fn clean_all() -> Result<()> { ----- stdout ----- ----- stderr ----- + DEBUG Searching for user configuration in: `[UV_USER_CONFIG_DIR]/uv.toml` DEBUG uv [VERSION] ([COMMIT] DATE) Clearing cache at: [CACHE_DIR]/ Removed [N] files ([SIZE]) @@ -60,6 +61,7 @@ fn clear_all_alias() -> Result<()> { ----- stdout ----- ----- stderr ----- + DEBUG Searching for user configuration in: `[UV_USER_CONFIG_DIR]/uv.toml` DEBUG uv [VERSION] ([COMMIT] DATE) Clearing cache at: [CACHE_DIR]/ Removed [N] files ([SIZE]) @@ -89,6 +91,7 @@ async fn clean_force() -> Result<()> { ----- stdout ----- ----- stderr ----- + DEBUG Searching for user configuration in: `[UV_USER_CONFIG_DIR]/uv.toml` DEBUG uv [VERSION] ([COMMIT] DATE) Clearing cache at: [CACHE_DIR]/ Removed [N] files ([SIZE]) @@ -111,6 +114,7 @@ async fn clean_force() -> Result<()> { ----- stdout ----- ----- stderr ----- + DEBUG Searching for user configuration in: `[UV_USER_CONFIG_DIR]/uv.toml` DEBUG uv [VERSION] ([COMMIT] DATE) DEBUG Lock is busy for `[CACHE_DIR]/` DEBUG Cache is currently in use, proceeding due to `--force` @@ -167,6 +171,7 @@ fn clean_package_pypi() -> Result<()> { ----- stdout ----- ----- stderr ----- + DEBUG Searching for user configuration in: `[UV_USER_CONFIG_DIR]/uv.toml` DEBUG uv [VERSION] ([COMMIT] DATE) DEBUG Removing dangling cache entry: [CACHE_DIR]/archive-v0/[ENTRY] Removed [N] files ([SIZE]) @@ -185,6 +190,7 @@ fn clean_package_pypi() -> Result<()> { ----- stdout ----- ----- stderr ----- + DEBUG Searching for user configuration in: `[UV_USER_CONFIG_DIR]/uv.toml` DEBUG uv [VERSION] ([COMMIT] DATE) Pruning cache at: [CACHE_DIR]/ No unused entries found @@ -242,6 +248,7 @@ fn clean_package_index() -> Result<()> { ----- stdout ----- ----- stderr ----- + DEBUG Searching for user configuration in: `[UV_USER_CONFIG_DIR]/uv.toml` DEBUG uv [VERSION] ([COMMIT] DATE) DEBUG Removing dangling cache entry: [CACHE_DIR]/archive-v0/[ENTRY] Removed [N] files ([SIZE]) @@ -313,6 +320,7 @@ fn clean_handles_verbatim_paths() -> Result<()> { ----- stdout ----- ----- stderr ----- + DEBUG Searching for user configuration in: `[UV_USER_CONFIG_DIR]/uv.toml` DEBUG uv [VERSION] ([COMMIT] DATE) Clearing cache at: [CACHE_DIR]/ Removed 2 files diff --git a/crates/uv/tests/it/cache_prune.rs b/crates/uv/tests/it/cache_prune.rs index cf638b8628fa1..369df3df09b8c 100644 --- a/crates/uv/tests/it/cache_prune.rs +++ b/crates/uv/tests/it/cache_prune.rs @@ -34,6 +34,7 @@ fn prune_no_op() -> Result<()> { ----- stdout ----- ----- stderr ----- + DEBUG Searching for user configuration in: `[UV_USER_CONFIG_DIR]/uv.toml` DEBUG uv [VERSION] ([COMMIT] DATE) Pruning cache at: [CACHE_DIR]/ No unused entries found @@ -73,6 +74,7 @@ fn prune_stale_directory() -> Result<()> { ----- stdout ----- ----- stderr ----- + DEBUG Searching for user configuration in: `[UV_USER_CONFIG_DIR]/uv.toml` DEBUG uv [VERSION] ([COMMIT] DATE) Pruning cache at: [CACHE_DIR]/ DEBUG Removing dangling cache bucket: [CACHE_DIR]/simple-v4 @@ -133,6 +135,7 @@ fn prune_cached_env() { ----- stdout ----- ----- stderr ----- + DEBUG Searching for user configuration in: `[UV_USER_CONFIG_DIR]/uv.toml` DEBUG uv [VERSION] ([COMMIT] DATE) Pruning cache at: [CACHE_DIR]/ DEBUG Removing dangling cache environment: [CACHE_DIR]/environments-v2/[ENTRY] @@ -178,6 +181,7 @@ fn prune_stale_symlink() -> Result<()> { ----- stdout ----- ----- stderr ----- + DEBUG Searching for user configuration in: `[UV_USER_CONFIG_DIR]/uv.toml` DEBUG uv [VERSION] ([COMMIT] DATE) Pruning cache at: [CACHE_DIR]/ DEBUG Removing dangling cache archive: [CACHE_DIR]/archive-v0/[ENTRY] @@ -208,6 +212,7 @@ async fn prune_force() -> Result<()> { ----- stdout ----- ----- stderr ----- + DEBUG Searching for user configuration in: `[UV_USER_CONFIG_DIR]/uv.toml` DEBUG uv [VERSION] ([COMMIT] DATE) Pruning cache at: [CACHE_DIR]/ No unused entries found @@ -227,6 +232,7 @@ async fn prune_force() -> Result<()> { ----- stdout ----- ----- stderr ----- + DEBUG Searching for user configuration in: `[UV_USER_CONFIG_DIR]/uv.toml` DEBUG uv [VERSION] ([COMMIT] DATE) DEBUG Lock is busy for `[CACHE_DIR]/` DEBUG Cache is currently in use, proceeding due to `--force` @@ -401,6 +407,10 @@ fn prune_stale_revision() -> Result<()> { ----- stdout ----- ----- stderr ----- + DEBUG Found workspace root: `[TEMP_DIR]/` + DEBUG Adding root workspace member: `[TEMP_DIR]/` + DEBUG Skipping `pyproject.toml` in `[TEMP_DIR]/` (no `[tool]` section) + DEBUG Searching for user configuration in: `[UV_USER_CONFIG_DIR]/uv.toml` DEBUG uv [VERSION] ([COMMIT] DATE) Pruning cache at: [CACHE_DIR]/ DEBUG Removing dangling source revision: [CACHE_DIR]/sdists-v9/[ENTRY] diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index 2e6b7cd405422..895b29c825461 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -19969,6 +19969,10 @@ fn lock_explicit_default_index() -> Result<()> { ----- stdout ----- ----- stderr ----- + DEBUG Found workspace root: `[TEMP_DIR]/` + DEBUG Adding root workspace member: `[TEMP_DIR]/` + DEBUG Found workspace configuration at `[TEMP_DIR]/pyproject.toml` + DEBUG Searching for user configuration in: `[UV_USER_CONFIG_DIR]/uv.toml` DEBUG uv [VERSION] ([COMMIT] DATE) DEBUG Found project root: `[TEMP_DIR]/` DEBUG No workspace root found, using project root From 0e961dd9a2bb6f73493d9e8398b725ad2d3b3837 Mon Sep 17 00:00:00 2001 From: Zsolt Dollenstein Date: Mon, 27 Apr 2026 12:40:45 +0100 Subject: [PATCH 70/70] Bump version to 0.11.8 (#19184) --- CHANGELOG.md | 37 +++++ Cargo.lock | 132 +++++++++--------- Cargo.toml | 124 ++++++++-------- crates/uv-audit/Cargo.toml | 2 +- crates/uv-audit/README.md | 4 +- crates/uv-auth/Cargo.toml | 2 +- crates/uv-auth/README.md | 4 +- crates/uv-bench/Cargo.toml | 2 +- crates/uv-bench/README.md | 4 +- crates/uv-bin-install/Cargo.toml | 2 +- crates/uv-bin-install/README.md | 4 +- crates/uv-build-backend/Cargo.toml | 2 +- crates/uv-build-backend/README.md | 4 +- crates/uv-build-frontend/Cargo.toml | 2 +- crates/uv-build-frontend/README.md | 4 +- crates/uv-build/Cargo.toml | 2 +- crates/uv-build/pyproject.toml | 2 +- crates/uv-cache-info/Cargo.toml | 2 +- crates/uv-cache-info/README.md | 4 +- crates/uv-cache-key/Cargo.toml | 2 +- crates/uv-cache-key/README.md | 4 +- crates/uv-cache/Cargo.toml | 2 +- crates/uv-cache/README.md | 4 +- crates/uv-cli/Cargo.toml | 2 +- crates/uv-cli/README.md | 4 +- crates/uv-client/Cargo.toml | 2 +- crates/uv-client/README.md | 4 +- crates/uv-configuration/Cargo.toml | 2 +- crates/uv-configuration/README.md | 4 +- crates/uv-console/Cargo.toml | 2 +- crates/uv-console/README.md | 4 +- crates/uv-dev/Cargo.toml | 2 +- crates/uv-dev/README.md | 4 +- crates/uv-dirs/Cargo.toml | 2 +- crates/uv-dirs/README.md | 4 +- crates/uv-dispatch/Cargo.toml | 2 +- crates/uv-dispatch/README.md | 4 +- crates/uv-distribution-filename/Cargo.toml | 2 +- crates/uv-distribution-filename/README.md | 4 +- crates/uv-distribution-types/Cargo.toml | 2 +- crates/uv-distribution-types/README.md | 4 +- crates/uv-distribution/Cargo.toml | 2 +- crates/uv-distribution/README.md | 4 +- crates/uv-extract/Cargo.toml | 2 +- crates/uv-extract/README.md | 4 +- crates/uv-flags/Cargo.toml | 2 +- crates/uv-flags/README.md | 4 +- crates/uv-fs/Cargo.toml | 2 +- crates/uv-fs/README.md | 4 +- crates/uv-git-types/Cargo.toml | 2 +- crates/uv-git-types/README.md | 4 +- crates/uv-git/Cargo.toml | 2 +- crates/uv-git/README.md | 4 +- crates/uv-globfilter/Cargo.toml | 2 +- crates/uv-install-wheel/Cargo.toml | 2 +- crates/uv-install-wheel/README.md | 4 +- crates/uv-installer/Cargo.toml | 2 +- crates/uv-installer/README.md | 4 +- crates/uv-keyring/Cargo.toml | 2 +- crates/uv-logging/Cargo.toml | 2 +- crates/uv-logging/README.md | 4 +- crates/uv-macros/Cargo.toml | 2 +- crates/uv-macros/README.md | 4 +- crates/uv-metadata/Cargo.toml | 2 +- crates/uv-metadata/README.md | 4 +- crates/uv-normalize/Cargo.toml | 2 +- crates/uv-normalize/README.md | 4 +- crates/uv-once-map/Cargo.toml | 2 +- crates/uv-once-map/README.md | 4 +- crates/uv-options-metadata/Cargo.toml | 2 +- crates/uv-options-metadata/README.md | 4 +- crates/uv-pep440/Cargo.toml | 2 +- crates/uv-pep440/README.md | 4 +- crates/uv-pep508/Cargo.toml | 2 +- crates/uv-pep508/README.md | 4 +- .../Cargo.toml | 2 +- .../uv-performance-memory-allocator/README.md | 4 +- crates/uv-platform-tags/Cargo.toml | 2 +- crates/uv-platform-tags/README.md | 4 +- crates/uv-platform/Cargo.toml | 2 +- crates/uv-platform/README.md | 4 +- crates/uv-preview/Cargo.toml | 2 +- crates/uv-preview/README.md | 4 +- crates/uv-publish/Cargo.toml | 2 +- crates/uv-publish/README.md | 4 +- crates/uv-pypi-types/Cargo.toml | 2 +- crates/uv-pypi-types/README.md | 4 +- crates/uv-python/Cargo.toml | 2 +- crates/uv-python/README.md | 4 +- crates/uv-redacted/Cargo.toml | 2 +- crates/uv-redacted/README.md | 4 +- crates/uv-requirements-txt/Cargo.toml | 2 +- crates/uv-requirements-txt/README.md | 4 +- crates/uv-requirements/Cargo.toml | 2 +- crates/uv-requirements/README.md | 4 +- crates/uv-resolver/Cargo.toml | 2 +- crates/uv-resolver/README.md | 4 +- crates/uv-scripts/Cargo.toml | 2 +- crates/uv-scripts/README.md | 4 +- crates/uv-settings/Cargo.toml | 2 +- crates/uv-settings/README.md | 4 +- crates/uv-shell/Cargo.toml | 2 +- crates/uv-shell/README.md | 4 +- crates/uv-small-str/Cargo.toml | 2 +- crates/uv-small-str/README.md | 4 +- crates/uv-state/Cargo.toml | 2 +- crates/uv-state/README.md | 4 +- crates/uv-static/Cargo.toml | 2 +- crates/uv-static/README.md | 4 +- crates/uv-static/src/env_vars.rs | 8 +- crates/uv-test/Cargo.toml | 2 +- crates/uv-test/README.md | 4 +- crates/uv-tool/Cargo.toml | 2 +- crates/uv-tool/README.md | 4 +- crates/uv-torch/Cargo.toml | 2 +- crates/uv-torch/README.md | 4 +- crates/uv-trampoline-builder/Cargo.toml | 2 +- crates/uv-trampoline-builder/README.md | 4 +- crates/uv-trampoline/Cargo.lock | 6 +- crates/uv-types/Cargo.toml | 2 +- crates/uv-types/README.md | 4 +- crates/uv-unix/Cargo.toml | 2 +- crates/uv-unix/README.md | 4 +- crates/uv-version/Cargo.toml | 2 +- crates/uv-version/README.md | 4 +- crates/uv-virtualenv/Cargo.toml | 2 +- crates/uv-warnings/Cargo.toml | 2 +- crates/uv-warnings/README.md | 4 +- crates/uv-windows/Cargo.toml | 2 +- crates/uv-windows/README.md | 4 +- crates/uv-workspace/Cargo.toml | 2 +- crates/uv-workspace/README.md | 4 +- crates/uv/Cargo.toml | 2 +- crates/uv/README.md | 4 +- docs/concepts/build-backend.md | 2 +- docs/concepts/projects/init.md | 6 +- docs/concepts/projects/workspaces.md | 6 +- docs/getting-started/installation.md | 4 +- docs/guides/integration/aws-lambda.md | 4 +- docs/guides/integration/docker.md | 10 +- docs/guides/integration/github.md | 2 +- docs/guides/integration/gitlab.md | 2 +- docs/guides/integration/pre-commit.md | 10 +- pyproject.toml | 2 +- uv.lock | 2 +- uv.schema.json | 2 +- 146 files changed, 389 insertions(+), 352 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4ac1dff5ce97..c8ed85c4dbd9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,43 @@ +## 0.11.8 + +Released on 2026-04-27. + +### Enhancements + +- Add `--python-downloads-json-url` to `python pin` ([#19092](https://github.com/astral-sh/uv/pull/19092)) +- Fetch uv from Astral mirror during self-update ([#18682](https://github.com/astral-sh/uv/pull/18682)) +- Support `pip uninstall -y` ([#19082](https://github.com/astral-sh/uv/pull/19082)) +- Add `UV_PYTHON_NO_REGISTRY` ([#19035](https://github.com/astral-sh/uv/pull/19035)) +- Allow `exclude-newer` to be missing from the lockfile when `exclude-newer-span` is present ([#19024](https://github.com/astral-sh/uv/pull/19024)) +- Only show the version number in `uv self version --short` ([#19019](https://github.com/astral-sh/uv/pull/19019)) +- Silence warnings on empty `SSL_CERT_DIR` directory ([#19018](https://github.com/astral-sh/uv/pull/19018)) +- Use a sentinel timestamp for relative `exclude-newer` and `exclude-newer-package` values in lockfiles ([#19022](https://github.com/astral-sh/uv/pull/19022), [#19101](https://github.com/astral-sh/uv/pull/19101)) + +### Configuration + +- Add an environment variable for `UV_NO_PROJECT` ([#19052](https://github.com/astral-sh/uv/pull/19052)) +- Expose `UV_PYTHON_SEARCH_PATH` for Python discovery `PATH` overrides ([#19034](https://github.com/astral-sh/uv/pull/19034)) + +### Bug fixes + +- Add `rust-toolchain.toml` to uv-build sdist ([#19131](https://github.com/astral-sh/uv/pull/19131)) +- Ensure uv invocations of git do not inherit repository location environment variables ([#19088](https://github.com/astral-sh/uv/pull/19088)) +- Redact pre-signed upload URLs in verbose output ([#19146](https://github.com/astral-sh/uv/pull/19146)) +- Handle transitive URL dependencies in PEP 517 build requirements ([#19076](https://github.com/astral-sh/uv/pull/19076), [#19086](https://github.com/astral-sh/uv/pull/19086)) +- Support `uv lock` on a `pyproject.toml` that only contains dependency-groups ([#19087](https://github.com/astral-sh/uv/pull/19087)) +- Disable transparent Python upgrades in projects when a patch version is requested via `.python-version` ([#19102](https://github.com/astral-sh/uv/pull/19102)) +- Fix Python variant tagging in the Windows registry ([#19012](https://github.com/astral-sh/uv/pull/19012)) +- Use a single codepath for extracting a .tar.zst wheel, disallowing external symlinks ([#19144](https://github.com/astral-sh/uv/pull/19144)) + +### Documentation + +- Bump astral-sh/setup-uv version in docs ([#19030](https://github.com/astral-sh/uv/pull/19030)) +- Update PyTorch documentation for PyTorch 2.11 ([#19095](https://github.com/astral-sh/uv/pull/19095)) +- Remove deprecated license classifiers from uv-build and add Python 3.14 classifier ([#19130](https://github.com/astral-sh/uv/pull/19130)) + ## 0.11.7 Released on 2026-04-15. diff --git a/Cargo.lock b/Cargo.lock index b9b7402986b41..89bd17e11ed95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5750,7 +5750,7 @@ dependencies = [ [[package]] name = "uv" -version = "0.11.7" +version = "0.11.8" dependencies = [ "anstream", "anyhow", @@ -5877,7 +5877,7 @@ dependencies = [ [[package]] name = "uv-audit" -version = "0.0.40" +version = "0.0.41" dependencies = [ "astral-reqwest-middleware", "clap", @@ -5901,7 +5901,7 @@ dependencies = [ [[package]] name = "uv-auth" -version = "0.0.40" +version = "0.0.41" dependencies = [ "anyhow", "arcstr", @@ -5944,7 +5944,7 @@ dependencies = [ [[package]] name = "uv-bench" -version = "0.0.40" +version = "0.0.41" dependencies = [ "anyhow", "codspeed-criterion-compat", @@ -5971,7 +5971,7 @@ dependencies = [ [[package]] name = "uv-bin-install" -version = "0.0.40" +version = "0.0.41" dependencies = [ "astral-reqwest-middleware", "astral-reqwest-retry", @@ -5998,7 +5998,7 @@ dependencies = [ [[package]] name = "uv-build" -version = "0.11.7" +version = "0.11.8" dependencies = [ "anstream", "anyhow", @@ -6013,7 +6013,7 @@ dependencies = [ [[package]] name = "uv-build-backend" -version = "0.0.40" +version = "0.0.41" dependencies = [ "astral-version-ranges", "base64 0.22.1", @@ -6055,7 +6055,7 @@ dependencies = [ [[package]] name = "uv-build-frontend" -version = "0.0.40" +version = "0.0.41" dependencies = [ "anstream", "fs-err", @@ -6092,7 +6092,7 @@ dependencies = [ [[package]] name = "uv-cache" -version = "0.0.40" +version = "0.0.41" dependencies = [ "clap", "fs-err", @@ -6118,7 +6118,7 @@ dependencies = [ [[package]] name = "uv-cache-info" -version = "0.0.40" +version = "0.0.41" dependencies = [ "anyhow", "fs-err", @@ -6135,7 +6135,7 @@ dependencies = [ [[package]] name = "uv-cache-key" -version = "0.0.40" +version = "0.0.41" dependencies = [ "hex", "memchr", @@ -6147,7 +6147,7 @@ dependencies = [ [[package]] name = "uv-cli" -version = "0.0.40" +version = "0.0.41" dependencies = [ "anstream", "anyhow", @@ -6180,7 +6180,7 @@ dependencies = [ [[package]] name = "uv-client" -version = "0.0.40" +version = "0.0.41" dependencies = [ "anyhow", "astral-reqwest-middleware", @@ -6250,7 +6250,7 @@ dependencies = [ [[package]] name = "uv-configuration" -version = "0.0.40" +version = "0.0.41" dependencies = [ "anyhow", "clap", @@ -6284,14 +6284,14 @@ dependencies = [ [[package]] name = "uv-console" -version = "0.0.40" +version = "0.0.41" dependencies = [ "console", ] [[package]] name = "uv-dev" -version = "0.0.40" +version = "0.0.41" dependencies = [ "anstream", "anyhow", @@ -6340,7 +6340,7 @@ dependencies = [ [[package]] name = "uv-dirs" -version = "0.0.40" +version = "0.0.41" dependencies = [ "assert_fs", "etcetera", @@ -6352,7 +6352,7 @@ dependencies = [ [[package]] name = "uv-dispatch" -version = "0.0.40" +version = "0.0.41" dependencies = [ "anyhow", "futures", @@ -6385,7 +6385,7 @@ dependencies = [ [[package]] name = "uv-distribution" -version = "0.0.40" +version = "0.0.41" dependencies = [ "anyhow", "astral-reqwest-middleware", @@ -6437,7 +6437,7 @@ dependencies = [ [[package]] name = "uv-distribution-filename" -version = "0.0.40" +version = "0.0.41" dependencies = [ "insta", "memchr", @@ -6454,7 +6454,7 @@ dependencies = [ [[package]] name = "uv-distribution-types" -version = "0.0.40" +version = "0.0.41" dependencies = [ "arcstr", "astral-version-ranges", @@ -6494,7 +6494,7 @@ dependencies = [ [[package]] name = "uv-extract" -version = "0.0.40" +version = "0.0.41" dependencies = [ "astral-tokio-tar", "astral_async_zip", @@ -6523,14 +6523,14 @@ dependencies = [ [[package]] name = "uv-flags" -version = "0.0.40" +version = "0.0.41" dependencies = [ "bitflags 2.11.0", ] [[package]] name = "uv-fs" -version = "0.0.40" +version = "0.0.41" dependencies = [ "backon", "clap", @@ -6559,7 +6559,7 @@ dependencies = [ [[package]] name = "uv-git" -version = "0.0.40" +version = "0.0.41" dependencies = [ "anyhow", "astral-reqwest-middleware", @@ -6584,7 +6584,7 @@ dependencies = [ [[package]] name = "uv-git-types" -version = "0.0.40" +version = "0.0.41" dependencies = [ "serde", "thiserror 2.0.18", @@ -6597,7 +6597,7 @@ dependencies = [ [[package]] name = "uv-globfilter" -version = "0.0.40" +version = "0.0.41" dependencies = [ "anstream", "fs-err", @@ -6614,7 +6614,7 @@ dependencies = [ [[package]] name = "uv-install-wheel" -version = "0.0.40" +version = "0.0.41" dependencies = [ "anyhow", "assert_fs", @@ -6651,7 +6651,7 @@ dependencies = [ [[package]] name = "uv-installer" -version = "0.0.40" +version = "0.0.41" dependencies = [ "anstream", "anyhow", @@ -6694,7 +6694,7 @@ dependencies = [ [[package]] name = "uv-keyring" -version = "0.0.40" +version = "0.0.41" dependencies = [ "async-trait", "byteorder", @@ -6710,7 +6710,7 @@ dependencies = [ [[package]] name = "uv-logging" -version = "0.0.40" +version = "0.0.41" dependencies = [ "jiff", "owo-colors", @@ -6720,7 +6720,7 @@ dependencies = [ [[package]] name = "uv-macros" -version = "0.0.40" +version = "0.0.41" dependencies = [ "proc-macro2", "quote", @@ -6730,7 +6730,7 @@ dependencies = [ [[package]] name = "uv-metadata" -version = "0.0.40" +version = "0.0.41" dependencies = [ "astral_async_zip", "fs-err", @@ -6747,7 +6747,7 @@ dependencies = [ [[package]] name = "uv-normalize" -version = "0.0.40" +version = "0.0.41" dependencies = [ "rkyv", "schemars", @@ -6757,7 +6757,7 @@ dependencies = [ [[package]] name = "uv-once-map" -version = "0.0.40" +version = "0.0.41" dependencies = [ "dashmap", "futures", @@ -6766,14 +6766,14 @@ dependencies = [ [[package]] name = "uv-options-metadata" -version = "0.0.40" +version = "0.0.41" dependencies = [ "serde", ] [[package]] name = "uv-pep440" -version = "0.0.40" +version = "0.0.41" dependencies = [ "astral-version-ranges", "indoc", @@ -6787,7 +6787,7 @@ dependencies = [ [[package]] name = "uv-pep508" -version = "0.0.40" +version = "0.0.41" dependencies = [ "arcstr", "astral-version-ranges", @@ -6817,7 +6817,7 @@ dependencies = [ [[package]] name = "uv-performance-memory-allocator" -version = "0.0.40" +version = "0.0.41" dependencies = [ "mimalloc", "tikv-jemallocator", @@ -6825,7 +6825,7 @@ dependencies = [ [[package]] name = "uv-platform" -version = "0.0.40" +version = "0.0.41" dependencies = [ "fs-err", "goblin", @@ -6846,7 +6846,7 @@ dependencies = [ [[package]] name = "uv-platform-tags" -version = "0.0.40" +version = "0.0.41" dependencies = [ "bitflags 2.11.0", "insta", @@ -6860,7 +6860,7 @@ dependencies = [ [[package]] name = "uv-preview" -version = "0.0.40" +version = "0.0.41" dependencies = [ "enumflags2", "itertools 0.14.0", @@ -6871,7 +6871,7 @@ dependencies = [ [[package]] name = "uv-publish" -version = "0.0.40" +version = "0.0.41" dependencies = [ "ambient-id", "anstream", @@ -6913,7 +6913,7 @@ dependencies = [ [[package]] name = "uv-pypi-types" -version = "0.0.40" +version = "0.0.41" dependencies = [ "anyhow", "hashbrown 0.17.0", @@ -6946,7 +6946,7 @@ dependencies = [ [[package]] name = "uv-python" -version = "0.0.40" +version = "0.0.41" dependencies = [ "anyhow", "assert_fs", @@ -7009,7 +7009,7 @@ dependencies = [ [[package]] name = "uv-redacted" -version = "0.0.40" +version = "0.0.41" dependencies = [ "ref-cast", "schemars", @@ -7020,7 +7020,7 @@ dependencies = [ [[package]] name = "uv-requirements" -version = "0.0.40" +version = "0.0.41" dependencies = [ "anyhow", "configparser", @@ -7054,7 +7054,7 @@ dependencies = [ [[package]] name = "uv-requirements-txt" -version = "0.0.40" +version = "0.0.41" dependencies = [ "anyhow", "assert_fs", @@ -7087,7 +7087,7 @@ dependencies = [ [[package]] name = "uv-resolver" -version = "0.0.40" +version = "0.0.41" dependencies = [ "arcstr", "astral-pubgrub", @@ -7153,7 +7153,7 @@ dependencies = [ [[package]] name = "uv-scripts" -version = "0.0.40" +version = "0.0.41" dependencies = [ "fs-err", "indoc", @@ -7178,7 +7178,7 @@ dependencies = [ [[package]] name = "uv-settings" -version = "0.0.40" +version = "0.0.41" dependencies = [ "clap", "fs-err", @@ -7215,7 +7215,7 @@ dependencies = [ [[package]] name = "uv-shell" -version = "0.0.40" +version = "0.0.41" dependencies = [ "anyhow", "fs-err", @@ -7232,7 +7232,7 @@ dependencies = [ [[package]] name = "uv-small-str" -version = "0.0.40" +version = "0.0.41" dependencies = [ "arcstr", "rkyv", @@ -7242,7 +7242,7 @@ dependencies = [ [[package]] name = "uv-state" -version = "0.0.40" +version = "0.0.41" dependencies = [ "fs-err", "tempfile", @@ -7251,7 +7251,7 @@ dependencies = [ [[package]] name = "uv-static" -version = "0.0.40" +version = "0.0.41" dependencies = [ "thiserror 2.0.18", "uv-macros", @@ -7259,7 +7259,7 @@ dependencies = [ [[package]] name = "uv-test" -version = "0.0.40" +version = "0.0.41" dependencies = [ "anyhow", "assert_cmd", @@ -7289,7 +7289,7 @@ dependencies = [ [[package]] name = "uv-tool" -version = "0.0.40" +version = "0.0.41" dependencies = [ "fs-err", "owo-colors", @@ -7318,7 +7318,7 @@ dependencies = [ [[package]] name = "uv-torch" -version = "0.0.40" +version = "0.0.41" dependencies = [ "clap", "either", @@ -7338,7 +7338,7 @@ dependencies = [ [[package]] name = "uv-trampoline-builder" -version = "0.0.40" +version = "0.0.41" dependencies = [ "anyhow", "assert_cmd", @@ -7356,7 +7356,7 @@ dependencies = [ [[package]] name = "uv-types" -version = "0.0.40" +version = "0.0.41" dependencies = [ "anyhow", "dashmap", @@ -7378,7 +7378,7 @@ dependencies = [ [[package]] name = "uv-unix" -version = "0.0.40" +version = "0.0.41" dependencies = [ "nix 0.31.2", "thiserror 2.0.18", @@ -7386,11 +7386,11 @@ dependencies = [ [[package]] name = "uv-version" -version = "0.11.7" +version = "0.11.8" [[package]] name = "uv-virtualenv" -version = "0.0.40" +version = "0.0.41" dependencies = [ "console", "fs-err", @@ -7411,7 +7411,7 @@ dependencies = [ [[package]] name = "uv-warnings" -version = "0.0.40" +version = "0.0.41" dependencies = [ "anstream", "anyhow", @@ -7423,14 +7423,14 @@ dependencies = [ [[package]] name = "uv-windows" -version = "0.0.40" +version = "0.0.41" dependencies = [ "windows", ] [[package]] name = "uv-workspace" -version = "0.0.40" +version = "0.0.41" dependencies = [ "anyhow", "assert_fs", diff --git a/Cargo.toml b/Cargo.toml index b13cd0fa49031..5e42137881828 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,77 +16,77 @@ authors = ["uv"] license = "MIT OR Apache-2.0" [workspace.dependencies] -uv-audit = { version = "0.0.40", path = "crates/uv-audit" } -uv-auth = { version = "0.0.40", path = "crates/uv-auth" } -uv-bin-install = { version = "0.0.40", path = "crates/uv-bin-install" } -uv-build-backend = { version = "0.0.40", path = "crates/uv-build-backend" } -uv-build-frontend = { version = "0.0.40", path = "crates/uv-build-frontend" } -uv-cache = { version = "0.0.40", path = "crates/uv-cache" } -uv-cache-info = { version = "0.0.40", path = "crates/uv-cache-info" } -uv-cache-key = { version = "0.0.40", path = "crates/uv-cache-key" } -uv-cli = { version = "0.0.40", path = "crates/uv-cli" } -uv-client = { version = "0.0.40", path = "crates/uv-client" } -uv-configuration = { version = "0.0.40", path = "crates/uv-configuration" } -uv-console = { version = "0.0.40", path = "crates/uv-console" } -uv-dirs = { version = "0.0.40", path = "crates/uv-dirs" } -uv-dispatch = { version = "0.0.40", path = "crates/uv-dispatch" } -uv-distribution = { version = "0.0.40", path = "crates/uv-distribution" } -uv-distribution-filename = { version = "0.0.40", path = "crates/uv-distribution-filename" } -uv-distribution-types = { version = "0.0.40", path = "crates/uv-distribution-types" } -uv-extract = { version = "0.0.40", path = "crates/uv-extract" } -uv-flags = { version = "0.0.40", path = "crates/uv-flags" } -uv-fs = { version = "0.0.40", path = "crates/uv-fs", features = [ +uv-audit = { version = "0.0.41", path = "crates/uv-audit" } +uv-auth = { version = "0.0.41", path = "crates/uv-auth" } +uv-bin-install = { version = "0.0.41", path = "crates/uv-bin-install" } +uv-build-backend = { version = "0.0.41", path = "crates/uv-build-backend" } +uv-build-frontend = { version = "0.0.41", path = "crates/uv-build-frontend" } +uv-cache = { version = "0.0.41", path = "crates/uv-cache" } +uv-cache-info = { version = "0.0.41", path = "crates/uv-cache-info" } +uv-cache-key = { version = "0.0.41", path = "crates/uv-cache-key" } +uv-cli = { version = "0.0.41", path = "crates/uv-cli" } +uv-client = { version = "0.0.41", path = "crates/uv-client" } +uv-configuration = { version = "0.0.41", path = "crates/uv-configuration" } +uv-console = { version = "0.0.41", path = "crates/uv-console" } +uv-dirs = { version = "0.0.41", path = "crates/uv-dirs" } +uv-dispatch = { version = "0.0.41", path = "crates/uv-dispatch" } +uv-distribution = { version = "0.0.41", path = "crates/uv-distribution" } +uv-distribution-filename = { version = "0.0.41", path = "crates/uv-distribution-filename" } +uv-distribution-types = { version = "0.0.41", path = "crates/uv-distribution-types" } +uv-extract = { version = "0.0.41", path = "crates/uv-extract" } +uv-flags = { version = "0.0.41", path = "crates/uv-flags" } +uv-fs = { version = "0.0.41", path = "crates/uv-fs", features = [ "serde", "tokio", ] } -uv-git = { version = "0.0.40", path = "crates/uv-git" } -uv-git-types = { version = "0.0.40", path = "crates/uv-git-types" } -uv-globfilter = { version = "0.0.40", path = "crates/uv-globfilter" } -uv-install-wheel = { version = "0.0.40", path = "crates/uv-install-wheel", default-features = false } -uv-installer = { version = "0.0.40", path = "crates/uv-installer" } -uv-keyring = { version = "0.0.40", path = "crates/uv-keyring" } -uv-logging = { version = "0.0.40", path = "crates/uv-logging" } -uv-macros = { version = "0.0.40", path = "crates/uv-macros" } -uv-metadata = { version = "0.0.40", path = "crates/uv-metadata" } -uv-normalize = { version = "0.0.40", path = "crates/uv-normalize" } -uv-once-map = { version = "0.0.40", path = "crates/uv-once-map" } -uv-options-metadata = { version = "0.0.40", path = "crates/uv-options-metadata" } -uv-performance-memory-allocator = { version = "0.0.40", path = "crates/uv-performance-memory-allocator" } -uv-pep440 = { version = "0.0.40", path = "crates/uv-pep440", features = [ +uv-git = { version = "0.0.41", path = "crates/uv-git" } +uv-git-types = { version = "0.0.41", path = "crates/uv-git-types" } +uv-globfilter = { version = "0.0.41", path = "crates/uv-globfilter" } +uv-install-wheel = { version = "0.0.41", path = "crates/uv-install-wheel", default-features = false } +uv-installer = { version = "0.0.41", path = "crates/uv-installer" } +uv-keyring = { version = "0.0.41", path = "crates/uv-keyring" } +uv-logging = { version = "0.0.41", path = "crates/uv-logging" } +uv-macros = { version = "0.0.41", path = "crates/uv-macros" } +uv-metadata = { version = "0.0.41", path = "crates/uv-metadata" } +uv-normalize = { version = "0.0.41", path = "crates/uv-normalize" } +uv-once-map = { version = "0.0.41", path = "crates/uv-once-map" } +uv-options-metadata = { version = "0.0.41", path = "crates/uv-options-metadata" } +uv-performance-memory-allocator = { version = "0.0.41", path = "crates/uv-performance-memory-allocator" } +uv-pep440 = { version = "0.0.41", path = "crates/uv-pep440", features = [ "tracing", "rkyv", "version-ranges", ] } -uv-pep508 = { version = "0.0.40", path = "crates/uv-pep508", features = [ +uv-pep508 = { version = "0.0.41", path = "crates/uv-pep508", features = [ "non-pep508-extensions", ] } -uv-platform = { version = "0.0.40", path = "crates/uv-platform" } -uv-platform-tags = { version = "0.0.40", path = "crates/uv-platform-tags" } -uv-preview = { version = "0.0.40", path = "crates/uv-preview" } -uv-publish = { version = "0.0.40", path = "crates/uv-publish" } -uv-pypi-types = { version = "0.0.40", path = "crates/uv-pypi-types" } -uv-python = { version = "0.0.40", path = "crates/uv-python" } -uv-redacted = { version = "0.0.40", path = "crates/uv-redacted" } -uv-requirements = { version = "0.0.40", path = "crates/uv-requirements" } -uv-requirements-txt = { version = "0.0.40", path = "crates/uv-requirements-txt" } -uv-resolver = { version = "0.0.40", path = "crates/uv-resolver" } -uv-scripts = { version = "0.0.40", path = "crates/uv-scripts" } -uv-settings = { version = "0.0.40", path = "crates/uv-settings" } -uv-shell = { version = "0.0.40", path = "crates/uv-shell" } -uv-small-str = { version = "0.0.40", path = "crates/uv-small-str" } -uv-state = { version = "0.0.40", path = "crates/uv-state" } -uv-static = { version = "0.0.40", path = "crates/uv-static" } -uv-test = { version = "0.0.40", path = "crates/uv-test" } -uv-tool = { version = "0.0.40", path = "crates/uv-tool" } -uv-torch = { version = "0.0.40", path = "crates/uv-torch" } -uv-trampoline-builder = { version = "0.0.40", path = "crates/uv-trampoline-builder" } -uv-types = { version = "0.0.40", path = "crates/uv-types" } -uv-unix = { version = "0.0.40", path = "crates/uv-unix" } -uv-version = { version = "0.11.7", path = "crates/uv-version" } -uv-virtualenv = { version = "0.0.40", path = "crates/uv-virtualenv" } -uv-warnings = { version = "0.0.40", path = "crates/uv-warnings" } -uv-windows = { version = "0.0.40", path = "crates/uv-windows" } -uv-workspace = { version = "0.0.40", path = "crates/uv-workspace" } +uv-platform = { version = "0.0.41", path = "crates/uv-platform" } +uv-platform-tags = { version = "0.0.41", path = "crates/uv-platform-tags" } +uv-preview = { version = "0.0.41", path = "crates/uv-preview" } +uv-publish = { version = "0.0.41", path = "crates/uv-publish" } +uv-pypi-types = { version = "0.0.41", path = "crates/uv-pypi-types" } +uv-python = { version = "0.0.41", path = "crates/uv-python" } +uv-redacted = { version = "0.0.41", path = "crates/uv-redacted" } +uv-requirements = { version = "0.0.41", path = "crates/uv-requirements" } +uv-requirements-txt = { version = "0.0.41", path = "crates/uv-requirements-txt" } +uv-resolver = { version = "0.0.41", path = "crates/uv-resolver" } +uv-scripts = { version = "0.0.41", path = "crates/uv-scripts" } +uv-settings = { version = "0.0.41", path = "crates/uv-settings" } +uv-shell = { version = "0.0.41", path = "crates/uv-shell" } +uv-small-str = { version = "0.0.41", path = "crates/uv-small-str" } +uv-state = { version = "0.0.41", path = "crates/uv-state" } +uv-static = { version = "0.0.41", path = "crates/uv-static" } +uv-test = { version = "0.0.41", path = "crates/uv-test" } +uv-tool = { version = "0.0.41", path = "crates/uv-tool" } +uv-torch = { version = "0.0.41", path = "crates/uv-torch" } +uv-trampoline-builder = { version = "0.0.41", path = "crates/uv-trampoline-builder" } +uv-types = { version = "0.0.41", path = "crates/uv-types" } +uv-unix = { version = "0.0.41", path = "crates/uv-unix" } +uv-version = { version = "0.11.8", path = "crates/uv-version" } +uv-virtualenv = { version = "0.0.41", path = "crates/uv-virtualenv" } +uv-warnings = { version = "0.0.41", path = "crates/uv-warnings" } +uv-windows = { version = "0.0.41", path = "crates/uv-windows" } +uv-workspace = { version = "0.0.41", path = "crates/uv-workspace" } ambient-id = { version = "0.0.11", default-features = false, features = [ "reqwest-middleware", diff --git a/crates/uv-audit/Cargo.toml b/crates/uv-audit/Cargo.toml index e6e9fdac003c0..316028b780098 100644 --- a/crates/uv-audit/Cargo.toml +++ b/crates/uv-audit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-audit" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition.workspace = true rust-version.workspace = true diff --git a/crates/uv-audit/README.md b/crates/uv-audit/README.md index 0aac80c3cba6e..8b50ea51a3744 100644 --- a/crates/uv-audit/README.md +++ b/crates/uv-audit/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-audit). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-audit). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-auth/Cargo.toml b/crates/uv-auth/Cargo.toml index 6dc35abc35008..5ee4d2a91fb46 100644 --- a/crates/uv-auth/Cargo.toml +++ b/crates/uv-auth/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-auth" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-auth/README.md b/crates/uv-auth/README.md index fd27c39e4b52e..711ace429d9f6 100644 --- a/crates/uv-auth/README.md +++ b/crates/uv-auth/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-auth). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-auth). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-bench/Cargo.toml b/crates/uv-bench/Cargo.toml index 0fa6b5b1e4e6f..39bd514d96c26 100644 --- a/crates/uv-bench/Cargo.toml +++ b/crates/uv-bench/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-bench" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" publish = false authors = { workspace = true } diff --git a/crates/uv-bench/README.md b/crates/uv-bench/README.md index fe2e10cefa532..c5b84fa37696b 100644 --- a/crates/uv-bench/README.md +++ b/crates/uv-bench/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-bench). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-bench). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-bin-install/Cargo.toml b/crates/uv-bin-install/Cargo.toml index 95712f6295fe5..0d612eba2332a 100644 --- a/crates/uv-bin-install/Cargo.toml +++ b/crates/uv-bin-install/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-bin-install" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-bin-install/README.md b/crates/uv-bin-install/README.md index 9fda89167451b..7c94d7999f1c0 100644 --- a/crates/uv-bin-install/README.md +++ b/crates/uv-bin-install/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-bin-install). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-bin-install). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-build-backend/Cargo.toml b/crates/uv-build-backend/Cargo.toml index 1a987be1c0afa..138084d83097c 100644 --- a/crates/uv-build-backend/Cargo.toml +++ b/crates/uv-build-backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-build-backend" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-build-backend/README.md b/crates/uv-build-backend/README.md index a2059c9459e69..848f5eea63362 100644 --- a/crates/uv-build-backend/README.md +++ b/crates/uv-build-backend/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-build-backend). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-build-backend). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-build-frontend/Cargo.toml b/crates/uv-build-frontend/Cargo.toml index 4311c00d1a2ca..0a25a44fb3c76 100644 --- a/crates/uv-build-frontend/Cargo.toml +++ b/crates/uv-build-frontend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-build-frontend" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-build-frontend/README.md b/crates/uv-build-frontend/README.md index 216434af5cebb..f6cae65698a7b 100644 --- a/crates/uv-build-frontend/README.md +++ b/crates/uv-build-frontend/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-build-frontend). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-build-frontend). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-build/Cargo.toml b/crates/uv-build/Cargo.toml index 21e5697add40f..327d5abd91df6 100644 --- a/crates/uv-build/Cargo.toml +++ b/crates/uv-build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-build" -version = "0.11.7" +version = "0.11.8" description = "A Python build backend" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-build/pyproject.toml b/crates/uv-build/pyproject.toml index edc668786a06f..a271ea577634a 100644 --- a/crates/uv-build/pyproject.toml +++ b/crates/uv-build/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uv-build" -version = "0.11.7" +version = "0.11.8" description = "The uv build backend" authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }] requires-python = ">=3.8" diff --git a/crates/uv-cache-info/Cargo.toml b/crates/uv-cache-info/Cargo.toml index b472ecd2bc3ae..0f15dfceac28a 100644 --- a/crates/uv-cache-info/Cargo.toml +++ b/crates/uv-cache-info/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-cache-info" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-cache-info/README.md b/crates/uv-cache-info/README.md index af8522d6da1da..81521d8d4e61d 100644 --- a/crates/uv-cache-info/README.md +++ b/crates/uv-cache-info/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-cache-info). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-cache-info). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-cache-key/Cargo.toml b/crates/uv-cache-key/Cargo.toml index 25dcdb39075b6..3e34b7706bccc 100644 --- a/crates/uv-cache-key/Cargo.toml +++ b/crates/uv-cache-key/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-cache-key" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-cache-key/README.md b/crates/uv-cache-key/README.md index 51aee89ea2bd9..87c51861d874a 100644 --- a/crates/uv-cache-key/README.md +++ b/crates/uv-cache-key/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-cache-key). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-cache-key). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-cache/Cargo.toml b/crates/uv-cache/Cargo.toml index 617a68b0f9cf3..9fb59a15b47d1 100644 --- a/crates/uv-cache/Cargo.toml +++ b/crates/uv-cache/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-cache" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-cache/README.md b/crates/uv-cache/README.md index 551fc51f8ac45..8406ccd39efd2 100644 --- a/crates/uv-cache/README.md +++ b/crates/uv-cache/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-cache). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-cache). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-cli/Cargo.toml b/crates/uv-cli/Cargo.toml index dda0a1a836f05..71ee40c0ad1f2 100644 --- a/crates/uv-cli/Cargo.toml +++ b/crates/uv-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-cli" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-cli/README.md b/crates/uv-cli/README.md index dcd9124888cf4..9cbcf506779dd 100644 --- a/crates/uv-cli/README.md +++ b/crates/uv-cli/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-cli). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-cli). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-client/Cargo.toml b/crates/uv-client/Cargo.toml index 5f79464109ee4..6294b11bd4f14 100644 --- a/crates/uv-client/Cargo.toml +++ b/crates/uv-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-client" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-client/README.md b/crates/uv-client/README.md index 18f5da5dd7062..5fcc783d1ff2a 100644 --- a/crates/uv-client/README.md +++ b/crates/uv-client/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-client). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-client). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-configuration/Cargo.toml b/crates/uv-configuration/Cargo.toml index 35af3a0ebc2b6..ffc30d8124b5f 100644 --- a/crates/uv-configuration/Cargo.toml +++ b/crates/uv-configuration/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-configuration" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-configuration/README.md b/crates/uv-configuration/README.md index 2e35af3e9854d..731170d17d162 100644 --- a/crates/uv-configuration/README.md +++ b/crates/uv-configuration/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-configuration). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-configuration). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-console/Cargo.toml b/crates/uv-console/Cargo.toml index 90f12c89d0e3c..f8e1744cb8ced 100644 --- a/crates/uv-console/Cargo.toml +++ b/crates/uv-console/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-console" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-console/README.md b/crates/uv-console/README.md index 62d18e072a7fb..93dac043d8b1e 100644 --- a/crates/uv-console/README.md +++ b/crates/uv-console/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-console). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-console). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-dev/Cargo.toml b/crates/uv-dev/Cargo.toml index 0df366d348f5f..7aed0060bbc95 100644 --- a/crates/uv-dev/Cargo.toml +++ b/crates/uv-dev/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-dev" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" publish = false diff --git a/crates/uv-dev/README.md b/crates/uv-dev/README.md index 7a51538c14b8d..7b625d8c1ea9c 100644 --- a/crates/uv-dev/README.md +++ b/crates/uv-dev/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-dev). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-dev). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-dirs/Cargo.toml b/crates/uv-dirs/Cargo.toml index 4796247d2c090..f46acf3a56819 100644 --- a/crates/uv-dirs/Cargo.toml +++ b/crates/uv-dirs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-dirs" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-dirs/README.md b/crates/uv-dirs/README.md index 48bc22c70cc5b..09fb3e3845b27 100644 --- a/crates/uv-dirs/README.md +++ b/crates/uv-dirs/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-dirs). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-dirs). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-dispatch/Cargo.toml b/crates/uv-dispatch/Cargo.toml index 6fb96b2f9d176..3565d24dd019d 100644 --- a/crates/uv-dispatch/Cargo.toml +++ b/crates/uv-dispatch/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-dispatch" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-dispatch/README.md b/crates/uv-dispatch/README.md index 41e5a59737029..9af4dbf5b252f 100644 --- a/crates/uv-dispatch/README.md +++ b/crates/uv-dispatch/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-dispatch). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-dispatch). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-distribution-filename/Cargo.toml b/crates/uv-distribution-filename/Cargo.toml index ef3c546dbcbee..8fcac7f26b9ad 100644 --- a/crates/uv-distribution-filename/Cargo.toml +++ b/crates/uv-distribution-filename/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-distribution-filename" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-distribution-filename/README.md b/crates/uv-distribution-filename/README.md index fd73ca80f8591..78c77cef33333 100644 --- a/crates/uv-distribution-filename/README.md +++ b/crates/uv-distribution-filename/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-distribution-filename). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-distribution-filename). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-distribution-types/Cargo.toml b/crates/uv-distribution-types/Cargo.toml index 45d001993b5e1..91848832db442 100644 --- a/crates/uv-distribution-types/Cargo.toml +++ b/crates/uv-distribution-types/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-distribution-types" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-distribution-types/README.md b/crates/uv-distribution-types/README.md index a6846ef09918e..f1c9c3f5af8fc 100644 --- a/crates/uv-distribution-types/README.md +++ b/crates/uv-distribution-types/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-distribution-types). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-distribution-types). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-distribution/Cargo.toml b/crates/uv-distribution/Cargo.toml index f72ca0609b94f..9f97115fc6eca 100644 --- a/crates/uv-distribution/Cargo.toml +++ b/crates/uv-distribution/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-distribution" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-distribution/README.md b/crates/uv-distribution/README.md index 8b369870627d6..0f9c6b4c89b5d 100644 --- a/crates/uv-distribution/README.md +++ b/crates/uv-distribution/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-distribution). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-distribution). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-extract/Cargo.toml b/crates/uv-extract/Cargo.toml index a77ee06a8676b..579edabd2a84c 100644 --- a/crates/uv-extract/Cargo.toml +++ b/crates/uv-extract/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-extract" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-extract/README.md b/crates/uv-extract/README.md index 476458a92fd58..b886fbc65d4f8 100644 --- a/crates/uv-extract/README.md +++ b/crates/uv-extract/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-extract). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-extract). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-flags/Cargo.toml b/crates/uv-flags/Cargo.toml index 5061b77c11cdb..309a0c06ea7a5 100644 --- a/crates/uv-flags/Cargo.toml +++ b/crates/uv-flags/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-flags" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-flags/README.md b/crates/uv-flags/README.md index e1615395d7328..6e8249417576e 100644 --- a/crates/uv-flags/README.md +++ b/crates/uv-flags/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-flags). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-flags). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-fs/Cargo.toml b/crates/uv-fs/Cargo.toml index 95211349232c3..a6c488f788af8 100644 --- a/crates/uv-fs/Cargo.toml +++ b/crates/uv-fs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-fs" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-fs/README.md b/crates/uv-fs/README.md index 52555baf91901..47855e02ba28c 100644 --- a/crates/uv-fs/README.md +++ b/crates/uv-fs/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-fs). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-fs). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-git-types/Cargo.toml b/crates/uv-git-types/Cargo.toml index 80442d0280984..60f11c676d533 100644 --- a/crates/uv-git-types/Cargo.toml +++ b/crates/uv-git-types/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-git-types" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-git-types/README.md b/crates/uv-git-types/README.md index 3c84524b01683..d3b3dee4a1224 100644 --- a/crates/uv-git-types/README.md +++ b/crates/uv-git-types/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-git-types). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-git-types). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-git/Cargo.toml b/crates/uv-git/Cargo.toml index 636e54b71aad1..d98e55f9e03a0 100644 --- a/crates/uv-git/Cargo.toml +++ b/crates/uv-git/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-git" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-git/README.md b/crates/uv-git/README.md index 09ef8b75307b7..583f0ce8623bc 100644 --- a/crates/uv-git/README.md +++ b/crates/uv-git/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-git). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-git). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-globfilter/Cargo.toml b/crates/uv-globfilter/Cargo.toml index 8335480415baa..0cbcc5dc3565a 100644 --- a/crates/uv-globfilter/Cargo.toml +++ b/crates/uv-globfilter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-globfilter" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" readme = "README.md" edition = { workspace = true } diff --git a/crates/uv-install-wheel/Cargo.toml b/crates/uv-install-wheel/Cargo.toml index 8fdd70fdad921..7e16ad8288efc 100644 --- a/crates/uv-install-wheel/Cargo.toml +++ b/crates/uv-install-wheel/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-install-wheel" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" keywords = ["wheel", "python"] diff --git a/crates/uv-install-wheel/README.md b/crates/uv-install-wheel/README.md index 0ecb80b0cda0a..3b8af3b744147 100644 --- a/crates/uv-install-wheel/README.md +++ b/crates/uv-install-wheel/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-install-wheel). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-install-wheel). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-installer/Cargo.toml b/crates/uv-installer/Cargo.toml index 46e5c6255f315..a7e73e9dd963e 100644 --- a/crates/uv-installer/Cargo.toml +++ b/crates/uv-installer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-installer" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-installer/README.md b/crates/uv-installer/README.md index aaf06179c981e..d7be762c22419 100644 --- a/crates/uv-installer/README.md +++ b/crates/uv-installer/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-installer). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-installer). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-keyring/Cargo.toml b/crates/uv-keyring/Cargo.toml index c355dec2cfaa6..306f33d3324f8 100644 --- a/crates/uv-keyring/Cargo.toml +++ b/crates/uv-keyring/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-keyring" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-logging/Cargo.toml b/crates/uv-logging/Cargo.toml index ef1b8a79a8eb4..5532446c1a8f5 100644 --- a/crates/uv-logging/Cargo.toml +++ b/crates/uv-logging/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-logging" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-logging/README.md b/crates/uv-logging/README.md index 4f53ed8de73fe..bc7dc9d9c8845 100644 --- a/crates/uv-logging/README.md +++ b/crates/uv-logging/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-logging). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-logging). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-macros/Cargo.toml b/crates/uv-macros/Cargo.toml index 0d0db434e7d3d..4c4c11fd18b9d 100644 --- a/crates/uv-macros/Cargo.toml +++ b/crates/uv-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-macros" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-macros/README.md b/crates/uv-macros/README.md index 4175b68ed4c2a..ea52582f90650 100644 --- a/crates/uv-macros/README.md +++ b/crates/uv-macros/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-macros). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-macros). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-metadata/Cargo.toml b/crates/uv-metadata/Cargo.toml index 15ef807e73cae..51d879819634e 100644 --- a/crates/uv-metadata/Cargo.toml +++ b/crates/uv-metadata/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-metadata" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-metadata/README.md b/crates/uv-metadata/README.md index cf994a703d749..177e8b34f5b7b 100644 --- a/crates/uv-metadata/README.md +++ b/crates/uv-metadata/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-metadata). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-metadata). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-normalize/Cargo.toml b/crates/uv-normalize/Cargo.toml index 679c77182bc72..490219f63e2c4 100644 --- a/crates/uv-normalize/Cargo.toml +++ b/crates/uv-normalize/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-normalize" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-normalize/README.md b/crates/uv-normalize/README.md index 1b6e91ca5463a..80eabcce64e5b 100644 --- a/crates/uv-normalize/README.md +++ b/crates/uv-normalize/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-normalize). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-normalize). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-once-map/Cargo.toml b/crates/uv-once-map/Cargo.toml index 738c94aabea6a..53ce45f43db4c 100644 --- a/crates/uv-once-map/Cargo.toml +++ b/crates/uv-once-map/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-once-map" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-once-map/README.md b/crates/uv-once-map/README.md index dfee9170f9155..c38dc5f1e46d3 100644 --- a/crates/uv-once-map/README.md +++ b/crates/uv-once-map/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-once-map). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-once-map). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-options-metadata/Cargo.toml b/crates/uv-options-metadata/Cargo.toml index 71951ab52a014..e13ec90cb7334 100644 --- a/crates/uv-options-metadata/Cargo.toml +++ b/crates/uv-options-metadata/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-options-metadata" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-options-metadata/README.md b/crates/uv-options-metadata/README.md index e756e3c09e2d4..e1153b6100de2 100644 --- a/crates/uv-options-metadata/README.md +++ b/crates/uv-options-metadata/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-options-metadata). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-options-metadata). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-pep440/Cargo.toml b/crates/uv-pep440/Cargo.toml index c0e7bb3849a64..1fb51617ecd88 100644 --- a/crates/uv-pep440/Cargo.toml +++ b/crates/uv-pep440/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-pep440" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" license = "Apache-2.0 OR BSD-2-Clause" include = ["/src", "Changelog.md", "License-Apache", "License-BSD", "Readme.md", "pyproject.toml"] diff --git a/crates/uv-pep440/README.md b/crates/uv-pep440/README.md index 7c7c833ff9ffa..57ffc4a8865e9 100644 --- a/crates/uv-pep440/README.md +++ b/crates/uv-pep440/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-pep440). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-pep440). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-pep508/Cargo.toml b/crates/uv-pep508/Cargo.toml index 8247a62ee5c69..68117781036c2 100644 --- a/crates/uv-pep508/Cargo.toml +++ b/crates/uv-pep508/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-pep508" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" include = ["/src", "Changelog.md", "License-Apache", "License-BSD", "Readme.md", "pyproject.toml"] license = "Apache-2.0 OR BSD-2-Clause" diff --git a/crates/uv-pep508/README.md b/crates/uv-pep508/README.md index 48b160f08f628..ed38271349376 100644 --- a/crates/uv-pep508/README.md +++ b/crates/uv-pep508/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-pep508). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-pep508). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-performance-memory-allocator/Cargo.toml b/crates/uv-performance-memory-allocator/Cargo.toml index 98ea972988e89..70d26180370ec 100644 --- a/crates/uv-performance-memory-allocator/Cargo.toml +++ b/crates/uv-performance-memory-allocator/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-performance-memory-allocator" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-performance-memory-allocator/README.md b/crates/uv-performance-memory-allocator/README.md index 7b6f282d765f9..3627a69e09ab3 100644 --- a/crates/uv-performance-memory-allocator/README.md +++ b/crates/uv-performance-memory-allocator/README.md @@ -5,9 +5,9 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source can be found -[here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-performance-memory-allocator). +[here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-performance-memory-allocator). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-platform-tags/Cargo.toml b/crates/uv-platform-tags/Cargo.toml index ad82d3d1ecdad..10efa42a8b0bc 100644 --- a/crates/uv-platform-tags/Cargo.toml +++ b/crates/uv-platform-tags/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-platform-tags" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-platform-tags/README.md b/crates/uv-platform-tags/README.md index fe9a62f90073c..1dc7e18135b90 100644 --- a/crates/uv-platform-tags/README.md +++ b/crates/uv-platform-tags/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-platform-tags). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-platform-tags). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-platform/Cargo.toml b/crates/uv-platform/Cargo.toml index 63bb1075ad396..c59d3f31388d4 100644 --- a/crates/uv-platform/Cargo.toml +++ b/crates/uv-platform/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-platform" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-platform/README.md b/crates/uv-platform/README.md index 6926c60ea0f12..3fbd9d65d0270 100644 --- a/crates/uv-platform/README.md +++ b/crates/uv-platform/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-platform). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-platform). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-preview/Cargo.toml b/crates/uv-preview/Cargo.toml index 7b7554e1153a8..a5c40bcd9f476 100644 --- a/crates/uv-preview/Cargo.toml +++ b/crates/uv-preview/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-preview" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-preview/README.md b/crates/uv-preview/README.md index 773ec020b35d4..7469b6bc287d6 100644 --- a/crates/uv-preview/README.md +++ b/crates/uv-preview/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-preview). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-preview). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-publish/Cargo.toml b/crates/uv-publish/Cargo.toml index e1145f9c9f5d9..9fa41f24c0bed 100644 --- a/crates/uv-publish/Cargo.toml +++ b/crates/uv-publish/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-publish" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-publish/README.md b/crates/uv-publish/README.md index 2571f8c33af60..a68909f341448 100644 --- a/crates/uv-publish/README.md +++ b/crates/uv-publish/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-publish). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-publish). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-pypi-types/Cargo.toml b/crates/uv-pypi-types/Cargo.toml index 04aa6a3b56bed..528933b23a681 100644 --- a/crates/uv-pypi-types/Cargo.toml +++ b/crates/uv-pypi-types/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-pypi-types" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-pypi-types/README.md b/crates/uv-pypi-types/README.md index 89fcec606560d..fd20ffc01612b 100644 --- a/crates/uv-pypi-types/README.md +++ b/crates/uv-pypi-types/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-pypi-types). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-pypi-types). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-python/Cargo.toml b/crates/uv-python/Cargo.toml index 0a5d8c2ade8e9..50bd951e32ba0 100644 --- a/crates/uv-python/Cargo.toml +++ b/crates/uv-python/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-python" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-python/README.md b/crates/uv-python/README.md index 09e99dee1d34b..1166f4d698e2e 100644 --- a/crates/uv-python/README.md +++ b/crates/uv-python/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-python). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-python). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-redacted/Cargo.toml b/crates/uv-redacted/Cargo.toml index 28734235b7ee0..cddc5c011cc23 100644 --- a/crates/uv-redacted/Cargo.toml +++ b/crates/uv-redacted/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-redacted" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-redacted/README.md b/crates/uv-redacted/README.md index 6a94e5e2351d5..257c1db203998 100644 --- a/crates/uv-redacted/README.md +++ b/crates/uv-redacted/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-redacted). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-redacted). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-requirements-txt/Cargo.toml b/crates/uv-requirements-txt/Cargo.toml index 020b6ae0981b7..01b2f872f60c2 100644 --- a/crates/uv-requirements-txt/Cargo.toml +++ b/crates/uv-requirements-txt/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-requirements-txt" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-requirements-txt/README.md b/crates/uv-requirements-txt/README.md index 22fe0a74c51b4..a8f8aa1e3634a 100644 --- a/crates/uv-requirements-txt/README.md +++ b/crates/uv-requirements-txt/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-requirements-txt). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-requirements-txt). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-requirements/Cargo.toml b/crates/uv-requirements/Cargo.toml index 1a59ee809900e..94027ec652b6c 100644 --- a/crates/uv-requirements/Cargo.toml +++ b/crates/uv-requirements/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-requirements" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-requirements/README.md b/crates/uv-requirements/README.md index bd9c4fb3f8a80..0c40aee6a38f7 100644 --- a/crates/uv-requirements/README.md +++ b/crates/uv-requirements/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-requirements). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-requirements). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-resolver/Cargo.toml b/crates/uv-resolver/Cargo.toml index 184dba84341c0..2f404cd4bb84e 100644 --- a/crates/uv-resolver/Cargo.toml +++ b/crates/uv-resolver/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-resolver" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-resolver/README.md b/crates/uv-resolver/README.md index 78cab84315dfa..a19f8208da453 100644 --- a/crates/uv-resolver/README.md +++ b/crates/uv-resolver/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-resolver). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-resolver). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-scripts/Cargo.toml b/crates/uv-scripts/Cargo.toml index b595a344d3db6..d10eaa3a50188 100644 --- a/crates/uv-scripts/Cargo.toml +++ b/crates/uv-scripts/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-scripts" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-scripts/README.md b/crates/uv-scripts/README.md index 5a5e48a487932..6993f44938249 100644 --- a/crates/uv-scripts/README.md +++ b/crates/uv-scripts/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-scripts). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-scripts). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-settings/Cargo.toml b/crates/uv-settings/Cargo.toml index ccd87473b8abf..19a5fc31d1ef4 100644 --- a/crates/uv-settings/Cargo.toml +++ b/crates/uv-settings/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-settings" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-settings/README.md b/crates/uv-settings/README.md index c3cf7c9f0ea82..fdd5f8f3cf021 100644 --- a/crates/uv-settings/README.md +++ b/crates/uv-settings/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-settings). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-settings). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-shell/Cargo.toml b/crates/uv-shell/Cargo.toml index a7dcef2a7ae2d..31b4842bad8e4 100644 --- a/crates/uv-shell/Cargo.toml +++ b/crates/uv-shell/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-shell" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-shell/README.md b/crates/uv-shell/README.md index 91ecd22fc4eff..d108e04ad6876 100644 --- a/crates/uv-shell/README.md +++ b/crates/uv-shell/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-shell). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-shell). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-small-str/Cargo.toml b/crates/uv-small-str/Cargo.toml index d181522107db2..cab6985428f9d 100644 --- a/crates/uv-small-str/Cargo.toml +++ b/crates/uv-small-str/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-small-str" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-small-str/README.md b/crates/uv-small-str/README.md index 36cbfd8bf49c2..021dad70cc551 100644 --- a/crates/uv-small-str/README.md +++ b/crates/uv-small-str/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-small-str). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-small-str). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-state/Cargo.toml b/crates/uv-state/Cargo.toml index 8e2c1fea77fb8..1ea002b111259 100644 --- a/crates/uv-state/Cargo.toml +++ b/crates/uv-state/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-state" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-state/README.md b/crates/uv-state/README.md index c2c391945ce43..edf219812c9cc 100644 --- a/crates/uv-state/README.md +++ b/crates/uv-state/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-state). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-state). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-static/Cargo.toml b/crates/uv-static/Cargo.toml index c28d856851e2e..9641329df024e 100644 --- a/crates/uv-static/Cargo.toml +++ b/crates/uv-static/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-static" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-static/README.md b/crates/uv-static/README.md index 12bba9ca683d2..95aa95c2751d5 100644 --- a/crates/uv-static/README.md +++ b/crates/uv-static/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-static). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-static). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-static/src/env_vars.rs b/crates/uv-static/src/env_vars.rs index 13ffa8bb89ead..8ded548da5054 100644 --- a/crates/uv-static/src/env_vars.rs +++ b/crates/uv-static/src/env_vars.rs @@ -425,7 +425,7 @@ impl EnvVars { /// When set, uv will not discover Python interpreters from the Windows registry or Microsoft /// Store locations, and managed Python installations will not be registered in the Windows /// registry. - #[attr_added_in("next release")] + #[attr_added_in("0.11.8")] pub const UV_PYTHON_NO_REGISTRY: &'static str = "UV_PYTHON_NO_REGISTRY"; /// Managed Python installations information is hardcoded in the `uv` binary. @@ -508,7 +508,7 @@ impl EnvVars { /// /// When set, uv will search for Python interpreters in the directories specified by this /// variable instead of `PATH`. - #[attr_added_in("next release")] + #[attr_added_in("0.11.8")] pub const UV_PYTHON_SEARCH_PATH: &'static str = "UV_PYTHON_SEARCH_PATH"; /// Include resolver and installer output related to environment modifications. @@ -932,7 +932,7 @@ impl EnvVars { /// Cleared for uv's git invocations to ensure git behaves correctly in /// spite of an odd environment. #[attr_hidden] - #[attr_added_in("next release")] + #[attr_added_in("0.11.8")] pub const GIT_COMMON_DIR: &'static str = "GIT_COMMON_DIR"; /// Indicates that the current process is running in GitHub Actions. @@ -1288,7 +1288,7 @@ impl EnvVars { pub const UV_PROJECT: &'static str = "UV_PROJECT"; /// Equivalent to the `--no-project` command-line argument. - #[attr_added_in("next release")] + #[attr_added_in("0.11.8")] pub const UV_NO_PROJECT: &'static str = "UV_NO_PROJECT"; /// Equivalent to the `--directory` command-line argument. `UV_WORKING_DIRECTORY` (added in diff --git a/crates/uv-test/Cargo.toml b/crates/uv-test/Cargo.toml index 5d05e82a32c87..635eef19dc3da 100644 --- a/crates/uv-test/Cargo.toml +++ b/crates/uv-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-test" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-test/README.md b/crates/uv-test/README.md index 595fe3d065235..01c34c05dc8ef 100644 --- a/crates/uv-test/README.md +++ b/crates/uv-test/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-test). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-test). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-tool/Cargo.toml b/crates/uv-tool/Cargo.toml index 0e092057e3af9..1f5aa124538c0 100644 --- a/crates/uv-tool/Cargo.toml +++ b/crates/uv-tool/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-tool" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-tool/README.md b/crates/uv-tool/README.md index c85697db03234..c70055a69b658 100644 --- a/crates/uv-tool/README.md +++ b/crates/uv-tool/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-tool). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-tool). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-torch/Cargo.toml b/crates/uv-torch/Cargo.toml index 256fad50bd8e9..2469289c643e2 100644 --- a/crates/uv-torch/Cargo.toml +++ b/crates/uv-torch/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-torch" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-torch/README.md b/crates/uv-torch/README.md index 9c09c9d2b0984..ea8b2c331a67e 100644 --- a/crates/uv-torch/README.md +++ b/crates/uv-torch/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-torch). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-torch). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-trampoline-builder/Cargo.toml b/crates/uv-trampoline-builder/Cargo.toml index d6a0d382a81f0..7b7ea90424458 100644 --- a/crates/uv-trampoline-builder/Cargo.toml +++ b/crates/uv-trampoline-builder/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-trampoline-builder" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } diff --git a/crates/uv-trampoline-builder/README.md b/crates/uv-trampoline-builder/README.md index 7ad8b34de15db..3303982208642 100644 --- a/crates/uv-trampoline-builder/README.md +++ b/crates/uv-trampoline-builder/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-trampoline-builder). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-trampoline-builder). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-trampoline/Cargo.lock b/crates/uv-trampoline/Cargo.lock index 7ffef1f6e8317..8ce6a3863f63a 100644 --- a/crates/uv-trampoline/Cargo.lock +++ b/crates/uv-trampoline/Cargo.lock @@ -138,7 +138,7 @@ checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "uv-macros" -version = "0.0.40" +version = "0.0.41" dependencies = [ "proc-macro2", "quote", @@ -148,7 +148,7 @@ dependencies = [ [[package]] name = "uv-static" -version = "0.0.40" +version = "0.0.41" dependencies = [ "thiserror", "uv-macros", @@ -169,7 +169,7 @@ dependencies = [ [[package]] name = "uv-windows" -version = "0.0.40" +version = "0.0.41" dependencies = [ "windows", ] diff --git a/crates/uv-types/Cargo.toml b/crates/uv-types/Cargo.toml index ac4bfb0764dd9..b7d8d6d823df2 100644 --- a/crates/uv-types/Cargo.toml +++ b/crates/uv-types/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-types" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-types/README.md b/crates/uv-types/README.md index d0ec30178910e..31621d4450bfd 100644 --- a/crates/uv-types/README.md +++ b/crates/uv-types/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-types). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-types). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-unix/Cargo.toml b/crates/uv-unix/Cargo.toml index b46b720d547b4..05af8290e6552 100644 --- a/crates/uv-unix/Cargo.toml +++ b/crates/uv-unix/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-unix" -version = "0.0.40" +version = "0.0.41" description = "Unix-specific functionality for uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-unix/README.md b/crates/uv-unix/README.md index fcc8e202dec38..a3462b15b3c4d 100644 --- a/crates/uv-unix/README.md +++ b/crates/uv-unix/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-unix). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-unix). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-version/Cargo.toml b/crates/uv-version/Cargo.toml index b18f487320eba..6f4e1e887d115 100644 --- a/crates/uv-version/Cargo.toml +++ b/crates/uv-version/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-version" -version = "0.11.7" +version = "0.11.8" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-version/README.md b/crates/uv-version/README.md index 6d52a877c4d07..607d01859b526 100644 --- a/crates/uv-version/README.md +++ b/crates/uv-version/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.11.7) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-version). +This version (0.11.8) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-version). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-virtualenv/Cargo.toml b/crates/uv-virtualenv/Cargo.toml index b29c740a62c96..dc4fe36fb2c00 100644 --- a/crates/uv-virtualenv/Cargo.toml +++ b/crates/uv-virtualenv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-virtualenv" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" keywords = ["virtualenv", "venv", "python"] diff --git a/crates/uv-warnings/Cargo.toml b/crates/uv-warnings/Cargo.toml index 54a9bfc44a56d..af5f0ce13cada 100644 --- a/crates/uv-warnings/Cargo.toml +++ b/crates/uv-warnings/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-warnings" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-warnings/README.md b/crates/uv-warnings/README.md index 8ae9c9767e0d8..59d0a83fa65cc 100644 --- a/crates/uv-warnings/README.md +++ b/crates/uv-warnings/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-warnings). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-warnings). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-windows/Cargo.toml b/crates/uv-windows/Cargo.toml index b895f81bedbdd..51940212f0807 100644 --- a/crates/uv-windows/Cargo.toml +++ b/crates/uv-windows/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-windows" -version = "0.0.40" +version = "0.0.41" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } diff --git a/crates/uv-windows/README.md b/crates/uv-windows/README.md index 5b47a3166a98a..fa1011c4aa581 100644 --- a/crates/uv-windows/README.md +++ b/crates/uv-windows/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-windows). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-windows). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv-workspace/Cargo.toml b/crates/uv-workspace/Cargo.toml index f6b4715494e16..495299c80bc31 100644 --- a/crates/uv-workspace/Cargo.toml +++ b/crates/uv-workspace/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-workspace" -version = "0.0.40" +version = "0.0.41" description = "This is an internal component crate of uv" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv-workspace/README.md b/crates/uv-workspace/README.md index 3e32b5c6085a2..19ce076db233c 100644 --- a/crates/uv-workspace/README.md +++ b/crates/uv-workspace/README.md @@ -5,8 +5,8 @@ This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here is unstable and will have frequent breaking changes. -This version (0.0.40) is a component of [uv 0.11.7](https://crates.io/crates/uv/0.11.7). The source -can be found [here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv-workspace). +This version (0.0.41) is a component of [uv 0.11.8](https://crates.io/crates/uv/0.11.8). The source +can be found [here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv-workspace). See uv's [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml index 87853d6cadb05..b7c9d3e047983 100644 --- a/crates/uv/Cargo.toml +++ b/crates/uv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv" -version = "0.11.7" +version = "0.11.8" description = "A Python package and project manager" edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/uv/README.md b/crates/uv/README.md index 312604cb3f79b..465102cef7ebf 100644 --- a/crates/uv/README.md +++ b/crates/uv/README.md @@ -10,8 +10,8 @@ for more information. This crate is the entry point to the uv command-line interface. The Rust API exposed here is not considered public interface. -This is version 0.11.7. The source can be found -[here](https://github.com/astral-sh/uv/blob/0.11.7/crates/uv). +This is version 0.11.8. The source can be found +[here](https://github.com/astral-sh/uv/blob/0.11.8/crates/uv). The following uv workspace members are also available: diff --git a/docs/concepts/build-backend.md b/docs/concepts/build-backend.md index 0f87d257b0f80..fbf41bc0f623a 100644 --- a/docs/concepts/build-backend.md +++ b/docs/concepts/build-backend.md @@ -31,7 +31,7 @@ To use uv as a build backend in an existing project, add `uv_build` to the ```toml title="pyproject.toml" [build-system] -requires = ["uv_build>=0.11.7,<0.12"] +requires = ["uv_build>=0.11.8,<0.12"] build-backend = "uv_build" ``` diff --git a/docs/concepts/projects/init.md b/docs/concepts/projects/init.md index 725d164a97654..eb208474d9dee 100644 --- a/docs/concepts/projects/init.md +++ b/docs/concepts/projects/init.md @@ -113,7 +113,7 @@ dependencies = [] example-pkg = "example_pkg:main" [build-system] -requires = ["uv_build>=0.11.7,<0.12"] +requires = ["uv_build>=0.11.8,<0.12"] build-backend = "uv_build" ``` @@ -136,7 +136,7 @@ dependencies = [] example-pkg = "example_pkg:main" [build-system] -requires = ["uv_build>=0.11.7,<0.12"] +requires = ["uv_build>=0.11.8,<0.12"] build-backend = "uv_build" ``` @@ -197,7 +197,7 @@ requires-python = ">=3.11" dependencies = [] [build-system] -requires = ["uv_build>=0.11.7,<0.12"] +requires = ["uv_build>=0.11.8,<0.12"] build-backend = "uv_build" ``` diff --git a/docs/concepts/projects/workspaces.md b/docs/concepts/projects/workspaces.md index ab7f04c20dc98..ee693de528e79 100644 --- a/docs/concepts/projects/workspaces.md +++ b/docs/concepts/projects/workspaces.md @@ -75,7 +75,7 @@ bird-feeder = { workspace = true } members = ["packages/*"] [build-system] -requires = ["uv_build>=0.11.7,<0.12"] +requires = ["uv_build>=0.11.8,<0.12"] build-backend = "uv_build" ``` @@ -106,7 +106,7 @@ tqdm = { git = "https://github.com/tqdm/tqdm" } members = ["packages/*"] [build-system] -requires = ["uv_build>=0.11.7,<0.12"] +requires = ["uv_build>=0.11.8,<0.12"] build-backend = "uv_build" ``` @@ -188,7 +188,7 @@ dependencies = ["bird-feeder", "tqdm>=4,<5"] bird-feeder = { path = "packages/bird-feeder" } [build-system] -requires = ["uv_build>=0.11.7,<0.12"] +requires = ["uv_build>=0.11.8,<0.12"] build-backend = "uv_build" ``` diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 892caf530630a..3209ed6cca61b 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -25,7 +25,7 @@ uv provides a standalone installer to download and install uv: Request a specific version by including it in the URL: ```console - $ curl -LsSf https://astral.sh/uv/0.11.7/install.sh | sh + $ curl -LsSf https://astral.sh/uv/0.11.8/install.sh | sh ``` === "Windows" @@ -41,7 +41,7 @@ uv provides a standalone installer to download and install uv: Request a specific version by including it in the URL: ```pwsh-session - PS> powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/0.11.7/install.ps1 | iex" + PS> powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/0.11.8/install.ps1 | iex" ``` !!! tip diff --git a/docs/guides/integration/aws-lambda.md b/docs/guides/integration/aws-lambda.md index a09c5da2457e5..6e11ed060a5cd 100644 --- a/docs/guides/integration/aws-lambda.md +++ b/docs/guides/integration/aws-lambda.md @@ -92,7 +92,7 @@ the second stage, we'll copy this directory over to the final image, omitting th other unnecessary files. ```dockerfile title="Dockerfile" -FROM ghcr.io/astral-sh/uv:0.11.7 AS uv +FROM ghcr.io/astral-sh/uv:0.11.8 AS uv # First, bundle the dependencies into the task root. FROM public.ecr.aws/lambda/python:3.13 AS builder @@ -334,7 +334,7 @@ And confirm that opening http://127.0.0.1:8000/ in a web browser displays, "Hell Finally, we'll update the Dockerfile to include the local library in the deployment package: ```dockerfile title="Dockerfile" -FROM ghcr.io/astral-sh/uv:0.11.7 AS uv +FROM ghcr.io/astral-sh/uv:0.11.8 AS uv # First, bundle the dependencies into the task root. FROM public.ecr.aws/lambda/python:3.13 AS builder diff --git a/docs/guides/integration/docker.md b/docs/guides/integration/docker.md index 6469b96238ca3..99d859da7f0f7 100644 --- a/docs/guides/integration/docker.md +++ b/docs/guides/integration/docker.md @@ -31,7 +31,7 @@ $ docker run --rm -it ghcr.io/astral-sh/uv:debian uv --help The following distroless images are available: - `ghcr.io/astral-sh/uv:latest` -- `ghcr.io/astral-sh/uv:{major}.{minor}.{patch}`, e.g., `ghcr.io/astral-sh/uv:0.11.7` +- `ghcr.io/astral-sh/uv:{major}.{minor}.{patch}`, e.g., `ghcr.io/astral-sh/uv:0.11.8` - `ghcr.io/astral-sh/uv:{major}.{minor}`, e.g., `ghcr.io/astral-sh/uv:0.8` (the latest patch version) @@ -92,7 +92,7 @@ And the following derived images are available: As with the distroless image, each derived image is published with uv version tags as `ghcr.io/astral-sh/uv:{major}.{minor}.{patch}-{base}` and -`ghcr.io/astral-sh/uv:{major}.{minor}-{base}`, e.g., `ghcr.io/astral-sh/uv:0.11.7-alpine`. +`ghcr.io/astral-sh/uv:{major}.{minor}-{base}`, e.g., `ghcr.io/astral-sh/uv:0.11.8-alpine`. In addition, starting with `0.8` each derived image also sets `UV_TOOL_BIN_DIR` to `/usr/local/bin` to allow `uv tool install` to work as expected with the default user. @@ -133,7 +133,7 @@ Note this requires `curl` to be available. In either case, it is best practice to pin to a specific uv version, e.g., with: ```dockerfile -COPY --from=ghcr.io/astral-sh/uv:0.11.7 /uv /uvx /bin/ +COPY --from=ghcr.io/astral-sh/uv:0.11.8 /uv /uvx /bin/ ``` !!! tip @@ -151,7 +151,7 @@ COPY --from=ghcr.io/astral-sh/uv:0.11.7 /uv /uvx /bin/ Or, with the installer: ```dockerfile -ADD https://astral.sh/uv/0.11.7/install.sh /uv-installer.sh +ADD https://astral.sh/uv/0.11.8/install.sh /uv-installer.sh ``` ### Installing a project @@ -619,5 +619,5 @@ Verified OK !!! tip These examples use `latest`, but best practice is to verify the attestation for a specific - version tag, e.g., `ghcr.io/astral-sh/uv:0.11.7`, or (even better) the specific image digest, + version tag, e.g., `ghcr.io/astral-sh/uv:0.11.8`, or (even better) the specific image digest, such as `ghcr.io/astral-sh/uv:0.5.27@sha256:5adf09a5a526f380237408032a9308000d14d5947eafa687ad6c6a2476787b4f`. diff --git a/docs/guides/integration/github.md b/docs/guides/integration/github.md index 9266e49848b20..c60f52eb537ee 100644 --- a/docs/guides/integration/github.md +++ b/docs/guides/integration/github.md @@ -47,7 +47,7 @@ jobs: uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: # Install a specific version of uv. - version: "0.11.7" + version: "0.11.8" ``` ## Setting up Python diff --git a/docs/guides/integration/gitlab.md b/docs/guides/integration/gitlab.md index 3aa90f1d9800d..300fe713fd1b6 100644 --- a/docs/guides/integration/gitlab.md +++ b/docs/guides/integration/gitlab.md @@ -13,7 +13,7 @@ Select a variant that is suitable for your workflow. ```yaml title=".gitlab-ci.yml" variables: - UV_VERSION: "0.11.7" + UV_VERSION: "0.11.8" PYTHON_VERSION: "3.12" BASE_LAYER: trixie-slim # GitLab CI creates a separate mountpoint for the build directory, diff --git a/docs/guides/integration/pre-commit.md b/docs/guides/integration/pre-commit.md index f82af716d3cfd..b392045bc85a9 100644 --- a/docs/guides/integration/pre-commit.md +++ b/docs/guides/integration/pre-commit.md @@ -19,7 +19,7 @@ To make sure your `uv.lock` file is up to date even if your `pyproject.toml` fil repos: - repo: https://github.com/astral-sh/uv-pre-commit # uv version. - rev: 0.11.7 + rev: 0.11.8 hooks: - id: uv-lock ``` @@ -30,7 +30,7 @@ To keep a `requirements.txt` file in sync with your `uv.lock` file: repos: - repo: https://github.com/astral-sh/uv-pre-commit # uv version. - rev: 0.11.7 + rev: 0.11.8 hooks: - id: uv-export ``` @@ -41,7 +41,7 @@ To compile requirements files: repos: - repo: https://github.com/astral-sh/uv-pre-commit # uv version. - rev: 0.11.7 + rev: 0.11.8 hooks: # Compile requirements - id: pip-compile @@ -54,7 +54,7 @@ To compile alternative requirements files, modify `args` and `files`: repos: - repo: https://github.com/astral-sh/uv-pre-commit # uv version. - rev: 0.11.7 + rev: 0.11.8 hooks: # Compile requirements - id: pip-compile @@ -68,7 +68,7 @@ To run the hook over multiple files at the same time, add additional entries: repos: - repo: https://github.com/astral-sh/uv-pre-commit # uv version. - rev: 0.11.7 + rev: 0.11.8 hooks: # Compile requirements - id: pip-compile diff --git a/pyproject.toml b/pyproject.toml index be81272bf6fe2..24ea42bd54eee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "uv" -version = "0.11.7" +version = "0.11.8" description = "An extremely fast Python package and project manager, written in Rust." authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }] requires-python = ">=3.8" diff --git a/uv.lock b/uv.lock index 8283e179c190d..c1a48a4482a44 100644 --- a/uv.lock +++ b/uv.lock @@ -934,7 +934,7 @@ wheels = [ [[package]] name = "uv" -version = "0.11.7" +version = "0.11.8" source = { editable = "." } [package.dev-dependencies] diff --git a/uv.schema.json b/uv.schema.json index 3d96bae4f3842..3146d4c478626 100644 --- a/uv.schema.json +++ b/uv.schema.json @@ -834,7 +834,7 @@ } }, "DisplaySafeUrl": { - "description": "A [`Url`] wrapper that redacts credentials when displaying the URL.\n\n`DisplaySafeUrl` wraps the standard [`url::Url`] type, providing functionality to mask\nsecrets by default when the URL is displayed or logged. This helps prevent accidental\nexposure of sensitive information in logs and debug output.\n\n# Examples\n\n```\nuse uv_redacted::DisplaySafeUrl;\nuse std::str::FromStr;\n\n// Create a `DisplaySafeUrl` from a `&str`\nlet mut url = DisplaySafeUrl::parse(\"https://user:password@example.com\").unwrap();\n\n// Display will mask secrets\nassert_eq!(url.to_string(), \"https://user:****@example.com/\");\n\n// You can still access the username and password\nassert_eq!(url.username(), \"user\");\nassert_eq!(url.password(), Some(\"password\"));\n\n// And you can still update the username and password\nlet _ = url.set_username(\"new_user\");\nlet _ = url.set_password(Some(\"new_password\"));\nassert_eq!(url.username(), \"new_user\");\nassert_eq!(url.password(), Some(\"new_password\"));\n\n// It is also possible to remove the credentials entirely\nurl.remove_credentials();\nassert_eq!(url.username(), \"\");\nassert_eq!(url.password(), None);\n```", + "description": "A [`Url`] wrapper that redacts credentials and sensitive query parameters when displaying the URL.\n\n`DisplaySafeUrl` wraps the standard [`url::Url`] type, providing functionality to mask\nsecrets by default when the URL is displayed or logged. This helps prevent accidental\nexposure of sensitive information in logs and debug output.\n\n# Examples\n\n```\nuse uv_redacted::DisplaySafeUrl;\nuse std::str::FromStr;\n\n// Create a `DisplaySafeUrl` from a `&str`\nlet mut url = DisplaySafeUrl::parse(\"https://user:password@example.com\").unwrap();\n\n// Display will mask secrets\nassert_eq!(url.to_string(), \"https://user:****@example.com/\");\n\n// You can still access the username and password\nassert_eq!(url.username(), \"user\");\nassert_eq!(url.password(), Some(\"password\"));\n\n// And you can still update the username and password\nlet _ = url.set_username(\"new_user\");\nlet _ = url.set_password(Some(\"new_password\"));\nassert_eq!(url.username(), \"new_user\");\nassert_eq!(url.password(), Some(\"new_password\"));\n\n// It is also possible to remove the credentials entirely\nurl.remove_credentials();\nassert_eq!(url.username(), \"\");\nassert_eq!(url.password(), None);\n```", "type": "string", "format": "uri" },