From 423cfaabf5322f2797ca5b0239de56de7f36a7a2 Mon Sep 17 00:00:00 2001 From: konsti Date: Mon, 16 Jun 2025 19:10:21 +0200 Subject: [PATCH 001/349] Show backtraces for CI crashes (#14081) I only just realized that we can get backtraces for crashes with `RUST_BACKTRACE: 1`, even non-panics (https://github.com/astral-sh/uv/pull/14079). This is much better than trying to analyze crash dumps. --- .github/workflows/ci.yml | 55 ++-------------------------------------- 1 file changed, 2 insertions(+), 53 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 09d27b6d2..8a1f78c9e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,8 +14,9 @@ env: CARGO_INCREMENTAL: 0 CARGO_NET_RETRY: 10 CARGO_TERM_COLOR: always - RUSTUP_MAX_RETRIES: 10 PYTHON_VERSION: "3.12" + RUSTUP_MAX_RETRIES: 10 + RUST_BACKTRACE: 1 jobs: determine_changes: @@ -295,23 +296,7 @@ jobs: with: tool: cargo-nextest - # Get crash dumps to debug the `exit_code: -1073741819` failures - - name: Configure crash dumps - if: runner.os == 'Windows' - shell: powershell - run: | - $dumps = "$env:GITHUB_WORKSPACE\dumps" - New-Item -Path $dumps -ItemType Directory -Force - - # https://github.com/microsoft/terminal/wiki/Troubleshooting-Tips#capture-automatically - $reg = "HKLM:\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps" - New-Item -Path $reg -Force | Out-Null - Set-ItemProperty -Path $reg -Name "DumpFolder" -Value $dumps - Set-ItemProperty -Path $reg -Name "DumpType" -Value 2 - - name: "Cargo test" - id: test - continue-on-error: true working-directory: ${{ env.UV_WORKSPACE }} env: # Avoid permission errors during concurrent tests @@ -325,42 +310,6 @@ jobs: --workspace \ --status-level skip --failure-output immediate-final --no-fail-fast -j 20 --final-status-level slow - # Get crash dumps to debug the `exit_code: -1073741819` failures (contd.) - - name: Analyze crashes - if: steps.test.outcome == 'failure' - shell: powershell - run: | - $dumps = Get-ChildItem "$env:GITHUB_WORKSPACE\dumps\*.dmp" -ErrorAction SilentlyContinue - if (!$dumps) { exit 0 } - - Write-Host "Found $($dumps.Count) crash dump(s)" - - # Download cdb if needed - $cdb = "C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe" - if (!(Test-Path $cdb)) { - # https://github.com/microsoft/react-native-windows/blob/f1570a5ef1c4fc1e78d0a0ad5af848ab91a4061c/vnext/Scripts/Analyze-Crash.ps1#L44-L56 - Invoke-WebRequest "https://go.microsoft.com/fwlink/?linkid=2173743" -OutFile "$env:TEMP\sdk.exe" - Start-Process "$env:TEMP\sdk.exe" -ArgumentList "/features OptionId.WindowsDesktopDebuggers /quiet" -Wait - } - - # Analyze each dump - foreach ($dump in $dumps) { - Write-Host "`n=== $($dump.Name) ===" - & $cdb -z $dump -c "!analyze -v; .ecxr; k; q" 2>&1 | Select-String -Pattern "(ExceptionCode:|SYMBOL_NAME:|IMAGE_NAME:|STACK_TEXT:)" -Context 0,2 - } - - - name: Upload crash dumps - if: steps.test.outcome == 'failure' - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: crash-dumps-${{ github.run_number }} - path: dumps/*.dmp - if-no-files-found: ignore - - - name: Fail if tests failed - if: steps.test.outcome == 'failure' - run: exit 1 - # Separate jobs for the nightly crate windows-trampoline-check: timeout-minutes: 15 From d73d3e8b536065e63a534fa1126d96e792003b05 Mon Sep 17 00:00:00 2001 From: Kyle Galbraith Date: Mon, 16 Jun 2025 20:32:35 +0200 Subject: [PATCH 002/349] Refactor Docker image builds to use Depot (#13459) ## Summary Simplify the Docker image build process to leverage Depot container builders for faster layer caching and native multi-platform image builds. The combo of the two removes the need to save cache to registries and do complex merge operations across GHA runners to get a multi-platform image. ## Test Plan UV team will need to add a trust relationship in Depot so that the container builds can authenticate and run. This can be done following these docs: https://depot.dev/docs/cli/authentication#adding-a-trust-relationship-for-github-actions. Once that is done, this should just work as before, but without all of the extra work around manifests. We should double that all of the tagging still makes sense for you all, as some bits of that were unclear. Additional context in this draft PR: https://github.com/astral-sh/uv/pull/9156 --------- Co-authored-by: Zanie Blue --- .github/workflows/build-docker.yml | 234 +++++++---------------------- 1 file changed, 57 insertions(+), 177 deletions(-) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 1488774b0..ce12d3c5d 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -1,11 +1,19 @@ -# Build and publish a Docker image. +# Build and publish Docker images. # -# Assumed to run as a subworkflow of .github/workflows/release.yml; specifically, as a local -# artifacts job within `cargo-dist`. +# Uses Depot for multi-platform builds. Includes both a `uv` base image, which +# is just the binary in a scratch image, and a set of extra, common images with +# the uv binary installed. # -# TODO(charlie): Ideally, the publish step would happen as a publish job within `cargo-dist`, but -# sharing the built image as an artifact between jobs is challenging. -name: "Build Docker image" +# Images are built on all runs. +# +# On release, assumed to run as a subworkflow of .github/workflows/release.yml; +# specifically, as a local artifacts job within `cargo-dist`. In this case, +# images are published based on the `plan`. +# +# TODO(charlie): Ideally, the publish step would happen as a publish job within +# `cargo-dist`, but sharing the built image as an artifact between jobs is +# challenging. +name: "Docker images" on: workflow_call: @@ -32,18 +40,19 @@ env: UV_BASE_IMG: ghcr.io/${{ github.repository_owner }}/uv jobs: - docker-build: + docker-publish-base: if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }} - name: Build Docker image (ghcr.io/astral-sh/uv) for ${{ matrix.platform }} + name: uv runs-on: ubuntu-latest + permissions: + contents: read + id-token: write # for Depot OIDC + packages: write # for GHCR environment: name: release - strategy: - fail-fast: false - matrix: - platform: - - linux/amd64 - - linux/arm64 + outputs: + image-tags: ${{ steps.meta.outputs.tags }} + image-digest: ${{ steps.build.outputs.digest }} steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: @@ -57,14 +66,14 @@ jobs: username: astralshbot password: ${{ secrets.DOCKERHUB_TOKEN_RO }} - - uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 - - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} + - uses: depot/setup-action@b0b1ea4f69e92ebf5dea3f8713a1b0c37b2126a5 + - name: Check tag consistency if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }} run: | @@ -87,96 +96,33 @@ jobs: tags: | type=raw,value=dry-run,enable=${{ inputs.plan == '' || fromJson(inputs.plan).announcement_tag_is_implicit }} type=pep440,pattern={{ version }},value=${{ inputs.plan != '' && fromJson(inputs.plan).announcement_tag || 'dry-run' }},enable=${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }} + type=pep440,pattern={{ major }}.{{ minor }},value=${{ inputs.plan != '' && fromJson(inputs.plan).announcement_tag || 'dry-run' }},enable=${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }} - - name: Normalize Platform Pair (replace / with -) - run: | - platform=${{ matrix.platform }} - echo "PLATFORM_TUPLE=${platform//\//-}" >> $GITHUB_ENV - - # Adapted from https://docs.docker.com/build/ci/github-actions/multi-platform/ - name: Build and push by digest id: build - uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 + uses: depot/build-push-action@636daae76684e38c301daa0c5eca1c095b24e780 # v1.14.0 with: + project: 7hd4vdzmw5 # astral-sh/uv context: . - platforms: ${{ matrix.platform }} - cache-from: type=gha,scope=uv-${{ env.PLATFORM_TUPLE }} - cache-to: type=gha,mode=min,scope=uv-${{ env.PLATFORM_TUPLE }} + platforms: linux/amd64,linux/arm64 + push: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }} + tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - outputs: type=image,name=${{ env.UV_BASE_IMG }},push-by-digest=true,name-canonical=true,push=${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }} - - name: Export digests - run: | - mkdir -p /tmp/digests - digest="${{ steps.build.outputs.digest }}" - touch "/tmp/digests/${digest#sha256:}" - - - name: Upload digests - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + - name: Generate artifact attestation for base image + if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }} + uses: actions/attest-build-provenance@c074443f1aee8d4aeeae555aebba3282517141b2 # v2.2.3 with: - name: digests-${{ env.PLATFORM_TUPLE }} - path: /tmp/digests/* - if-no-files-found: error - retention-days: 1 - - docker-publish: - name: Publish Docker image (ghcr.io/astral-sh/uv) - runs-on: ubuntu-latest - environment: - name: release - needs: - - docker-build - if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }} - steps: - # Login to DockerHub first, to avoid rate-limiting - - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 - with: - username: astralshbot - password: ${{ secrets.DOCKERHUB_TOKEN_RO }} - - - name: Download digests - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 - with: - path: /tmp/digests - pattern: digests-* - merge-multiple: true - - - uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0 - with: - images: ${{ env.UV_BASE_IMG }} - # Order is on purpose such that the label org.opencontainers.image.version has the first pattern with the full version - tags: | - type=pep440,pattern={{ version }},value=${{ fromJson(inputs.plan).announcement_tag }} - type=pep440,pattern={{ major }}.{{ minor }},value=${{ fromJson(inputs.plan).announcement_tag }} - - - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - # Adapted from https://docs.docker.com/build/ci/github-actions/multi-platform/ - - name: Create manifest list and push - working-directory: /tmp/digests - # The jq command expands the docker/metadata json "tags" array entry to `-t tag1 -t tag2 ...` for each tag in the array - # The printf will expand the base image with the `@sha256: ...` for each sha256 in the directory - # The final command becomes `docker buildx imagetools create -t tag1 -t tag2 ... @sha256: @sha256: ...` - run: | - docker buildx imagetools create \ - $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ - $(printf '${{ env.UV_BASE_IMG }}@sha256:%s ' *) + subject-name: ${{ env.UV_BASE_IMG }} + subject-digest: ${{ steps.build.outputs.digest }} docker-publish-extra: - name: Publish additional Docker image based on ${{ matrix.image-mapping }} + name: ${{ matrix.image-mapping }} runs-on: ubuntu-latest environment: name: release needs: - - docker-publish + - docker-publish-base if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }} permissions: packages: write @@ -215,18 +161,19 @@ jobs: steps: # Login to DockerHub first, to avoid rate-limiting - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 + if: ${{ github.event.pull_request.head.repo.full_name == 'astral-sh/uv' }} with: username: astralshbot password: ${{ secrets.DOCKERHUB_TOKEN_RO }} - - uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 - - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} + - uses: depot/setup-action@b0b1ea4f69e92ebf5dea3f8713a1b0c37b2126a5 + - name: Generate Dynamic Dockerfile Tags shell: bash run: | @@ -257,9 +204,6 @@ jobs: # Remove the trailing newline from the pattern list TAG_PATTERNS="${TAG_PATTERNS%\\n}" - # Export image cache name - echo "IMAGE_REF=${BASE_IMAGE//:/-}" >> $GITHUB_ENV - # Export tag patterns using the multiline env var syntax { echo "TAG_PATTERNS<@sha256: ...` for each sha256 in the directory - # The final command becomes `docker buildx imagetools create -t tag1 -t tag2 ... @sha256: @sha256: ...` + - name: Push tags + env: + IMAGE: ${{ env.UV_BASE_IMG }} + DIGEST: ${{ needs.docker-publish-base.outputs.image-digest }} + TAGS: ${{ needs.docker-publish-base.outputs.image-tags }} run: | - readarray -t lines <<< "$DOCKER_METADATA_OUTPUT_ANNOTATIONS"; annotations=(); for line in "${lines[@]}"; do annotations+=(--annotation "$line"); done - docker buildx imagetools create \ - "${annotations[@]}" \ - $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ - $(printf '${{ env.UV_BASE_IMG }}@sha256:%s ' *) - - - name: Share manifest digest - id: manifest-digest - # To sign the manifest, we need it's digest. Unfortunately "docker - # buildx imagetools create" does not (yet) have a clean way of sharing - # the digest of the manifest it creates (see docker/buildx#2407), so - # we use a separate command to retrieve it. - # imagetools inspect [TAG] --format '{{json .Manifest}}' gives us - # the machine readable JSON description of the manifest, and the - # jq command extracts the digest from this. The digest is then - # sent to the Github step output file for sharing with other steps. - run: | - digest="$( - docker buildx imagetools inspect \ - "${UV_BASE_IMG}:${DOCKER_METADATA_OUTPUT_VERSION}" \ - --format '{{json .Manifest}}' \ - | jq -r '.digest' - )" - echo "digest=${digest}" >> "$GITHUB_OUTPUT" - - - name: Generate artifact attestation - uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0 - with: - subject-name: ${{ env.UV_BASE_IMG }} - subject-digest: ${{ steps.manifest-digest.outputs.digest }} - # push-to-registry is explicitly not enabled to maintain full control over the top image + docker pull "${IMAGE}@${DIGEST}" + for tag in $TAGS; do + docker tag "${IMAGE}@${DIGEST}" "${tag}" + docker push "${tag}" + done From cf67d9c633c1545ae5f46a1020182f4cef62ae76 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Mon, 16 Jun 2025 15:01:17 -0500 Subject: [PATCH 003/349] Clear `XDG_DATA_HOME` during test runs (#14075) --- crates/uv/tests/it/common/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/uv/tests/it/common/mod.rs b/crates/uv/tests/it/common/mod.rs index 23845a8a8..66eb21729 100644 --- a/crates/uv/tests/it/common/mod.rs +++ b/crates/uv/tests/it/common/mod.rs @@ -747,6 +747,7 @@ impl TestContext { .env_remove(EnvVars::UV_CACHE_DIR) .env_remove(EnvVars::UV_TOOL_BIN_DIR) .env_remove(EnvVars::XDG_CONFIG_HOME) + .env_remove(EnvVars::XDG_DATA_HOME) .current_dir(self.temp_dir.path()); for (key, value) in &self.extra_env { From e02cd74e64bd918dc8adea96e15a96a1c3c08d08 Mon Sep 17 00:00:00 2001 From: John Mumm Date: Tue, 17 Jun 2025 06:18:54 -0400 Subject: [PATCH 004/349] Turn off `clippy::struct_excessive_bools` rule (#14102) We always ignore the `clippy::struct_excessive_bools` rule and formerly annotated this at the function level. This PR specifies the allow in `workspace.lints.clippy` in `Cargo.toml`. --- Cargo.toml | 1 + crates/uv-cli/src/compat.rs | 6 --- crates/uv-cli/src/lib.rs | 48 -------------------- crates/uv-client/src/httpcache/control.rs | 1 - crates/uv-python/src/virtualenv.rs | 1 - crates/uv-resolver/src/resolution/display.rs | 1 - crates/uv-resolver/src/version_map.rs | 1 - crates/uv/src/settings.rs | 32 ------------- 8 files changed, 1 insertion(+), 90 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 479001ac8..c6d5729e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -214,6 +214,7 @@ missing_panics_doc = "allow" module_name_repetitions = "allow" must_use_candidate = "allow" similar_names = "allow" +struct_excessive_bools = "allow" too_many_arguments = "allow" too_many_lines = "allow" used_underscore_binding = "allow" diff --git a/crates/uv-cli/src/compat.rs b/crates/uv-cli/src/compat.rs index 50f4c173d..d29afa760 100644 --- a/crates/uv-cli/src/compat.rs +++ b/crates/uv-cli/src/compat.rs @@ -13,7 +13,6 @@ pub trait CompatArgs { /// For example, users often pass `--allow-unsafe`, which is unnecessary with uv. But it's a /// nice user experience to warn, rather than fail, when users pass `--allow-unsafe`. #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct PipCompileCompatArgs { #[clap(long, hide = true)] allow_unsafe: bool, @@ -159,7 +158,6 @@ impl CompatArgs for PipCompileCompatArgs { /// /// These represent a subset of the `pip list` interface that uv supports by default. #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct PipListCompatArgs { #[clap(long, hide = true)] disable_pip_version_check: bool, @@ -184,7 +182,6 @@ impl CompatArgs for PipListCompatArgs { /// /// These represent a subset of the `pip-sync` interface that uv supports by default. #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct PipSyncCompatArgs { #[clap(short, long, hide = true)] ask: bool, @@ -268,7 +265,6 @@ enum Resolver { /// /// These represent a subset of the `virtualenv` interface that uv supports by default. #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct VenvCompatArgs { #[clap(long, hide = true)] clear: bool, @@ -327,7 +323,6 @@ impl CompatArgs for VenvCompatArgs { /// /// These represent a subset of the `pip install` interface that uv supports by default. #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct PipInstallCompatArgs { #[clap(long, hide = true)] disable_pip_version_check: bool, @@ -361,7 +356,6 @@ impl CompatArgs for PipInstallCompatArgs { /// /// These represent a subset of the `pip` interface that exists on all commands. #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct PipGlobalCompatArgs { #[clap(long, hide = true)] disable_pip_version_check: bool, diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 0b96875e5..bd06f9a82 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -85,7 +85,6 @@ const STYLES: Styles = Styles::styled() disable_version_flag = true )] #[command(styles=STYLES)] -#[allow(clippy::struct_excessive_bools)] pub struct Cli { #[command(subcommand)] pub command: Box, @@ -133,7 +132,6 @@ pub struct TopLevelArgs { #[derive(Parser, Debug, Clone)] #[command(next_help_heading = "Global options", next_display_order = 1000)] -#[allow(clippy::struct_excessive_bools)] pub struct GlobalArgs { #[arg( global = true, @@ -526,7 +524,6 @@ pub struct HelpArgs { #[derive(Args)] #[command(group = clap::ArgGroup::new("operation"))] -#[allow(clippy::struct_excessive_bools)] pub struct VersionArgs { /// Set the project version to this value /// @@ -657,7 +654,6 @@ pub struct SelfUpdateArgs { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct CacheNamespace { #[command(subcommand)] pub command: CacheCommand, @@ -687,14 +683,12 @@ pub enum CacheCommand { } #[derive(Args, Debug)] -#[allow(clippy::struct_excessive_bools)] pub struct CleanArgs { /// The packages to remove from the cache. pub package: Vec, } #[derive(Args, Debug)] -#[allow(clippy::struct_excessive_bools)] pub struct PruneArgs { /// Optimize the cache for persistence in a continuous integration environment, like GitHub /// Actions. @@ -714,7 +708,6 @@ pub struct PruneArgs { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct PipNamespace { #[command(subcommand)] pub command: PipCommand, @@ -1095,7 +1088,6 @@ fn parse_maybe_string(input: &str) -> Result, String> { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] #[command(group = clap::ArgGroup::new("sources").required(true).multiple(true))] pub struct PipCompileArgs { /// Include all packages listed in the given `requirements.in` files. @@ -1443,7 +1435,6 @@ pub struct PipCompileArgs { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct PipSyncArgs { /// Include all packages listed in the given `requirements.txt` files. /// @@ -1700,7 +1691,6 @@ pub struct PipSyncArgs { #[derive(Args)] #[command(group = clap::ArgGroup::new("sources").required(true).multiple(true))] -#[allow(clippy::struct_excessive_bools)] pub struct PipInstallArgs { /// Install all listed packages. /// @@ -2015,7 +2005,6 @@ pub struct PipInstallArgs { #[derive(Args)] #[command(group = clap::ArgGroup::new("sources").required(true).multiple(true))] -#[allow(clippy::struct_excessive_bools)] pub struct PipUninstallArgs { /// Uninstall all listed packages. #[arg(group = "sources")] @@ -2104,7 +2093,6 @@ pub struct PipUninstallArgs { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct PipFreezeArgs { /// Exclude any editable packages from output. #[arg(long)] @@ -2159,7 +2147,6 @@ pub struct PipFreezeArgs { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct PipListArgs { /// Only include editable projects. #[arg(short, long)] @@ -2235,7 +2222,6 @@ pub struct PipListArgs { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct PipCheckArgs { /// The Python interpreter for which packages should be checked. /// @@ -2271,7 +2257,6 @@ pub struct PipCheckArgs { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct PipShowArgs { /// The package(s) to display. pub package: Vec, @@ -2325,7 +2310,6 @@ pub struct PipShowArgs { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct PipTreeArgs { /// Show the version constraint(s) imposed on each package. #[arg(long)] @@ -2382,7 +2366,6 @@ pub struct PipTreeArgs { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct BuildArgs { /// The directory from which distributions should be built, or a source /// distribution archive to build into a wheel. @@ -2529,7 +2512,6 @@ pub struct BuildArgs { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct VenvArgs { /// The Python interpreter to use for the virtual environment. /// @@ -2725,7 +2707,6 @@ pub enum AuthorFrom { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct InitArgs { /// The path to use for the project/script. /// @@ -2883,7 +2864,6 @@ pub struct InitArgs { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct RunArgs { /// Include optional dependencies from the specified extra name. /// @@ -3170,7 +3150,6 @@ pub struct RunArgs { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct SyncArgs { /// Include optional dependencies from the specified extra name. /// @@ -3427,7 +3406,6 @@ pub struct SyncArgs { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct LockArgs { /// Check if the lockfile is up-to-date. /// @@ -3489,7 +3467,6 @@ pub struct LockArgs { #[derive(Args)] #[command(group = clap::ArgGroup::new("sources").required(true).multiple(true))] -#[allow(clippy::struct_excessive_bools)] pub struct AddArgs { /// The packages to add, as PEP 508 requirements (e.g., `ruff==0.5.0`). #[arg(group = "sources")] @@ -3674,7 +3651,6 @@ pub struct AddArgs { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct RemoveArgs { /// The names of the dependencies to remove (e.g., `ruff`). #[arg(required = true)] @@ -3769,7 +3745,6 @@ pub struct RemoveArgs { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct TreeArgs { /// Show a platform-independent dependency tree. /// @@ -3909,7 +3884,6 @@ pub struct TreeArgs { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct ExportArgs { /// The format to which `uv.lock` should be exported. /// @@ -4124,7 +4098,6 @@ pub struct ExportArgs { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct ToolNamespace { #[command(subcommand)] pub command: ToolCommand, @@ -4217,7 +4190,6 @@ pub enum ToolCommand { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct ToolRunArgs { /// The command to run. /// @@ -4336,7 +4308,6 @@ pub struct UvxArgs { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct ToolInstallArgs { /// The package to install commands from. pub package: String, @@ -4425,7 +4396,6 @@ pub struct ToolInstallArgs { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct ToolListArgs { /// Whether to display the path to each tool environment and installed executable. #[arg(long)] @@ -4452,7 +4422,6 @@ pub struct ToolListArgs { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct ToolDirArgs { /// Show the directory into which `uv tool` will install executables. /// @@ -4471,7 +4440,6 @@ pub struct ToolDirArgs { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct ToolUninstallArgs { /// The name of the tool to uninstall. #[arg(required = true)] @@ -4483,7 +4451,6 @@ pub struct ToolUninstallArgs { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct ToolUpgradeArgs { /// The name of the tool to upgrade, along with an optional version specifier. #[arg(required = true)] @@ -4713,7 +4680,6 @@ pub struct ToolUpgradeArgs { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct PythonNamespace { #[command(subcommand)] pub command: PythonCommand, @@ -4793,7 +4759,6 @@ pub enum PythonCommand { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct PythonListArgs { /// A Python request to filter by. /// @@ -4848,7 +4813,6 @@ pub struct PythonListArgs { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct PythonDirArgs { /// Show the directory into which `uv python` will install Python executables. /// @@ -4866,7 +4830,6 @@ pub struct PythonDirArgs { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct PythonInstallArgs { /// The directory to store the Python installation in. /// @@ -4945,7 +4908,6 @@ pub struct PythonInstallArgs { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct PythonUninstallArgs { /// The directory where the Python was installed. #[arg(long, short, env = EnvVars::UV_PYTHON_INSTALL_DIR)] @@ -4963,7 +4925,6 @@ pub struct PythonUninstallArgs { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct PythonFindArgs { /// The Python request. /// @@ -5012,7 +4973,6 @@ pub struct PythonFindArgs { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct PythonPinArgs { /// The Python version request. /// @@ -5061,7 +5021,6 @@ pub struct PythonPinArgs { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct GenerateShellCompletionArgs { /// The shell to generate the completion script for pub shell: clap_complete_command::Shell, @@ -5100,7 +5059,6 @@ pub struct GenerateShellCompletionArgs { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct IndexArgs { /// The URLs to use when resolving dependencies, in addition to the default index. /// @@ -5175,7 +5133,6 @@ pub struct IndexArgs { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct RefreshArgs { /// Refresh all cached data. #[arg( @@ -5201,7 +5158,6 @@ pub struct RefreshArgs { } #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct BuildOptionsArgs { /// Don't build source distributions. /// @@ -5257,7 +5213,6 @@ pub struct BuildOptionsArgs { /// Arguments that are used by commands that need to install (but not resolve) packages. #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct InstallerArgs { #[command(flatten)] pub index_args: IndexArgs, @@ -5399,7 +5354,6 @@ pub struct InstallerArgs { /// Arguments that are used by commands that need to resolve (but not install) packages. #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct ResolverArgs { #[command(flatten)] pub index_args: IndexArgs, @@ -5566,7 +5520,6 @@ pub struct ResolverArgs { /// Arguments that are used by commands that need to resolve and install packages. #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct ResolverInstallerArgs { #[command(flatten)] pub index_args: IndexArgs, @@ -5783,7 +5736,6 @@ pub struct ResolverInstallerArgs { /// Arguments that are used by commands that need to fetch from the Simple API. #[derive(Args)] -#[allow(clippy::struct_excessive_bools)] pub struct FetchArgs { #[command(flatten)] pub index_args: IndexArgs, diff --git a/crates/uv-client/src/httpcache/control.rs b/crates/uv-client/src/httpcache/control.rs index 724683188..ddac9d1bc 100644 --- a/crates/uv-client/src/httpcache/control.rs +++ b/crates/uv-client/src/httpcache/control.rs @@ -21,7 +21,6 @@ use crate::rkyvutil::OwnedArchive; rkyv::Serialize, )] #[rkyv(derive(Debug))] -#[allow(clippy::struct_excessive_bools)] pub struct CacheControl { // directives for requests and responses /// * diff --git a/crates/uv-python/src/virtualenv.rs b/crates/uv-python/src/virtualenv.rs index 7d72188fc..ea578fff3 100644 --- a/crates/uv-python/src/virtualenv.rs +++ b/crates/uv-python/src/virtualenv.rs @@ -32,7 +32,6 @@ pub struct VirtualEnvironment { /// A parsed `pyvenv.cfg` #[derive(Debug, Clone)] -#[allow(clippy::struct_excessive_bools)] pub struct PyVenvConfiguration { /// Was the virtual environment created with the `virtualenv` package? pub(crate) virtualenv: bool, diff --git a/crates/uv-resolver/src/resolution/display.rs b/crates/uv-resolver/src/resolution/display.rs index 2f70f00f6..318fb4e54 100644 --- a/crates/uv-resolver/src/resolution/display.rs +++ b/crates/uv-resolver/src/resolution/display.rs @@ -14,7 +14,6 @@ use crate::{ResolverEnvironment, ResolverOutput}; /// A [`std::fmt::Display`] implementation for the resolution graph. #[derive(Debug)] -#[allow(clippy::struct_excessive_bools)] pub struct DisplayResolutionGraph<'a> { /// The underlying graph. resolution: &'a ResolverOutput, diff --git a/crates/uv-resolver/src/version_map.rs b/crates/uv-resolver/src/version_map.rs index 8a0b17fc4..63132ad0d 100644 --- a/crates/uv-resolver/src/version_map.rs +++ b/crates/uv-resolver/src/version_map.rs @@ -345,7 +345,6 @@ struct VersionMapEager { /// avoiding another conversion step into a fully filled out `VersionMap` can /// provide substantial savings in some cases. #[derive(Debug)] -#[allow(clippy::struct_excessive_bools)] struct VersionMapLazy { /// A map from version to possibly-initialized distribution. map: BTreeMap, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index f8d44b50c..fb1a62b41 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -54,7 +54,6 @@ use crate::commands::{InitKind, InitProjectKind, pip::operations::Modifications} const PYPI_PUBLISH_URL: &str = "https://upload.pypi.org/legacy/"; /// The resolved global settings to use for any invocation of the CLI. -#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct GlobalSettings { pub(crate) required_version: Option, @@ -199,7 +198,6 @@ impl NetworkSettings { } /// The resolved cache settings to use for any invocation of the CLI. -#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct CacheSettings { pub(crate) no_cache: bool, @@ -222,7 +220,6 @@ impl CacheSettings { } /// The resolved settings to use for a `init` invocation. -#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct InitSettings { pub(crate) path: Option, @@ -307,7 +304,6 @@ impl InitSettings { } /// The resolved settings to use for a `run` invocation. -#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct RunSettings { pub(crate) locked: bool, @@ -454,7 +450,6 @@ impl RunSettings { } /// The resolved settings to use for a `tool run` invocation. -#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct ToolRunSettings { pub(crate) command: Option, @@ -586,7 +581,6 @@ impl ToolRunSettings { } /// The resolved settings to use for a `tool install` invocation. -#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct ToolInstallSettings { pub(crate) package: String, @@ -681,7 +675,6 @@ impl ToolInstallSettings { } /// The resolved settings to use for a `tool upgrade` invocation. -#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct ToolUpgradeSettings { pub(crate) names: Vec, @@ -776,7 +769,6 @@ impl ToolUpgradeSettings { } /// The resolved settings to use for a `tool list` invocation. -#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct ToolListSettings { pub(crate) show_paths: bool, @@ -808,7 +800,6 @@ impl ToolListSettings { } /// The resolved settings to use for a `tool uninstall` invocation. -#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct ToolUninstallSettings { pub(crate) name: Vec, @@ -827,7 +818,6 @@ impl ToolUninstallSettings { } /// The resolved settings to use for a `tool dir` invocation. -#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct ToolDirSettings { pub(crate) bin: bool, @@ -854,7 +844,6 @@ pub(crate) enum PythonListKinds { } /// The resolved settings to use for a `tool run` invocation. -#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct PythonListSettings { pub(crate) request: Option, @@ -914,7 +903,6 @@ impl PythonListSettings { } /// The resolved settings to use for a `python dir` invocation. -#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct PythonDirSettings { pub(crate) bin: bool, @@ -931,7 +919,6 @@ impl PythonDirSettings { } /// The resolved settings to use for a `python install` invocation. -#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct PythonInstallSettings { pub(crate) install_dir: Option, @@ -987,7 +974,6 @@ impl PythonInstallSettings { } /// The resolved settings to use for a `python uninstall` invocation. -#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct PythonUninstallSettings { pub(crate) install_dir: Option, @@ -1017,7 +1003,6 @@ impl PythonUninstallSettings { } /// The resolved settings to use for a `python find` invocation. -#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct PythonFindSettings { pub(crate) request: Option, @@ -1049,7 +1034,6 @@ impl PythonFindSettings { } /// The resolved settings to use for a `python pin` invocation. -#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct PythonPinSettings { pub(crate) request: Option, @@ -1575,7 +1559,6 @@ impl VersionSettings { } /// The resolved settings to use for a `tree` invocation. -#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct TreeSettings { pub(crate) groups: DependencyGroups, @@ -1771,7 +1754,6 @@ impl ExportSettings { } /// The resolved settings to use for a `pip compile` invocation. -#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct PipCompileSettings { pub(crate) format: Option, @@ -1949,7 +1931,6 @@ impl PipCompileSettings { } /// The resolved settings to use for a `pip sync` invocation. -#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct PipSyncSettings { pub(crate) src_file: Vec, @@ -2036,7 +2017,6 @@ impl PipSyncSettings { } /// The resolved settings to use for a `pip install` invocation. -#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct PipInstallSettings { pub(crate) package: Vec, @@ -2195,7 +2175,6 @@ impl PipInstallSettings { } /// The resolved settings to use for a `pip uninstall` invocation. -#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct PipUninstallSettings { pub(crate) package: Vec, @@ -2243,7 +2222,6 @@ impl PipUninstallSettings { } /// The resolved settings to use for a `pip freeze` invocation. -#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct PipFreezeSettings { pub(crate) exclude_editable: bool, @@ -2282,7 +2260,6 @@ impl PipFreezeSettings { } /// The resolved settings to use for a `pip list` invocation. -#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct PipListSettings { pub(crate) editable: Option, @@ -2330,7 +2307,6 @@ impl PipListSettings { } /// The resolved settings to use for a `pip show` invocation. -#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct PipShowSettings { pub(crate) package: Vec, @@ -2369,7 +2345,6 @@ impl PipShowSettings { } /// The resolved settings to use for a `pip tree` invocation. -#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct PipTreeSettings { pub(crate) show_version_specifiers: bool, @@ -2419,7 +2394,6 @@ impl PipTreeSettings { } /// The resolved settings to use for a `pip check` invocation. -#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct PipCheckSettings { pub(crate) settings: PipSettings, @@ -2448,7 +2422,6 @@ impl PipCheckSettings { } /// The resolved settings to use for a `build` invocation. -#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct BuildSettings { pub(crate) src: Option, @@ -2525,7 +2498,6 @@ impl BuildSettings { } /// The resolved settings to use for a `venv` invocation. -#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct VenvSettings { pub(crate) seed: bool, @@ -2612,7 +2584,6 @@ pub(crate) struct InstallerSettingsRef<'a> { /// /// Combines the `[tool.uv]` persistent configuration with the command-line arguments /// ([`ResolverArgs`], represented as [`ResolverOptions`]). -#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone, Default)] pub(crate) struct ResolverSettings { pub(crate) build_options: BuildOptions, @@ -2702,7 +2673,6 @@ impl From for ResolverSettings { /// /// Represents the shared settings that are used across all uv commands outside the `pip` API. /// Analogous to the settings contained in the `[tool.uv]` table, combined with [`ResolverInstallerArgs`]. -#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone, Default)] pub(crate) struct ResolverInstallerSettings { pub(crate) resolver: ResolverSettings, @@ -2792,7 +2762,6 @@ impl From for ResolverInstallerSettings { /// /// Represents the shared settings that are used across all `pip` commands. Analogous to the /// settings contained in the `[tool.uv.pip]` table. -#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct PipSettings { pub(crate) index_locations: IndexLocations, @@ -3169,7 +3138,6 @@ impl<'a> From<&'a ResolverInstallerSettings> for InstallerSettingsRef<'a> { } /// The resolved settings to use for an invocation of the `uv publish` CLI. -#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct PublishSettings { // CLI only, see [`PublishArgs`] for docs. From d653fbb1331cfd0bb8f937402c93f95e2e331f82 Mon Sep 17 00:00:00 2001 From: FishAlchemist <48265002+FishAlchemist@users.noreply.github.com> Date: Tue, 17 Jun 2025 21:55:03 +0800 Subject: [PATCH 005/349] doc: Sync PyTorch integration index for CUDA and ROCm versions from PyTorch website. (#14100) ## Summary Just to sync the documentation with PyTorch's officially recommended installation method. ![image](https://github.com/user-attachments/assets/7be0aea6-b51b-4083-acae-fbe8aa79c363) **Change:** * CUDA 12.1 to CUDA 12.6 * CUDA 12.4 to CUDA 12.8 * ROCm 6.2 to ROCm 6.3 ## Test Plan Run doc server in local. Result:
CUDA 12.6

![image](https://github.com/user-attachments/assets/962a1058-3922-4709-a57f-200939d0a397) ![image](https://github.com/user-attachments/assets/0dae693f-2dc1-4d3e-9186-d26aa84c7d73)

CUDA 12.8

![image](https://github.com/user-attachments/assets/dc12d09b-f8f2-41d3-b185-cb1e4e8b062b) ![image](https://github.com/user-attachments/assets/1b5490e3-6265-4bad-8af2-eab0a207adab)

ROCm6

![image](https://github.com/user-attachments/assets/3b17a24f-3210-4a99-a81b-f8f2f5578b73) ![image](https://github.com/user-attachments/assets/c7baae55-14e5-45d0-94a0-164ab31bbffc)

--- docs/guides/integration/pytorch.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/guides/integration/pytorch.md b/docs/guides/integration/pytorch.md index 79b6f31e1..7fd0f8004 100644 --- a/docs/guides/integration/pytorch.md +++ b/docs/guides/integration/pytorch.md @@ -85,21 +85,21 @@ In such cases, the first step is to add the relevant PyTorch index to your `pypr explicit = true ``` -=== "CUDA 12.1" +=== "CUDA 12.6" ```toml [[tool.uv.index]] - name = "pytorch-cu121" - url = "https://download.pytorch.org/whl/cu121" + name = "pytorch-cu126" + url = "https://download.pytorch.org/whl/cu126" explicit = true ``` -=== "CUDA 12.4" +=== "CUDA 12.8" ```toml [[tool.uv.index]] - name = "pytorch-cu124" - url = "https://download.pytorch.org/whl/cu124" + name = "pytorch-cu128" + url = "https://download.pytorch.org/whl/cu128" explicit = true ``` @@ -108,7 +108,7 @@ In such cases, the first step is to add the relevant PyTorch index to your `pypr ```toml [[tool.uv.index]] name = "pytorch-rocm" - url = "https://download.pytorch.org/whl/rocm6.2" + url = "https://download.pytorch.org/whl/rocm6.3" explicit = true ``` @@ -154,7 +154,7 @@ Next, update the `pyproject.toml` to point `torch` and `torchvision` to the desi ] ``` -=== "CUDA 12.1" +=== "CUDA 12.6" PyTorch doesn't publish CUDA builds for macOS. As such, we gate on `sys_platform` to instruct uv to limit the PyTorch index to Linux and Windows, falling back to PyPI on macOS: @@ -162,14 +162,14 @@ Next, update the `pyproject.toml` to point `torch` and `torchvision` to the desi ```toml [tool.uv.sources] torch = [ - { index = "pytorch-cu121", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { index = "pytorch-cu126", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, ] torchvision = [ - { index = "pytorch-cu121", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { index = "pytorch-cu126", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, ] ``` -=== "CUDA 12.4" +=== "CUDA 12.8" PyTorch doesn't publish CUDA builds for macOS. As such, we gate on `sys_platform` to instruct uv to limit the PyTorch index to Linux and Windows, falling back to PyPI on macOS: @@ -177,10 +177,10 @@ Next, update the `pyproject.toml` to point `torch` and `torchvision` to the desi ```toml [tool.uv.sources] torch = [ - { index = "pytorch-cu124", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { index = "pytorch-cu128", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, ] torchvision = [ - { index = "pytorch-cu124", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { index = "pytorch-cu128", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, ] ``` From 3d4f0c934e8f663a9be3c72af7c0a32111b9567e Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Tue, 17 Jun 2025 11:50:05 -0400 Subject: [PATCH 006/349] Fix handling of changes to `requires-python` (#14076) When using `uv lock --upgrade-package=python` after changing `requires-python`, it was possible to get into a state where the fork markers produced corresponded to the empty set. This in turn resulted in an empty lock file. There was already some infrastructure in place that I think was perhaps intended to handle this. In particular, `Lock::check_marker_coverage` checks whether the fork markers have some overlap with the supported environments (including the `requires-python`). But there were two problems with this. First is that in lock validation, this marker coverage check came _after_ a path that returned `Preferable` (meaning that the fork markers should be kept) when `--upgrade-package` was used. Second is that the marker coverage check used the `requires-python` in the lock file and _not_ the `requires-python` in the now updated `pyproject.toml`. We attempt to solve this conundrum by slightly re-arranging lock file validation and by explicitly checking whether the *new* `requires-python` is disjoint from the fork markers in the lock file. If it is, then we return `Versions` from lock file validation (indicating that the fork markers should be dropped). Fixes #13951 --- crates/uv-resolver/src/lock/mod.rs | 30 +++++ crates/uv/src/commands/project/lock.rs | 62 ++++++--- crates/uv/tests/it/lock.rs | 169 ++++++++++++++++++++++++- crates/uv/tests/it/sync.rs | 12 +- 4 files changed, 250 insertions(+), 23 deletions(-) diff --git a/crates/uv-resolver/src/lock/mod.rs b/crates/uv-resolver/src/lock/mod.rs index 47597f2ec..5af4377b9 100644 --- a/crates/uv-resolver/src/lock/mod.rs +++ b/crates/uv-resolver/src/lock/mod.rs @@ -768,6 +768,36 @@ impl Lock { } } + /// Checks whether the new requires-python specification is disjoint with + /// the fork markers in this lock file. + /// + /// If they are disjoint, then the union of the fork markers along with the + /// given requires-python specification (converted to a marker tree) are + /// returned. + /// + /// When disjoint, the fork markers in the lock file should be dropped and + /// not used. + pub fn requires_python_coverage( + &self, + new_requires_python: &RequiresPython, + ) -> Result<(), (MarkerTree, MarkerTree)> { + let fork_markers_union = if self.fork_markers().is_empty() { + self.requires_python.to_marker_tree() + } else { + let mut fork_markers_union = MarkerTree::FALSE; + for fork_marker in self.fork_markers() { + fork_markers_union.or(fork_marker.pep508()); + } + fork_markers_union + }; + let new_requires_python = new_requires_python.to_marker_tree(); + if fork_markers_union.is_disjoint(new_requires_python) { + Err((fork_markers_union, new_requires_python)) + } else { + Ok(()) + } + } + /// Returns the TOML representation of this lockfile. pub fn to_toml(&self) -> Result { // Catch a lockfile where the union of fork markers doesn't cover the supported diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index b57df429b..1ab4441b0 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -983,13 +983,54 @@ impl ValidatedLock { return Ok(Self::Unusable(lock)); } Upgrade::Packages(_) => { - // If the user specified `--upgrade-package`, then at best we can prefer some of - // the existing versions. - debug!("Ignoring existing lockfile due to `--upgrade-package`"); - return Ok(Self::Preferable(lock)); + // This is handled below, after some checks regarding fork + // markers. In particular, we'd like to return `Preferable` + // here, but we shouldn't if the fork markers cannot be + // reused. } } + // NOTE: It's important that this appears before any possible path that + // returns `Self::Preferable`. In particular, if our fork markers are + // bunk, then we shouldn't return a result that indicates we should try + // to re-use the existing fork markers. + if let Err((fork_markers_union, environments_union)) = lock.check_marker_coverage() { + warn_user!( + "Ignoring existing lockfile due to fork markers not covering the supported environments: `{}` vs `{}`", + fork_markers_union + .try_to_string() + .unwrap_or("true".to_string()), + environments_union + .try_to_string() + .unwrap_or("true".to_string()), + ); + return Ok(Self::Versions(lock)); + } + + // NOTE: Similarly as above, this should also appear before any + // possible code path that can return `Self::Preferable`. + if let Err((fork_markers_union, requires_python_marker)) = + lock.requires_python_coverage(requires_python) + { + warn_user!( + "Ignoring existing lockfile due to fork markers being disjoint with `requires-python`: `{}` vs `{}`", + fork_markers_union + .try_to_string() + .unwrap_or("true".to_string()), + requires_python_marker + .try_to_string() + .unwrap_or("true".to_string()), + ); + return Ok(Self::Versions(lock)); + } + + if let Upgrade::Packages(_) = upgrade { + // If the user specified `--upgrade-package`, then at best we can prefer some of + // the existing versions. + debug!("Ignoring existing lockfile due to `--upgrade-package`"); + return Ok(Self::Preferable(lock)); + } + // If the Requires-Python bound has changed, we have to perform a clean resolution, since // the set of `resolution-markers` may no longer cover the entire supported Python range. if lock.requires_python().range() != requires_python.range() { @@ -1022,19 +1063,6 @@ impl ValidatedLock { return Ok(Self::Versions(lock)); } - if let Err((fork_markers_union, environments_union)) = lock.check_marker_coverage() { - warn_user!( - "Ignoring existing lockfile due to fork markers not covering the supported environments: `{}` vs `{}`", - fork_markers_union - .try_to_string() - .unwrap_or("true".to_string()), - environments_union - .try_to_string() - .unwrap_or("true".to_string()), - ); - return Ok(Self::Versions(lock)); - } - // If the set of required platforms has changed, we have to perform a clean resolution. let expected = lock.simplified_required_environments(); let actual = required_environments diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index e9615538e..ac20124a0 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -4731,15 +4731,16 @@ fn lock_requires_python_wheels() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.lock(), @r###" + uv_snapshot!(context.filters(), context.lock(), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Using CPython 3.11.[X] interpreter at: [PYTHON-3.11] + warning: Ignoring existing lockfile due to fork markers being disjoint with `requires-python`: `python_full_version == '3.12.*'` vs `python_full_version == '3.11.*'` Resolved 2 packages in [TIME] - "###); + "); let lock = fs_err::read_to_string(&lockfile).unwrap(); @@ -28020,6 +28021,170 @@ fn lock_conflict_for_disjoint_python_version() -> Result<()> { Ok(()) } +/// Check that we hint if the resolution failed for a different platform. +#[cfg(feature = "python-patch")] +#[test] +fn lock_requires_python_empty_lock_file() -> Result<()> { + // N.B. These versions were selected based on what was + // in `.python-versions` at the time of writing (2025-06-16). + let (v1, v2) = ("3.13.0", "3.13.2"); + let context = TestContext::new_with_versions(&[v1, v2]); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str(&format!( + r#" + [project] + name = "renovate-bug-repro" + version = "0.1.0" + requires-python = "=={v1}" + dependencies = ["opencv-python-headless>=4.8"] + "#, + ))?; + + uv_snapshot!(context.filters(), context.lock(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.13.0 interpreter at: [PYTHON-3.13.0] + Resolved 3 packages in [TIME] + "); + + let lock = context.read("uv.lock"); + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + lock, @r#" + version = 1 + revision = 2 + requires-python = "==3.13.0" + resolution-markers = [ + "sys_platform == 'darwin'", + "platform_machine == 'aarch64' and sys_platform == 'linux'", + "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')", + ] + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [[package]] + name = "numpy" + version = "1.26.4" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", size = 15786129, upload-time = "2024-02-06T00:26:44.495Z" } + + [[package]] + name = "opencv-python-headless" + version = "4.9.0.80" + source = { registry = "https://pypi.org/simple" } + dependencies = [ + { name = "numpy" }, + ] + sdist = { url = "https://files.pythonhosted.org/packages/3d/b2/c308bc696bf5d75304175c62222ec8af9a6d5cfe36c14f19f15ea9d1a132/opencv-python-headless-4.9.0.80.tar.gz", hash = "sha256:71a4cd8cf7c37122901d8e81295db7fb188730e33a0e40039a4e59c1030b0958", size = 92910044, upload-time = "2023-12-31T13:34:50.518Z" } + wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/42/da433fca5733a3ce7e88dd0d4018f70dcffaf48770b5142250815f4faddb/opencv_python_headless-4.9.0.80-cp37-abi3-macosx_10_16_x86_64.whl", hash = "sha256:2ea8a2edc4db87841991b2fbab55fc07b97ecb602e0f47d5d485bd75cee17c1a", size = 55689478, upload-time = "2023-12-31T14:31:30.476Z" }, + { url = "https://files.pythonhosted.org/packages/32/0c/a59f2a40d6058ee8126668dc5dff6977c913f6ecd21dbd15b41563409a18/opencv_python_headless-4.9.0.80-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:e0ee54e27be493e8f7850847edae3128e18b540dac1d7b2e4001b8944e11e1c6", size = 35354670, upload-time = "2023-12-31T16:38:31.588Z" }, + { url = "https://files.pythonhosted.org/packages/36/37/225a1f8be42610ffecf677558311ab0f9dfdc63537c250a2bce76762a380/opencv_python_headless-4.9.0.80-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57ce2865e8fec431c6f97a81e9faaf23fa5be61011d0a75ccf47a3c0d65fa73d", size = 28954368, upload-time = "2023-12-31T16:40:00.838Z" }, + { url = "https://files.pythonhosted.org/packages/71/19/3c65483a80a1d062d46ae20faf5404712d25cb1dfdcaf371efbd67c38544/opencv_python_headless-4.9.0.80-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:976656362d68d9f40a5c66f83901430538002465f7db59142784f3893918f3df", size = 49591873, upload-time = "2023-12-31T13:34:44.316Z" }, + { url = "https://files.pythonhosted.org/packages/10/98/300382ff6ddff3a487e808c8a76362e430f5016002fcbefb3b3117aad32b/opencv_python_headless-4.9.0.80-cp37-abi3-win32.whl", hash = "sha256:11e3849d83e6651d4e7699aadda9ec7ed7c38957cbbcb99db074f2a2d2de9670", size = 28488841, upload-time = "2023-12-31T13:34:31.974Z" }, + { url = "https://files.pythonhosted.org/packages/20/44/458a0a135866f5e08266566b32ad9a182a7a059a894effe6c41a9c841ff1/opencv_python_headless-4.9.0.80-cp37-abi3-win_amd64.whl", hash = "sha256:a8056c2cb37cd65dfcdf4153ca16f7362afcf3a50d600d6bb69c660fc61ee29c", size = 38536073, upload-time = "2023-12-31T13:34:39.675Z" }, + ] + + [[package]] + name = "renovate-bug-repro" + version = "0.1.0" + source = { virtual = "." } + dependencies = [ + { name = "opencv-python-headless" }, + ] + + [package.metadata] + requires-dist = [{ name = "opencv-python-headless", specifier = ">=4.8" }] + "# + ); + }); + + pyproject_toml.write_str(&format!( + r#" + [project] + name = "renovate-bug-repro" + version = "0.1.0" + requires-python = "=={v2}" + dependencies = ["opencv-python-headless>=4.8"] + "#, + ))?; + + uv_snapshot!(context.filters(), context.lock().arg("--upgrade-package=python"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.13.2 interpreter at: [PYTHON-3.13.2] + warning: Ignoring existing lockfile due to fork markers being disjoint with `requires-python`: `python_full_version == '3.13.0'` vs `python_full_version == '3.13.2'` + Resolved 3 packages in [TIME] + "); + + let lock = context.read("uv.lock"); + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + lock, @r#" + version = 1 + revision = 2 + requires-python = "==3.13.2" + resolution-markers = [ + "sys_platform == 'darwin'", + "platform_machine == 'aarch64' and sys_platform == 'linux'", + "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')", + ] + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [[package]] + name = "numpy" + version = "1.26.4" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", size = 15786129, upload-time = "2024-02-06T00:26:44.495Z" } + + [[package]] + name = "opencv-python-headless" + version = "4.9.0.80" + source = { registry = "https://pypi.org/simple" } + dependencies = [ + { name = "numpy" }, + ] + sdist = { url = "https://files.pythonhosted.org/packages/3d/b2/c308bc696bf5d75304175c62222ec8af9a6d5cfe36c14f19f15ea9d1a132/opencv-python-headless-4.9.0.80.tar.gz", hash = "sha256:71a4cd8cf7c37122901d8e81295db7fb188730e33a0e40039a4e59c1030b0958", size = 92910044, upload-time = "2023-12-31T13:34:50.518Z" } + wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/42/da433fca5733a3ce7e88dd0d4018f70dcffaf48770b5142250815f4faddb/opencv_python_headless-4.9.0.80-cp37-abi3-macosx_10_16_x86_64.whl", hash = "sha256:2ea8a2edc4db87841991b2fbab55fc07b97ecb602e0f47d5d485bd75cee17c1a", size = 55689478, upload-time = "2023-12-31T14:31:30.476Z" }, + { url = "https://files.pythonhosted.org/packages/32/0c/a59f2a40d6058ee8126668dc5dff6977c913f6ecd21dbd15b41563409a18/opencv_python_headless-4.9.0.80-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:e0ee54e27be493e8f7850847edae3128e18b540dac1d7b2e4001b8944e11e1c6", size = 35354670, upload-time = "2023-12-31T16:38:31.588Z" }, + { url = "https://files.pythonhosted.org/packages/36/37/225a1f8be42610ffecf677558311ab0f9dfdc63537c250a2bce76762a380/opencv_python_headless-4.9.0.80-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57ce2865e8fec431c6f97a81e9faaf23fa5be61011d0a75ccf47a3c0d65fa73d", size = 28954368, upload-time = "2023-12-31T16:40:00.838Z" }, + { url = "https://files.pythonhosted.org/packages/71/19/3c65483a80a1d062d46ae20faf5404712d25cb1dfdcaf371efbd67c38544/opencv_python_headless-4.9.0.80-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:976656362d68d9f40a5c66f83901430538002465f7db59142784f3893918f3df", size = 49591873, upload-time = "2023-12-31T13:34:44.316Z" }, + { url = "https://files.pythonhosted.org/packages/10/98/300382ff6ddff3a487e808c8a76362e430f5016002fcbefb3b3117aad32b/opencv_python_headless-4.9.0.80-cp37-abi3-win32.whl", hash = "sha256:11e3849d83e6651d4e7699aadda9ec7ed7c38957cbbcb99db074f2a2d2de9670", size = 28488841, upload-time = "2023-12-31T13:34:31.974Z" }, + { url = "https://files.pythonhosted.org/packages/20/44/458a0a135866f5e08266566b32ad9a182a7a059a894effe6c41a9c841ff1/opencv_python_headless-4.9.0.80-cp37-abi3-win_amd64.whl", hash = "sha256:a8056c2cb37cd65dfcdf4153ca16f7362afcf3a50d600d6bb69c660fc61ee29c", size = 38536073, upload-time = "2023-12-31T13:34:39.675Z" }, + ] + + [[package]] + name = "renovate-bug-repro" + version = "0.1.0" + source = { virtual = "." } + dependencies = [ + { name = "opencv-python-headless" }, + ] + + [package.metadata] + requires-dist = [{ name = "opencv-python-headless", specifier = ">=4.8" }] + "# + ); + }); + + Ok(()) +} + /// Check that we hint if the resolution failed for a different platform. #[test] fn lock_conflict_for_disjoint_platform() -> Result<()> { diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index 70d8a9118..966dd41d2 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -8140,7 +8140,7 @@ fn sync_dry_run() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync().arg("--dry-run"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--dry-run"), @r" success: true exit_code: 0 ----- stdout ----- @@ -8148,14 +8148,15 @@ fn sync_dry_run() -> Result<()> { ----- stderr ----- Using CPython 3.9.[X] interpreter at: [PYTHON-3.9] Would replace existing virtual environment at: .venv + warning: Ignoring existing lockfile due to fork markers being disjoint with `requires-python`: `python_full_version >= '3.12'` vs `python_full_version == '3.9.*'` Resolved 2 packages in [TIME] Would update lockfile at: uv.lock Would install 1 package + iniconfig==2.0.0 - "###); + "); // Perform a full sync. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -8164,10 +8165,11 @@ fn sync_dry_run() -> Result<()> { Using CPython 3.9.[X] interpreter at: [PYTHON-3.9] Removed virtual environment at: .venv Creating virtual environment at: .venv + warning: Ignoring existing lockfile due to fork markers being disjoint with `requires-python`: `python_full_version >= '3.12'` vs `python_full_version == '3.9.*'` Resolved 2 packages in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); let output = context.sync().arg("--dry-run").arg("-vv").output()?; let stderr = String::from_utf8_lossy(&output.stderr); @@ -8658,6 +8660,7 @@ fn sync_locked_script() -> Result<()> { ----- stderr ----- Recreating script environment at: [CACHE_DIR]/environments-v2/script-[HASH] + warning: Ignoring existing lockfile due to fork markers being disjoint with `requires-python`: `python_full_version >= '3.11'` vs `python_full_version >= '3.8' and python_full_version < '3.11'` Resolved 6 packages in [TIME] error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. "); @@ -8669,6 +8672,7 @@ fn sync_locked_script() -> Result<()> { ----- stderr ----- Using script environment at: [CACHE_DIR]/environments-v2/script-[HASH] + warning: Ignoring existing lockfile due to fork markers being disjoint with `requires-python`: `python_full_version >= '3.11'` vs `python_full_version >= '3.8' and python_full_version < '3.11'` Resolved 6 packages in [TIME] Prepared 2 packages in [TIME] Installed 6 packages in [TIME] From 10e1d17cfce45c3759bc232fc270af993f9812cd Mon Sep 17 00:00:00 2001 From: konsti Date: Tue, 17 Jun 2025 19:18:08 +0200 Subject: [PATCH 007/349] Don't use walrus operator in interpreter query script (#14108) Fix `uv run -p 3.7` by not using a walrus operator. Python 3.7 isn't really supported anymore, but there's no reason to break interpreter discovery for it. --- crates/uv-python/python/packaging/_manylinux.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/uv-python/python/packaging/_manylinux.py b/crates/uv-python/python/packaging/_manylinux.py index ea7125c76..a0e8846e7 100644 --- a/crates/uv-python/python/packaging/_manylinux.py +++ b/crates/uv-python/python/packaging/_manylinux.py @@ -255,5 +255,6 @@ def platform_tags(archs: Sequence[str]) -> Iterator[str]: if _is_compatible(arch, glibc_version): yield "manylinux_{}_{}_{}".format(*glibc_version, arch) # Handle the legacy manylinux1, manylinux2010, manylinux2014 tags. - if legacy_tag := _LEGACY_MANYLINUX_MAP.get(glibc_version): + legacy_tag = _LEGACY_MANYLINUX_MAP.get(glibc_version) + if legacy_tag: yield f"{legacy_tag}_{arch}" From c25c800367a5f43069f1c9d778cfd5de1bcc54a6 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 17 Jun 2025 12:28:23 -0500 Subject: [PATCH 008/349] Fix Ruff linting (#14111) --- crates/uv-build/ruff.toml | 2 ++ crates/uv-python/fetch-download-metadata.py | 4 +++- crates/uv-python/python/get_interpreter_info.py | 13 +++++++------ crates/uv-python/python/packaging/_elffile.py | 3 +-- crates/uv-python/python/packaging/_manylinux.py | 3 +-- crates/uv-python/python/ruff.toml | 2 ++ ruff.toml | 4 ++-- scripts/transform_readme.py | 3 +-- .../albatross-in-example/src/albatross/__init__.py | 2 +- 9 files changed, 20 insertions(+), 16 deletions(-) create mode 100644 crates/uv-build/ruff.toml create mode 100644 crates/uv-python/python/ruff.toml diff --git a/crates/uv-build/ruff.toml b/crates/uv-build/ruff.toml new file mode 100644 index 000000000..e480507a2 --- /dev/null +++ b/crates/uv-build/ruff.toml @@ -0,0 +1,2 @@ +# It is important retain compatibility with old versions in the build backend +target-version = "py37" diff --git a/crates/uv-python/fetch-download-metadata.py b/crates/uv-python/fetch-download-metadata.py index 0b5d9caec..08adaecea 100755 --- a/crates/uv-python/fetch-download-metadata.py +++ b/crates/uv-python/fetch-download-metadata.py @@ -630,7 +630,9 @@ class GraalPyFinder(Finder): for download in batch: url = download.url + ".sha256" checksum_requests.append(self.client.get(url)) - for download, resp in zip(batch, await asyncio.gather(*checksum_requests)): + for download, resp in zip( + batch, await asyncio.gather(*checksum_requests), strict=False + ): try: resp.raise_for_status() except httpx.HTTPStatusError as e: diff --git a/crates/uv-python/python/get_interpreter_info.py b/crates/uv-python/python/get_interpreter_info.py index 0fe088819..8e9fc37fd 100644 --- a/crates/uv-python/python/get_interpreter_info.py +++ b/crates/uv-python/python/get_interpreter_info.py @@ -39,10 +39,9 @@ if hasattr(sys, "implementation"): # GraalPy reports the CPython version as sys.implementation.version, # so we need to discover the GraalPy version from the cache_tag import re + implementation_version = re.sub( - r"graalpy(\d)(\d+)-\d+", - r"\1.\2", - sys.implementation.cache_tag + r"graalpy(\d)(\d+)-\d+", r"\1.\2", sys.implementation.cache_tag ) else: implementation_version = format_full_version(sys.implementation.version) @@ -583,7 +582,6 @@ def main() -> None: elif os_and_arch["os"]["name"] == "musllinux": manylinux_compatible = True - # By default, pip uses sysconfig on Python 3.10+. # But Python distributors can override this decision by setting: # sysconfig._PIP_USE_SYSCONFIG = True / False @@ -608,7 +606,7 @@ def main() -> None: except (ImportError, AttributeError): pass - import distutils.dist + import distutils.dist # noqa: F401 except ImportError: # We require distutils, but it's not installed; this is fairly # common in, e.g., deadsnakes where distutils is packaged @@ -641,7 +639,10 @@ def main() -> None: # Prior to the introduction of `sysconfig` patching, python-build-standalone installations would always use # "/install" as the prefix. With `sysconfig` patching, we rewrite the prefix to match the actual installation # location. So in newer versions, we also write a dedicated flag to indicate standalone builds. - "standalone": sysconfig.get_config_var("prefix") == "/install" or bool(sysconfig.get_config_var("PYTHON_BUILD_STANDALONE")), + "standalone": ( + sysconfig.get_config_var("prefix") == "/install" + or bool(sysconfig.get_config_var("PYTHON_BUILD_STANDALONE")) + ), "scheme": get_scheme(use_sysconfig_scheme), "virtualenv": get_virtualenv(), "platform": os_and_arch, diff --git a/crates/uv-python/python/packaging/_elffile.py b/crates/uv-python/python/packaging/_elffile.py index f7a02180b..8dc7fb32a 100644 --- a/crates/uv-python/python/packaging/_elffile.py +++ b/crates/uv-python/python/packaging/_elffile.py @@ -69,8 +69,7 @@ class ELFFile: }[(self.capacity, self.encoding)] except KeyError: raise ELFInvalid( - f"unrecognized capacity ({self.capacity}) or " - f"encoding ({self.encoding})" + f"unrecognized capacity ({self.capacity}) or encoding ({self.encoding})" ) try: diff --git a/crates/uv-python/python/packaging/_manylinux.py b/crates/uv-python/python/packaging/_manylinux.py index a0e8846e7..7b52a5581 100644 --- a/crates/uv-python/python/packaging/_manylinux.py +++ b/crates/uv-python/python/packaging/_manylinux.py @@ -161,8 +161,7 @@ def _parse_glibc_version(version_str: str) -> _GLibCVersion: m = re.match(r"(?P[0-9]+)\.(?P[0-9]+)", version_str) if not m: warnings.warn( - f"Expected glibc version with 2 components major.minor," - f" got: {version_str}", + f"Expected glibc version with 2 components major.minor, got: {version_str}", RuntimeWarning, ) return _GLibCVersion(-1, -1) diff --git a/crates/uv-python/python/ruff.toml b/crates/uv-python/python/ruff.toml new file mode 100644 index 000000000..5e6921be4 --- /dev/null +++ b/crates/uv-python/python/ruff.toml @@ -0,0 +1,2 @@ +# It is important retain compatibility when querying interpreters +target-version = "py37" diff --git a/ruff.toml b/ruff.toml index 8aac77263..7c6488a1e 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,10 +1,10 @@ -target-version = "py37" +target-version = "py312" exclude = [ "crates/uv-virtualenv/src/activator/activate_this.py", "crates/uv-virtualenv/src/_virtualenv.py", - "crates/uv-python/python", "ecosystem", "scripts/workspaces", + "scripts/packages", ] [lint] diff --git a/scripts/transform_readme.py b/scripts/transform_readme.py index b8f11fbe0..9d52d6517 100644 --- a/scripts/transform_readme.py +++ b/scripts/transform_readme.py @@ -9,11 +9,10 @@ from __future__ import annotations import argparse import re +import tomllib import urllib.parse from pathlib import Path -import tomllib - # To be kept in sync with: `docs/index.md` URL = "https://github.com/astral-sh/uv/assets/1309177/{}" URL_LIGHT = URL.format("629e59c0-9c6e-4013-9ad4-adb2bcf5080d") diff --git a/scripts/workspaces/albatross-in-example/src/albatross/__init__.py b/scripts/workspaces/albatross-in-example/src/albatross/__init__.py index d79aed9cb..764c5ce3f 100644 --- a/scripts/workspaces/albatross-in-example/src/albatross/__init__.py +++ b/scripts/workspaces/albatross-in-example/src/albatross/__init__.py @@ -5,5 +5,5 @@ def fly(): pass -if __name__ == '__main__': +if __name__ == "__main__": print("Caw") From 8808e67cff7b6c2a4320a40322d80931d2724be5 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 17 Jun 2025 15:04:39 -0500 Subject: [PATCH 009/349] Add a `docker-plan` step to consolidate push and tag logic (#14083) The dist plan parsing is pretty hard to understand, and I want to add more images, e.g., for DockerHub in #14088. As a simplifying precursor... move the dist plan processing into a dedicated step. --- .github/workflows/build-docker.yml | 55 +++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index ce12d3c5d..e310add68 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -40,9 +40,35 @@ env: UV_BASE_IMG: ghcr.io/${{ github.repository_owner }}/uv jobs: + docker-plan: + name: plan + runs-on: ubuntu-latest + outputs: + push: ${{ steps.plan.outputs.push }} + tag: ${{ steps.plan.outputs.tag }} + action: ${{ steps.plan.outputs.action }} + steps: + - name: Set push variable + env: + DRY_RUN: ${{ inputs.plan == '' || fromJson(inputs.plan).announcement_tag_is_implicit }} + TAG: ${{ inputs.plan != '' && fromJson(inputs.plan).announcement_tag }} + id: plan + run: | + if [ "${{ env.DRY_RUN }}" == "false" ]; then + echo "push=true" >> "$GITHUB_OUTPUT" + echo "tag=${{ env.TAG }}" >> "$GITHUB_OUTPUT" + echo "action=build and publish" >> "$GITHUB_OUTPUT" + else + echo "push=false" >> "$GITHUB_OUTPUT" + echo "tag=dry-run" >> "$GITHUB_OUTPUT" + echo "action=build" >> "$GITHUB_OUTPUT" + fi + docker-publish-base: if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }} - name: uv + name: ${{ needs.docker-plan.outputs.action }} uv + needs: + - docker-plan runs-on: ubuntu-latest permissions: contents: read @@ -75,12 +101,12 @@ jobs: - uses: depot/setup-action@b0b1ea4f69e92ebf5dea3f8713a1b0c37b2126a5 - name: Check tag consistency - if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }} + if: ${{ needs.docker-plan.outputs.push == 'true' }} run: | version=$(grep "version = " pyproject.toml | sed -e 's/version = "\(.*\)"/\1/g') - if [ "${{ fromJson(inputs.plan).announcement_tag }}" != "${version}" ]; then + if [ "${{ needs.docker-plan.outputs.tag }}" != "${version}" ]; then echo "The input tag does not match the version from pyproject.toml:" >&2 - echo "${{ fromJson(inputs.plan).announcement_tag }}" >&2 + echo "${{ needs.docker-plan.outputs.tag }}" >&2 echo "${version}" >&2 exit 1 else @@ -94,9 +120,9 @@ jobs: images: ${{ env.UV_BASE_IMG }} # Defining this makes sure the org.opencontainers.image.version OCI label becomes the actual release version and not the branch name tags: | - type=raw,value=dry-run,enable=${{ inputs.plan == '' || fromJson(inputs.plan).announcement_tag_is_implicit }} - type=pep440,pattern={{ version }},value=${{ inputs.plan != '' && fromJson(inputs.plan).announcement_tag || 'dry-run' }},enable=${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }} - type=pep440,pattern={{ major }}.{{ minor }},value=${{ inputs.plan != '' && fromJson(inputs.plan).announcement_tag || 'dry-run' }},enable=${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }} + type=raw,value=dry-run,enable=${{ needs.docker-plan.outputs.push == 'false' }} + type=pep440,pattern={{ version }},value=${{ needs.docker-plan.outputs.tag }},enable=${{ needs.docker-plan.outputs.push }} + type=pep440,pattern={{ major }}.{{ minor }},value=${{ needs.docker-plan.outputs.tag }},enable=${{ needs.docker-plan.outputs.push }} - name: Build and push by digest id: build @@ -105,25 +131,26 @@ jobs: project: 7hd4vdzmw5 # astral-sh/uv context: . platforms: linux/amd64,linux/arm64 - push: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }} + push: ${{ needs.docker-plan.outputs.push }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - name: Generate artifact attestation for base image - if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }} + if: ${{ needs.docker-plan.outputs.push == 'true' }} uses: actions/attest-build-provenance@c074443f1aee8d4aeeae555aebba3282517141b2 # v2.2.3 with: subject-name: ${{ env.UV_BASE_IMG }} subject-digest: ${{ steps.build.outputs.digest }} docker-publish-extra: - name: ${{ matrix.image-mapping }} + name: ${{ needs.docker-plan.outputs.action }} ${{ matrix.image-mapping }} runs-on: ubuntu-latest environment: name: release needs: + - docker-plan - docker-publish-base - if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }} + if: ${{ needs.docker-plan.outputs.push == 'true' }} permissions: packages: write attestations: write # needed to push image attestations to the Github attestation store @@ -196,8 +223,8 @@ jobs: # Loop through all base tags and append its docker metadata pattern to the list # Order is on purpose such that the label org.opencontainers.image.version has the first pattern with the full version IFS=','; for TAG in ${BASE_TAGS}; do - TAG_PATTERNS="${TAG_PATTERNS}type=pep440,pattern={{ version }},suffix=-${TAG},value=${{ fromJson(inputs.plan).announcement_tag }}\n" - TAG_PATTERNS="${TAG_PATTERNS}type=pep440,pattern={{ major }}.{{ minor }},suffix=-${TAG},value=${{ fromJson(inputs.plan).announcement_tag }}\n" + TAG_PATTERNS="${TAG_PATTERNS}type=pep440,pattern={{ version }},suffix=-${TAG},value=${{ needs.docker-plan.outputs.tag }}\n" + TAG_PATTERNS="${TAG_PATTERNS}type=pep440,pattern={{ major }}.{{ minor }},suffix=-${TAG},value=${{ needs.docker-plan.outputs.tag }}\n" TAG_PATTERNS="${TAG_PATTERNS}type=raw,value=${TAG}\n" done @@ -249,8 +276,10 @@ jobs: environment: name: release needs: + - docker-plan - docker-publish-base - docker-publish-extra + if: ${{ needs.docker-plan.outputs.push == 'true' }} steps: - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 with: From 6c096246d826f68ba9a7f0e8cb6efa73b19aa24c Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 17 Jun 2025 16:49:00 -0400 Subject: [PATCH 010/349] Remove preview label from `--torch-backend` (#14119) This is now used in enough places that I'm comfortable committing to maintaining it under our versioning policy. Closes #14091. --- crates/uv/src/commands/pip/compile.rs | 26 ++++++++++++-------------- crates/uv/src/commands/pip/install.rs | 26 ++++++++++++-------------- crates/uv/src/commands/pip/sync.rs | 26 ++++++++++++-------------- docs/guides/integration/pytorch.md | 25 ++++++++++++++++--------- 4 files changed, 52 insertions(+), 51 deletions(-) diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs index 20a60416f..197455aae 100644 --- a/crates/uv/src/commands/pip/compile.rs +++ b/crates/uv/src/commands/pip/compile.rs @@ -388,20 +388,18 @@ pub(crate) async fn pip_compile( } // Determine the PyTorch backend. - let torch_backend = torch_backend.map(|mode| { - if preview.is_disabled() { - warn_user!("The `--torch-backend` setting is experimental and may change without warning. Pass `--preview` to disable this warning."); - } - - TorchStrategy::from_mode( - mode, - python_platform - .map(TargetTriple::platform) - .as_ref() - .unwrap_or(interpreter.platform()) - .os(), - ) - }).transpose()?; + let torch_backend = torch_backend + .map(|mode| { + TorchStrategy::from_mode( + mode, + python_platform + .map(TargetTriple::platform) + .as_ref() + .unwrap_or(interpreter.platform()) + .os(), + ) + }) + .transpose()?; // Initialize the registry client. let client = RegistryClientBuilder::try_from(client_builder)? diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs index 5fc9a66f4..e4f524c57 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -344,20 +344,18 @@ pub(crate) async fn pip_install( } // Determine the PyTorch backend. - let torch_backend = torch_backend.map(|mode| { - if preview.is_disabled() { - warn_user!("The `--torch-backend` setting is experimental and may change without warning. Pass `--preview` to disable this warning."); - } - - TorchStrategy::from_mode( - mode, - python_platform - .map(TargetTriple::platform) - .as_ref() - .unwrap_or(interpreter.platform()) - .os(), - ) - }).transpose()?; + let torch_backend = torch_backend + .map(|mode| { + TorchStrategy::from_mode( + mode, + python_platform + .map(TargetTriple::platform) + .as_ref() + .unwrap_or(interpreter.platform()) + .os(), + ) + }) + .transpose()?; // Initialize the registry client. let client = RegistryClientBuilder::try_from(client_builder)? diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index 35cef5907..e5bf92ae4 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -277,20 +277,18 @@ pub(crate) async fn pip_sync( } // Determine the PyTorch backend. - let torch_backend = torch_backend.map(|mode| { - if preview.is_disabled() { - warn_user!("The `--torch-backend` setting is experimental and may change without warning. Pass `--preview` to disable this warning."); - } - - TorchStrategy::from_mode( - mode, - python_platform - .map(TargetTriple::platform) - .as_ref() - .unwrap_or(interpreter.platform()) - .os(), - ) - }).transpose()?; + let torch_backend = torch_backend + .map(|mode| { + TorchStrategy::from_mode( + mode, + python_platform + .map(TargetTriple::platform) + .as_ref() + .unwrap_or(interpreter.platform()) + .os(), + ) + }) + .transpose()?; // Initialize the registry client. let client = RegistryClientBuilder::try_from(client_builder)? diff --git a/docs/guides/integration/pytorch.md b/docs/guides/integration/pytorch.md index 7fd0f8004..fb1d82b31 100644 --- a/docs/guides/integration/pytorch.md +++ b/docs/guides/integration/pytorch.md @@ -433,11 +433,14 @@ $ uv pip install torch torchvision torchaudio --index-url https://download.pytor ## Automatic backend selection -In [preview](../../reference/settings.md#preview), uv can automatically select the appropriate -PyTorch index at runtime by inspecting the system configuration via `--torch-backend=auto` (or -`UV_TORCH_BACKEND=auto`): +uv supports automatic selection of the appropriate PyTorch index via the `--torch-backend=auto` +command-line argument (or the `UV_TORCH_BACKEND=auto` environment variable), as in: ```shell +$ # With a command-line argument. +$ uv pip install torch --torch-backend=auto + +$ # With an environment variable. $ UV_TORCH_BACKEND=auto uv pip install torch ``` @@ -446,12 +449,16 @@ PyTorch index for all relevant packages (e.g., `torch`, `torchvision`, etc.). If 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. -To select a specific backend (e.g., `cu126`), set `--torch-backend=cu126` (or -`UV_TORCH_BACKEND=cu126`). +You can also select a specific backend (e.g., CUDA 12.6) with `--torch-backend=cu126` (or +`UV_TORCH_BACKEND=cu126`): + +```shell +$ # With a command-line argument. +$ uv pip install torch torchvision --torch-backend=cu126 + +$ # With an environment variable. +$ UV_TORCH_BACKEND=cu126 uv pip install torch torchvision +``` At present, `--torch-backend` is only available in the `uv pip` interface, and only supports detection of CUDA drivers (as opposed to other accelerators like ROCm or Intel GPUs). - -As `--torch-backend` is a preview feature, it should be considered experimental and is not governed -by uv's standard [versioning policy](../../reference/policies/versioning.md). `--torch-backend` may -change or be removed entirely in future versions of uv. From 47c522f9be7ea61884c424d73bcd1d75cea77694 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 17 Jun 2025 17:45:11 -0500 Subject: [PATCH 011/349] Serialize Python requests for tools as canonicalized strings (#14109) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When working on support for reading global Python pins in tool operations, I noticed that we weren't using the canonicalized Python request in receipts — we were using the raw string provided by the user. Since we'll need to compare these values, we should be using the canonicalized string. The `Tool` and `ToolReceipt` types have been updated to hold a `PythonRequest` instead of a `String`, and `Serialize` was implemented for `PythonRequest` so canonicalization can happen at the edge instead of being the caller's responsibility. --- crates/uv-python/src/discovery.rs | 20 ++++++++++++++++++++ crates/uv-tool/src/tool.rs | 17 ++++++++++++----- crates/uv/src/commands/tool/common.rs | 10 +++++++--- crates/uv/src/commands/tool/install.rs | 8 +++++--- crates/uv/src/commands/tool/upgrade.rs | 4 ++-- 5 files changed, 46 insertions(+), 13 deletions(-) diff --git a/crates/uv-python/src/discovery.rs b/crates/uv-python/src/discovery.rs index 3858fd525..27853e3db 100644 --- a/crates/uv-python/src/discovery.rs +++ b/crates/uv-python/src/discovery.rs @@ -67,6 +67,26 @@ pub enum PythonRequest { Key(PythonDownloadRequest), } +impl<'a> serde::Deserialize<'a> for PythonRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'a>, + { + let s = String::deserialize(deserializer)?; + Ok(PythonRequest::parse(&s)) + } +} + +impl serde::Serialize for PythonRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let s = self.to_canonical_string(); + serializer.serialize_str(&s) + } +} + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Deserialize)] #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[cfg_attr(feature = "clap", derive(clap::ValueEnum))] diff --git a/crates/uv-tool/src/tool.rs b/crates/uv-tool/src/tool.rs index df8571c94..cce3a2f58 100644 --- a/crates/uv-tool/src/tool.rs +++ b/crates/uv-tool/src/tool.rs @@ -7,6 +7,7 @@ use toml_edit::{Array, Item, Table, Value, value}; use uv_distribution_types::Requirement; use uv_fs::{PortablePath, Simplified}; use uv_pypi_types::VerbatimParsedUrl; +use uv_python::PythonRequest; use uv_settings::ToolOptions; /// A tool entry. @@ -22,7 +23,7 @@ pub struct Tool { /// The build constraints requested by the user during installation. build_constraints: Vec, /// The Python requested by the user during installation. - python: Option, + python: Option, /// A mapping of entry point names to their metadata. entrypoints: Vec, /// The [`ToolOptions`] used to install this tool. @@ -40,7 +41,7 @@ struct ToolWire { overrides: Vec, #[serde(default)] build_constraint_dependencies: Vec, - python: Option, + python: Option, entrypoints: Vec, #[serde(default)] options: ToolOptions, @@ -164,7 +165,7 @@ impl Tool { constraints: Vec, overrides: Vec, build_constraints: Vec, - python: Option, + python: Option, entrypoints: impl Iterator, options: ToolOptions, ) -> Self { @@ -280,7 +281,13 @@ impl Tool { } if let Some(ref python) = self.python { - table.insert("python", value(python)); + table.insert( + "python", + value(serde::Serialize::serialize( + &python, + toml_edit::ser::ValueSerializer::new(), + )?), + ); } table.insert("entrypoints", { @@ -327,7 +334,7 @@ impl Tool { &self.build_constraints } - pub fn python(&self) -> &Option { + pub fn python(&self) -> &Option { &self.python } diff --git a/crates/uv/src/commands/tool/common.rs b/crates/uv/src/commands/tool/common.rs index 77aba8619..807225cbc 100644 --- a/crates/uv/src/commands/tool/common.rs +++ b/crates/uv/src/commands/tool/common.rs @@ -158,14 +158,18 @@ pub(crate) async fn refine_interpreter( Ok(Some(interpreter)) } -/// Installs tool executables for a given package and handles any conflicts. -pub(crate) fn install_executables( +/// Finalizes a tool installation, after creation of an environment. +/// +/// Installs tool executables for a given package, handling any conflicts. +/// +/// Adds a receipt for the tool. +pub(crate) fn finalize_tool_install( environment: &PythonEnvironment, name: &PackageName, installed_tools: &InstalledTools, options: ToolOptions, force: bool, - python: Option, + python: Option, requirements: Vec, constraints: Vec, overrides: Vec, diff --git a/crates/uv/src/commands/tool/install.rs b/crates/uv/src/commands/tool/install.rs index 86b0d4bc6..a65ad3af2 100644 --- a/crates/uv/src/commands/tool/install.rs +++ b/crates/uv/src/commands/tool/install.rs @@ -33,7 +33,9 @@ use crate::commands::project::{ EnvironmentSpecification, PlatformState, ProjectError, resolve_environment, resolve_names, sync_environment, update_environment, }; -use crate::commands::tool::common::{install_executables, refine_interpreter, remove_entrypoints}; +use crate::commands::tool::common::{ + finalize_tool_install, refine_interpreter, remove_entrypoints, +}; use crate::commands::tool::{Target, ToolRequest}; use crate::commands::{diagnostics, reporters::PythonDownloadReporter}; use crate::printer::Printer; @@ -592,13 +594,13 @@ pub(crate) async fn install( } }; - install_executables( + finalize_tool_install( &environment, &from.name, &installed_tools, options, force || invalid_tool_receipt, - python, + python_request, requirements, constraints, overrides, diff --git a/crates/uv/src/commands/tool/upgrade.rs b/crates/uv/src/commands/tool/upgrade.rs index 9f4d3bcab..c930ecada 100644 --- a/crates/uv/src/commands/tool/upgrade.rs +++ b/crates/uv/src/commands/tool/upgrade.rs @@ -29,7 +29,7 @@ use crate::commands::project::{ }; use crate::commands::reporters::PythonDownloadReporter; use crate::commands::tool::common::remove_entrypoints; -use crate::commands::{ExitStatus, conjunction, tool::common::install_executables}; +use crate::commands::{ExitStatus, conjunction, tool::common::finalize_tool_install}; use crate::printer::Printer; use crate::settings::{NetworkSettings, ResolverInstallerSettings}; @@ -375,7 +375,7 @@ async fn upgrade_tool( remove_entrypoints(&existing_tool_receipt); // If we modified the target tool, reinstall the entrypoints. - install_executables( + finalize_tool_install( &environment, name, installed_tools, From 2fc922144a35906e9744479d606f5b4b7b51802e Mon Sep 17 00:00:00 2001 From: John Mumm Date: Wed, 18 Jun 2025 03:43:45 -0400 Subject: [PATCH 012/349] Add script for testing uv against different registries (#13615) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR provides a script that uses environment variables to determine which registries to test. This script is being used to run automated registry tests in CI for AWS, Azure, GCP, Artifactory, GitLab, Cloudsmith, and Gemfury. You must configure the following required env vars for each registry: ``` UV_TEST__URL URL for the registry UV_TEST__TOKEN authentication token UV_TEST__PKG private package to install ``` The username defaults to "\_\_token\_\_" but can be optionally set with: ``` UV_TEST__USERNAME ``` For each configured registry, the test will attempt to install the specified package. Some registries can fall back to PyPI internally, so it's important to choose a package that only exists in the registry you are testing. Currently, a successful test means that it finds the line “ + ” in the output. This is because in its current form we don’t know ahead of time what package it is and hence what the exact expected output would be. The advantage if that anyone can run this locally, though they would have to have access to the registries they want to test. You can also use the `--use-op` command line argument to derive these test env vars from a 1Password vault (default is "RegistryTests" but can be configured with `--op-vault`). It will look at all items in the vault with names following the pattern `UV_TEST_` and will derive the env vars as follows: ``` `UV_TEST__USERNAME` from the `username` field `UV_TEST__TOKEN` from the `password` field `UV_TEST__URL` from a field with the label `url` `UV_TEST__PKG` from a field with the label `pkg` ``` --- .github/workflows/ci.yml | 84 ++++++++ scripts/registries-test.py | 422 +++++++++++++++++++++++++++++++++++++ 2 files changed, 506 insertions(+) create mode 100644 scripts/registries-test.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8a1f78c9e..feaa38210 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1416,6 +1416,90 @@ jobs: done <<< "${CHANGED_FILES}" echo "code_any_changed=${CODE_CHANGED}" >> "${GITHUB_OUTPUT}" + integration-test-registries: + timeout-minutes: 10 + needs: build-binary-linux-libc + name: "integration test | registries" + runs-on: ubuntu-latest + if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && github.event.pull_request.head.repo.fork != true }} + environment: uv-test-registries + env: + PYTHON_VERSION: 3.12 + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: "${{ env.PYTHON_VERSION }}" + + - name: "Download binary" + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: uv-linux-libc-${{ github.sha }} + + - name: "Prepare binary" + run: chmod +x ./uv + + - name: "Configure AWS credentials" + uses: aws-actions/configure-aws-credentials@b47578312673ae6fa5b5096b330d9fbac3d116df + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-east-1 + + - name: "Get AWS CodeArtifact token" + run: | + UV_TEST_AWS_TOKEN=$(aws codeartifact get-authorization-token \ + --domain tests \ + --domain-owner ${{ secrets.AWS_ACCOUNT_ID }} \ + --region us-east-1 \ + --query authorizationToken \ + --output text) + echo "::add-mask::$UV_TEST_AWS_TOKEN" + echo "UV_TEST_AWS_TOKEN=$UV_TEST_AWS_TOKEN" >> $GITHUB_ENV + + - name: "Authenticate with GCP" + id: "auth" + uses: "google-github-actions/auth@ba79af03959ebeac9769e648f473a284504d9193" + with: + credentials_json: "${{ secrets.GCP_SERVICE_ACCOUNT_KEY }}" + + - name: "Set up GCP SDK" + uses: "google-github-actions/setup-gcloud@77e7a554d41e2ee56fc945c52dfd3f33d12def9a" + + - name: "Get GCP Artifact Registry token" + id: get_token + run: | + UV_TEST_GCP_TOKEN=$(gcloud auth print-access-token) + echo "::add-mask::$UV_TEST_GCP_TOKEN" + echo "UV_TEST_GCP_TOKEN=$UV_TEST_GCP_TOKEN" >> $GITHUB_ENV + + - name: "Run registry tests" + run: ./uv run -p ${{ env.PYTHON_VERSION }} scripts/registries-test.py --uv ./uv --color always --all + env: + RUST_LOG: uv=debug + UV_TEST_ARTIFACTORY_TOKEN: ${{ secrets.UV_TEST_ARTIFACTORY_TOKEN }} + UV_TEST_ARTIFACTORY_URL: ${{ secrets.UV_TEST_ARTIFACTORY_URL }} + UV_TEST_ARTIFACTORY_USERNAME: ${{ secrets.UV_TEST_ARTIFACTORY_USERNAME }} + UV_TEST_AWS_URL: ${{ secrets.UV_TEST_AWS_URL }} + UV_TEST_AWS_USERNAME: aws + UV_TEST_AZURE_TOKEN: ${{ secrets.UV_TEST_AZURE_TOKEN }} + UV_TEST_AZURE_URL: ${{ secrets.UV_TEST_AZURE_URL }} + UV_TEST_AZURE_USERNAME: dummy + UV_TEST_CLOUDSMITH_TOKEN: ${{ secrets.UV_TEST_CLOUDSMITH_TOKEN }} + UV_TEST_CLOUDSMITH_URL: ${{ secrets.UV_TEST_CLOUDSMITH_URL }} + UV_TEST_CLOUDSMITH_USERNAME: ${{ secrets.UV_TEST_CLOUDSMITH_USERNAME }} + UV_TEST_GCP_URL: ${{ secrets.UV_TEST_GCP_URL }} + UV_TEST_GCP_USERNAME: oauth2accesstoken + UV_TEST_GEMFURY_TOKEN: ${{ secrets.UV_TEST_GEMFURY_TOKEN }} + UV_TEST_GEMFURY_URL: ${{ secrets.UV_TEST_GEMFURY_URL }} + UV_TEST_GEMFURY_USERNAME: ${{ secrets.UV_TEST_GEMFURY_USERNAME }} + UV_TEST_GITLAB_TOKEN: ${{ secrets.UV_TEST_GITLAB_TOKEN }} + UV_TEST_GITLAB_URL: ${{ secrets.UV_TEST_GITLAB_URL }} + UV_TEST_GITLAB_USERNAME: token + integration-test-publish: timeout-minutes: 20 needs: integration-test-publish-changed diff --git a/scripts/registries-test.py b/scripts/registries-test.py new file mode 100644 index 000000000..2d4c1d2aa --- /dev/null +++ b/scripts/registries-test.py @@ -0,0 +1,422 @@ +#!/usr/bin/env python3 +""" +Test `uv add` against multiple Python package registries. + +This script looks for environment variables that configure registries for testing. +To configure a registry, set the following environment variables: + + `UV_TEST__URL` URL for the registry + `UV_TEST__TOKEN` authentication token + +The username defaults to "__token__" but can be optionally set with: + `UV_TEST__USERNAME` + +The package to install defaults to "astral-registries-test-pkg" but can be optionally +set with: + `UV_TEST__PKG` + +Keep in mind that some registries can fall back to PyPI internally, so make sure +you choose a package that only exists in the registry you are testing. + +You can also use the 1Password CLI to fetch registry credentials from a vault by passing +the `--use-op` flag. For each item in the vault named `UV_TEST_XXX`, the script will set +env vars for any of the following fields, if present: + `UV_TEST__USERNAME` from the `username` field + `UV_TEST__TOKEN` from the `password` field + `UV_TEST__URL` from a field with the label `url` + `UV_TEST__PKG` from a field with the label `pkg` + +# /// script +# requires-python = ">=3.12" +# dependencies = ["colorama>=0.4.6"] +# /// +""" + +import argparse +import json +import os +import re +import subprocess +import sys +import tempfile +from pathlib import Path +from typing import Dict + +import colorama +from colorama import Fore + + +def initialize_colorama(force_color=False): + colorama.init(strip=not force_color, autoreset=True) + + +cwd = Path(__file__).parent + +DEFAULT_TIMEOUT = 30 +DEFAULT_PKG_NAME = "astral-registries-test-pkg" + +KNOWN_REGISTRIES = [ + "artifactory", + "azure", + "aws", + "cloudsmith", + "gcp", + "gemfury", + "gitlab", +] + + +def fetch_op_items(vault_name: str, env: Dict[str, str]) -> Dict[str, str]: + """Fetch items from the specified 1Password vault and add them to the environment. + + For each item named UV_TEST_XXX in the vault: + - Set `UV_TEST_XXX_USERNAME` to the `username` field + - Set `UV_TEST_XXX_TOKEN` to the `password` field + - Set `UV_TEST_XXX_URL` to the `url` field + + Raises exceptions for any 1Password CLI errors so they can be handled by the caller. + """ + # Run 'op item list' to get all items in the vault + result = subprocess.run( + ["op", "item", "list", "--vault", vault_name, "--format", "json"], + capture_output=True, + text=True, + check=True, + ) + + items = json.loads(result.stdout) + updated_env = env.copy() + + for item in items: + item_id = item["id"] + item_title = item["title"] + + # Only process items that match the registry naming pattern + if item_title.startswith("UV_TEST_"): + # Extract the registry name (e.g., "AWS" from "UV_TEST_AWS") + registry_name = item_title.removeprefix("UV_TEST_") + + # Get the item details + item_details = subprocess.run( + ["op", "item", "get", item_id, "--format", "json"], + capture_output=True, + text=True, + check=True, + ) + + item_data = json.loads(item_details.stdout) + + username = None + password = None + url = None + pkg = None + + if "fields" in item_data: + for field in item_data["fields"]: + if field.get("id") == "username": + username = field.get("value") + elif field.get("id") == "password": + password = field.get("value") + elif field.get("label") == "url": + url = field.get("value") + elif field.get("label") == "pkg": + pkg = field.get("value") + if username: + updated_env[f"UV_TEST_{registry_name}_USERNAME"] = username + if password: + updated_env[f"UV_TEST_{registry_name}_TOKEN"] = password + if url: + updated_env[f"UV_TEST_{registry_name}_URL"] = url + if pkg: + updated_env[f"UV_TEST_{registry_name}_PKG"] = pkg + + print(f"Added 1Password credentials for {registry_name}") + + return updated_env + + +def get_registries(env: Dict[str, str]) -> Dict[str, str]: + pattern = re.compile(r"^UV_TEST_(.+)_URL$") + registries: Dict[str, str] = {} + + for env_var, value in env.items(): + match = pattern.match(env_var) + if match: + registry_name = match.group(1).lower() + registries[registry_name] = value + + return registries + + +def setup_test_project( + registry_name: str, registry_url: str, project_dir: str, requires_python: str +): + """Create a temporary project directory with a pyproject.toml""" + pyproject_content = f"""[project] +name = "{registry_name}-test" +version = "0.1.0" +description = "Test registry" +requires-python = ">={requires_python}" + +[[tool.uv.index]] +name = "{registry_name}" +url = "{registry_url}" +default = true +""" + pyproject_file = Path(project_dir) / "pyproject.toml" + pyproject_file.write_text(pyproject_content) + + +def run_test( + env: dict[str, str], + uv: Path, + registry_name: str, + registry_url: str, + package: str, + username: str, + token: str, + verbosity: int, + timeout: int, + requires_python: str, +) -> bool: + print(uv) + """Attempt to install a package from this registry.""" + print( + f"{registry_name} -- Running test for {registry_url} with username {username}" + ) + if package == DEFAULT_PKG_NAME: + print( + f"** Using default test package name: {package}. To choose a different package, set UV_TEST_{registry_name.upper()}_PKG" + ) + print(f"\nAttempting to install {package}") + env[f"UV_INDEX_{registry_name.upper()}_USERNAME"] = username + env[f"UV_INDEX_{registry_name.upper()}_PASSWORD"] = token + + with tempfile.TemporaryDirectory() as project_dir: + setup_test_project(registry_name, registry_url, project_dir, requires_python) + + cmd = [ + uv, + "add", + package, + "--directory", + project_dir, + ] + if verbosity: + cmd.extend(["-" + "v" * verbosity]) + + result = None + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + timeout=timeout, + check=False, + env=env, + ) + + if result.returncode != 0: + error_msg = result.stderr.strip() if result.stderr else "Unknown error" + print(f"{Fore.RED}{registry_name}: FAIL{Fore.RESET} \n\n{error_msg}") + return False + + success = False + for line in result.stderr.strip().split("\n"): + if line.startswith(f" + {package}=="): + success = True + if success: + print(f"{Fore.GREEN}{registry_name}: PASS") + if verbosity > 0: + print(f" stdout: {result.stdout.strip()}") + print(f" stderr: {result.stderr.strip()}") + return True + else: + print( + f"{Fore.RED}{registry_name}: FAIL{Fore.RESET} - Failed to install {package}." + ) + + except subprocess.TimeoutExpired: + print(f"{Fore.RED}{registry_name}: TIMEOUT{Fore.RESET} (>{timeout}s)") + except FileNotFoundError: + print(f"{Fore.RED}{registry_name}: ERROR{Fore.RESET} - uv not found") + except Exception as e: + print(f"{Fore.RED}{registry_name}: ERROR{Fore.RESET} - {e}") + + if result: + if result.stdout: + print(f"{Fore.RED} stdout:{Fore.RESET} {result.stdout.strip()}") + if result.stderr: + print(f"\n{Fore.RED} stderr:{Fore.RESET} {result.stderr.strip()}") + return False + + +def parse_args() -> argparse.Namespace: + """Parse command line arguments""" + parser = argparse.ArgumentParser( + description="Test uv add command against multiple registries", + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument( + "--all", + action="store_true", + help="fail if any known registry was not tested", + ) + parser.add_argument( + "--uv", + type=str, + help="specify a path to the uv binary (default: uv command)", + ) + parser.add_argument( + "--timeout", + type=int, + default=os.environ.get("UV_TEST_TIMEOUT", DEFAULT_TIMEOUT), + help=f"timeout in seconds for each test (default: {DEFAULT_TIMEOUT} or UV_TEST_TIMEOUT)", + ) + parser.add_argument( + "-v", + "--verbose", + action="count", + default=0, + help="increase verbosity (-v for debug, -vv for trace)", + ) + parser.add_argument( + "--use-op", + action="store_true", + help="use 1Password CLI to fetch registry credentials from the specified vault", + ) + parser.add_argument( + "--op-vault", + type=str, + default="RegistryTests", + help="name of the 1Password vault to use (default: RegistryTests)", + ) + parser.add_argument( + "--required-python", + type=str, + default="3.12", + help="minimum Python version for tests (default: 3.12)", + ) + parser.add_argument("--color", choices=["always", "auto", "never"], default="auto") + return parser.parse_args() + + +def main() -> None: + args = parse_args() + env = os.environ.copy() + + if args.color == "always": + initialize_colorama(force_color=True) + elif args.color == "never": + initialize_colorama(force_color=False) + else: + initialize_colorama(force_color=sys.stdout.isatty()) + + # If using 1Password, fetch credentials from the vault + if args.use_op: + print(f"Fetching credentials from 1Password vault '{args.op_vault}'...") + try: + env = fetch_op_items(args.op_vault, env) + except Exception as e: + print(f"{Fore.RED}Error accessing 1Password: {e}{Fore.RESET}") + print( + f"{Fore.YELLOW}Hint: If you're not authenticated, run 'op signin' first.{Fore.RESET}" + ) + sys.exit(1) + + if args.uv: + # We change the working directory for the subprocess calls, so we have to + # absolutize the path. + uv = Path.cwd().joinpath(args.uv) + else: + subprocess.run(["cargo", "build"]) + executable_suffix = ".exe" if os.name == "nt" else "" + uv = cwd.parent.joinpath(f"target/debug/uv{executable_suffix}") + + passed = [] + failed = [] + skipped = [] + untested_registries = set(KNOWN_REGISTRIES) + + print("Running tests...") + for registry_name, registry_url in get_registries(env).items(): + print("----------------") + + token = env.get(f"UV_TEST_{registry_name.upper()}_TOKEN") + if not token: + if args.all: + print( + f"{Fore.RED}{registry_name}: UV_TEST_{registry_name.upper()}_TOKEN contained no token. Required by --all" + ) + failed.append(registry_name) + else: + print( + f"{Fore.YELLOW}{registry_name}: UV_TEST_{registry_name.upper()}_TOKEN contained no token. Skipping test" + ) + skipped.append(registry_name) + continue + + # The private package we will test installing + package = env.get(f"UV_TEST_{registry_name.upper()}_PKG", DEFAULT_PKG_NAME) + username = env.get(f"UV_TEST_{registry_name.upper()}_USERNAME", "__token__") + + if run_test( + env, + uv, + registry_name, + registry_url, + package, + username, + token, + args.verbose, + args.timeout, + args.required_python, + ): + passed.append(registry_name) + else: + failed.append(registry_name) + + untested_registries.remove(registry_name) + + total = len(passed) + len(failed) + + print("----------------") + if passed: + print(f"\n{Fore.GREEN}Passed:") + for registry_name in passed: + print(f" * {registry_name}") + if failed: + print(f"\n{Fore.RED}Failed:") + for registry_name in failed: + print(f" * {registry_name}") + if skipped: + print(f"\n{Fore.YELLOW}Skipped:") + for registry_name in skipped: + print(f" * {registry_name}") + + print(f"\nResults: {len(passed)}/{total} tests passed, {len(skipped)} skipped") + + if args.all and len(untested_registries) > 0: + print( + f"\n{Fore.RED}Failed to test all known registries (requested via --all).{Fore.RESET}\nMissing:" + ) + for registry_name in untested_registries: + print(f" * {registry_name}") + print("You must use the exact registry name as listed here") + sys.exit(1) + + if total == 0: + print("\nNo tests were run - have you defined at least one registry?") + print(" * UV_TEST__URL") + print(" * UV_TEST__TOKEN") + print( + " * UV_TEST__PKG (the private package to test installing)" + ) + print(' * UV_TEST__USERNAME (defaults to "__token__")') + sys.exit(1) + + sys.exit(0 if len(failed) == 0 else 1) + + +if __name__ == "__main__": + main() From 499c8aa808b0a245d498b7ce6fa6f1f68a99251f Mon Sep 17 00:00:00 2001 From: konsti Date: Wed, 18 Jun 2025 09:51:53 +0200 Subject: [PATCH 013/349] Fix PyPI publish test script (#14116) The script stumbled over a newline introduced in https://github.com/pypi/warehouse/pull/18266 (which is valid). Also fixed: Don't read versions for the same package from other indexes. We were using `project_name` here instead of `target`, while using the latter and only reading from a single index simplifies the code too. --- scripts/publish/test_publish.py | 58 ++++++++++++++++----------------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/scripts/publish/test_publish.py b/scripts/publish/test_publish.py index 620a08e61..c2c35fe90 100644 --- a/scripts/publish/test_publish.py +++ b/scripts/publish/test_publish.py @@ -163,39 +163,37 @@ all_targets: dict[str, TargetConfiguration] = local_targets | { } -def get_latest_version(project_name: str, client: httpx.Client) -> Version: +def get_latest_version(target: str, client: httpx.Client) -> Version: """Return the latest version on all indexes of the package.""" # To keep the number of packages small we reuse them across targets, so we have to # pick a version that doesn't exist on any target yet versions = set() - for target_config in all_targets.values(): - if target_config.project_name != project_name: - continue - url = target_config.index_url + project_name + "/" + target_config = all_targets[target] + url = target_config.index_url + target_config.project_name + "/" - # Get with retries - error = None - for _ in range(5): - try: - versions.update(collect_versions(url, client)) - break - except httpx.HTTPError as err: - error = err - print( - f"Error getting version for {project_name}, sleeping for 1s: {err}", - file=sys.stderr, - ) - time.sleep(1) - except InvalidSdistFilename as err: - # Sometimes there's a link that says "status page" - error = err - print( - f"Invalid index page for {project_name}, sleeping for 1s: {err}", - file=sys.stderr, - ) - time.sleep(1) - else: - raise RuntimeError(f"Failed to fetch {url}") from error + # Get with retries + error = None + for _ in range(5): + try: + versions.update(collect_versions(url, client)) + break + except httpx.HTTPError as err: + error = err + print( + f"Error getting version for {target_config.project_name}, sleeping for 1s: {err}", + file=sys.stderr, + ) + time.sleep(1) + except InvalidSdistFilename as err: + # Sometimes there's a link that says "status page" + error = err + print( + f"Invalid index page for {target_config.project_name}, sleeping for 1s: {err}", + file=sys.stderr, + ) + time.sleep(1) + else: + raise RuntimeError(f"Failed to fetch {url}") from error return max(versions) @@ -223,7 +221,7 @@ def get_filenames(url: str, client: httpx.Client) -> list[str]: response = client.get(url) data = response.text # Works for the indexes in the list - href_text = r"([^<>]+)" + href_text = r"([^<>]+)" return [m.group(1) for m in re.finditer(href_text, data)] @@ -363,7 +361,7 @@ def publish_project(target: str, uv: Path, client: httpx.Client): print(f"\nPublish {project_name} for {target}", file=sys.stderr) # The distributions are build to the dist directory of the project. - previous_version = get_latest_version(project_name, client) + previous_version = get_latest_version(target, client) version = get_new_version(previous_version) project_dir = build_project_at_version(target, version, uv) From 0cac73dc1f8d56e9a2440a28ab863d1dc7398c97 Mon Sep 17 00:00:00 2001 From: Aaron Ang <67321817+aaron-ang@users.noreply.github.com> Date: Wed, 18 Jun 2025 01:48:21 -0700 Subject: [PATCH 014/349] Warn on empty index directory (#13940) Close #13922 ## Summary Add a warning if the directory given by the `--index` argument is empty. ## Test Plan Added test case `add_index_empty_directory` in `edit.rs` --- crates/uv-distribution-types/src/index_url.rs | 39 ++++++++----------- crates/uv/src/commands/project/add.rs | 11 +++++- crates/uv/tests/it/edit.rs | 32 +++++++++++++++ 3 files changed, 57 insertions(+), 25 deletions(-) diff --git a/crates/uv-distribution-types/src/index_url.rs b/crates/uv-distribution-types/src/index_url.rs index 9604fbd30..a523b4811 100644 --- a/crates/uv-distribution-types/src/index_url.rs +++ b/crates/uv-distribution-types/src/index_url.rs @@ -511,30 +511,23 @@ impl<'a> IndexUrls { /// iterator. pub fn defined_indexes(&'a self) -> impl Iterator + 'a { if self.no_index { - Either::Left(std::iter::empty()) - } else { - Either::Right( - { - let mut seen = FxHashSet::default(); - self.indexes - .iter() - .filter(move |index| { - index.name.as_ref().is_none_or(|name| seen.insert(name)) - }) - .filter(|index| !index.default) - } - .chain({ - let mut seen = FxHashSet::default(); - self.indexes - .iter() - .filter(move |index| { - index.name.as_ref().is_none_or(|name| seen.insert(name)) - }) - .find(|index| index.default) - .into_iter() - }), - ) + return Either::Left(std::iter::empty()); } + + let mut seen = FxHashSet::default(); + let (non_default, default) = self + .indexes + .iter() + .filter(move |index| { + if let Some(name) = &index.name { + seen.insert(name) + } else { + true + } + }) + .partition::, _>(|index| !index.default); + + Either::Right(non_default.into_iter().chain(default)) } /// Return the `--no-index` flag. diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index 8e3c4a03a..1c5297f90 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -512,8 +512,9 @@ pub(crate) async fn add( )?; // Validate any indexes that were provided on the command-line to ensure - // they point to existing directories when using path URLs. - for index in &indexes { + // they point to existing non-empty directories when using path URLs. + let mut valid_indexes = Vec::with_capacity(indexes.len()); + for index in indexes { if let IndexUrl::Path(url) = &index.url { let path = url .to_file_path() @@ -521,8 +522,14 @@ pub(crate) async fn add( if !path.is_dir() { bail!("Directory not found for index: {url}"); } + if fs_err::read_dir(&path)?.next().is_none() { + warn_user_once!("Index directory `{url}` is empty, skipping"); + continue; + } } + valid_indexes.push(index); } + let indexes = valid_indexes; // Add any indexes that were provided on the command-line, in priority order. if !raw { diff --git a/crates/uv/tests/it/edit.rs b/crates/uv/tests/it/edit.rs index a66cd2ed5..68eae5110 100644 --- a/crates/uv/tests/it/edit.rs +++ b/crates/uv/tests/it/edit.rs @@ -9434,6 +9434,38 @@ fn add_index_with_non_existent_relative_path_with_same_name_as_index() -> Result Ok(()) } +#[test] +fn add_index_empty_directory() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + "#})?; + + let packages = context.temp_dir.child("test-index"); + packages.create_dir_all()?; + + uv_snapshot!(context.filters(), context.add().arg("iniconfig").arg("--index").arg("test-index"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: Index directory `file://[TEMP_DIR]/test-index` is empty, skipping + Resolved 2 packages in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + iniconfig==2.0.0 + "); + + Ok(()) +} + /// Add a PyPI requirement. #[test] fn add_group_comment() -> Result<()> { From 4d9c9a1e76bb53c6f04ca55b6032e393b1ac42bb Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Wed, 18 Jun 2025 07:35:05 -0400 Subject: [PATCH 015/349] Add ROCm backends to `--torch-backend` (#14120) We don't yet support automatic detection, but this at least allows explicit selection (e.g., `uv pip install --torch-backend rocm5.3`). Closes #14087. --- clippy.toml | 1 + crates/uv-resolver/src/resolver/system.rs | 16 +- crates/uv-torch/src/backend.rs | 226 +++++++++++++++++++++- docs/reference/cli.md | 48 +++++ uv.schema.json | 112 +++++++++++ 5 files changed, 397 insertions(+), 6 deletions(-) diff --git a/clippy.toml b/clippy.toml index 191195e33..bb1f365b5 100644 --- a/clippy.toml +++ b/clippy.toml @@ -6,6 +6,7 @@ doc-valid-idents = [ "GraalPy", "ReFS", "PyTorch", + "ROCm", ".." # Include the defaults ] diff --git a/crates/uv-resolver/src/resolver/system.rs b/crates/uv-resolver/src/resolver/system.rs index 806b1c01c..a47000846 100644 --- a/crates/uv-resolver/src/resolver/system.rs +++ b/crates/uv-resolver/src/resolver/system.rs @@ -23,11 +23,17 @@ impl SystemDependency { /// For example, given `https://download.pytorch.org/whl/cu124`, returns CUDA 12.4. pub(super) fn from_index(index: &DisplaySafeUrl) -> Option { let backend = TorchBackend::from_index(index)?; - let cuda_version = backend.cuda_version()?; - Some(Self { - name: PackageName::from_str("cuda").unwrap(), - version: cuda_version, - }) + if let Some(cuda_version) = backend.cuda_version() { + Some(Self { + name: PackageName::from_str("cuda").unwrap(), + version: cuda_version, + }) + } else { + backend.rocm_version().map(|rocm_version| Self { + name: PackageName::from_str("rocm").unwrap(), + version: rocm_version, + }) + } } } diff --git a/crates/uv-torch/src/backend.rs b/crates/uv-torch/src/backend.rs index 0df5bd844..263ea07bd 100644 --- a/crates/uv-torch/src/backend.rs +++ b/crates/uv-torch/src/backend.rs @@ -35,7 +35,6 @@ //! OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE //! OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //! ``` -//! use std::str::FromStr; use std::sync::LazyLock; @@ -108,6 +107,70 @@ pub enum TorchMode { Cu90, /// Use the PyTorch index for CUDA 8.0. Cu80, + /// Use the PyTorch index for ROCm 6.3. + #[serde(rename = "rocm6.3")] + #[clap(name = "rocm6.3")] + Rocm63, + /// Use the PyTorch index for ROCm 6.2.4. + #[serde(rename = "rocm6.2.4")] + #[clap(name = "rocm6.2.4")] + Rocm624, + /// Use the PyTorch index for ROCm 6.2. + #[serde(rename = "rocm6.2")] + #[clap(name = "rocm6.2")] + Rocm62, + /// Use the PyTorch index for ROCm 6.1. + #[serde(rename = "rocm6.1")] + #[clap(name = "rocm6.1")] + Rocm61, + /// Use the PyTorch index for ROCm 6.0. + #[serde(rename = "rocm6.0")] + #[clap(name = "rocm6.0")] + Rocm60, + /// Use the PyTorch index for ROCm 5.7. + #[serde(rename = "rocm5.7")] + #[clap(name = "rocm5.7")] + Rocm57, + /// Use the PyTorch index for ROCm 5.6. + #[serde(rename = "rocm5.6")] + #[clap(name = "rocm5.6")] + Rocm56, + /// Use the PyTorch index for ROCm 5.5. + #[serde(rename = "rocm5.5")] + #[clap(name = "rocm5.5")] + Rocm55, + /// Use the PyTorch index for ROCm 5.4.2. + #[serde(rename = "rocm5.4.2")] + #[clap(name = "rocm5.4.2")] + Rocm542, + /// Use the PyTorch index for ROCm 5.4. + #[serde(rename = "rocm5.4")] + #[clap(name = "rocm5.4")] + Rocm54, + /// Use the PyTorch index for ROCm 5.3. + #[serde(rename = "rocm5.3")] + #[clap(name = "rocm5.3")] + Rocm53, + /// Use the PyTorch index for ROCm 5.2. + #[serde(rename = "rocm5.2")] + #[clap(name = "rocm5.2")] + Rocm52, + /// Use the PyTorch index for ROCm 5.1.1. + #[serde(rename = "rocm5.1.1")] + #[clap(name = "rocm5.1.1")] + Rocm511, + /// Use the PyTorch index for ROCm 4.2. + #[serde(rename = "rocm4.2")] + #[clap(name = "rocm4.2")] + Rocm42, + /// Use the PyTorch index for ROCm 4.1. + #[serde(rename = "rocm4.1")] + #[clap(name = "rocm4.1")] + Rocm41, + /// Use the PyTorch index for ROCm 4.0.1. + #[serde(rename = "rocm4.0.1")] + #[clap(name = "rocm4.0.1")] + Rocm401, } /// The strategy to use when determining the appropriate PyTorch index. @@ -158,6 +221,22 @@ impl TorchStrategy { TorchMode::Cu91 => Ok(Self::Backend(TorchBackend::Cu91)), TorchMode::Cu90 => Ok(Self::Backend(TorchBackend::Cu90)), TorchMode::Cu80 => Ok(Self::Backend(TorchBackend::Cu80)), + TorchMode::Rocm63 => Ok(Self::Backend(TorchBackend::Rocm63)), + TorchMode::Rocm624 => Ok(Self::Backend(TorchBackend::Rocm624)), + TorchMode::Rocm62 => Ok(Self::Backend(TorchBackend::Rocm62)), + TorchMode::Rocm61 => Ok(Self::Backend(TorchBackend::Rocm61)), + TorchMode::Rocm60 => Ok(Self::Backend(TorchBackend::Rocm60)), + TorchMode::Rocm57 => Ok(Self::Backend(TorchBackend::Rocm57)), + TorchMode::Rocm56 => Ok(Self::Backend(TorchBackend::Rocm56)), + TorchMode::Rocm55 => Ok(Self::Backend(TorchBackend::Rocm55)), + TorchMode::Rocm542 => Ok(Self::Backend(TorchBackend::Rocm542)), + TorchMode::Rocm54 => Ok(Self::Backend(TorchBackend::Rocm54)), + TorchMode::Rocm53 => Ok(Self::Backend(TorchBackend::Rocm53)), + TorchMode::Rocm52 => Ok(Self::Backend(TorchBackend::Rocm52)), + TorchMode::Rocm511 => Ok(Self::Backend(TorchBackend::Rocm511)), + TorchMode::Rocm42 => Ok(Self::Backend(TorchBackend::Rocm42)), + TorchMode::Rocm41 => Ok(Self::Backend(TorchBackend::Rocm41)), + TorchMode::Rocm401 => Ok(Self::Backend(TorchBackend::Rocm401)), } } @@ -177,6 +256,8 @@ impl TorchStrategy { | "torchtext" | "torchvision" | "pytorch-triton" + | "pytorch-triton-rocm" + | "pytorch-triton-xpu" ) } @@ -259,6 +340,22 @@ pub enum TorchBackend { Cu91, Cu90, Cu80, + Rocm63, + Rocm624, + Rocm62, + Rocm61, + Rocm60, + Rocm57, + Rocm56, + Rocm55, + Rocm542, + Rocm54, + Rocm53, + Rocm52, + Rocm511, + Rocm42, + Rocm41, + Rocm401, } impl TorchBackend { @@ -290,6 +387,22 @@ impl TorchBackend { Self::Cu91 => &CU91_INDEX_URL, Self::Cu90 => &CU90_INDEX_URL, Self::Cu80 => &CU80_INDEX_URL, + Self::Rocm63 => &ROCM63_INDEX_URL, + Self::Rocm624 => &ROCM624_INDEX_URL, + Self::Rocm62 => &ROCM62_INDEX_URL, + Self::Rocm61 => &ROCM61_INDEX_URL, + Self::Rocm60 => &ROCM60_INDEX_URL, + Self::Rocm57 => &ROCM57_INDEX_URL, + Self::Rocm56 => &ROCM56_INDEX_URL, + Self::Rocm55 => &ROCM55_INDEX_URL, + Self::Rocm542 => &ROCM542_INDEX_URL, + Self::Rocm54 => &ROCM54_INDEX_URL, + Self::Rocm53 => &ROCM53_INDEX_URL, + Self::Rocm52 => &ROCM52_INDEX_URL, + Self::Rocm511 => &ROCM511_INDEX_URL, + Self::Rocm42 => &ROCM42_INDEX_URL, + Self::Rocm41 => &ROCM41_INDEX_URL, + Self::Rocm401 => &ROCM401_INDEX_URL, } } @@ -336,6 +449,69 @@ impl TorchBackend { TorchBackend::Cu91 => Some(Version::new([9, 1])), TorchBackend::Cu90 => Some(Version::new([9, 0])), TorchBackend::Cu80 => Some(Version::new([8, 0])), + TorchBackend::Rocm63 => None, + TorchBackend::Rocm624 => None, + TorchBackend::Rocm62 => None, + TorchBackend::Rocm61 => None, + TorchBackend::Rocm60 => None, + TorchBackend::Rocm57 => None, + TorchBackend::Rocm56 => None, + TorchBackend::Rocm55 => None, + TorchBackend::Rocm542 => None, + TorchBackend::Rocm54 => None, + TorchBackend::Rocm53 => None, + TorchBackend::Rocm52 => None, + TorchBackend::Rocm511 => None, + TorchBackend::Rocm42 => None, + TorchBackend::Rocm41 => None, + TorchBackend::Rocm401 => None, + } + } + + /// Returns the ROCM [`Version`] for the given [`TorchBackend`]. + pub fn rocm_version(&self) -> Option { + match self { + TorchBackend::Cpu => None, + TorchBackend::Cu128 => None, + TorchBackend::Cu126 => None, + TorchBackend::Cu125 => None, + TorchBackend::Cu124 => None, + TorchBackend::Cu123 => None, + TorchBackend::Cu122 => None, + TorchBackend::Cu121 => None, + TorchBackend::Cu120 => None, + TorchBackend::Cu118 => None, + TorchBackend::Cu117 => None, + TorchBackend::Cu116 => None, + TorchBackend::Cu115 => None, + TorchBackend::Cu114 => None, + TorchBackend::Cu113 => None, + TorchBackend::Cu112 => None, + TorchBackend::Cu111 => None, + TorchBackend::Cu110 => None, + TorchBackend::Cu102 => None, + TorchBackend::Cu101 => None, + TorchBackend::Cu100 => None, + TorchBackend::Cu92 => None, + TorchBackend::Cu91 => None, + TorchBackend::Cu90 => None, + TorchBackend::Cu80 => None, + TorchBackend::Rocm63 => Some(Version::new([6, 3])), + TorchBackend::Rocm624 => Some(Version::new([6, 2, 4])), + TorchBackend::Rocm62 => Some(Version::new([6, 2])), + TorchBackend::Rocm61 => Some(Version::new([6, 1])), + TorchBackend::Rocm60 => Some(Version::new([6, 0])), + TorchBackend::Rocm57 => Some(Version::new([5, 7])), + TorchBackend::Rocm56 => Some(Version::new([5, 6])), + TorchBackend::Rocm55 => Some(Version::new([5, 5])), + TorchBackend::Rocm542 => Some(Version::new([5, 4, 2])), + TorchBackend::Rocm54 => Some(Version::new([5, 4])), + TorchBackend::Rocm53 => Some(Version::new([5, 3])), + TorchBackend::Rocm52 => Some(Version::new([5, 2])), + TorchBackend::Rocm511 => Some(Version::new([5, 1, 1])), + TorchBackend::Rocm42 => Some(Version::new([4, 2])), + TorchBackend::Rocm41 => Some(Version::new([4, 1])), + TorchBackend::Rocm401 => Some(Version::new([4, 0, 1])), } } } @@ -370,6 +546,22 @@ impl FromStr for TorchBackend { "cu91" => Ok(TorchBackend::Cu91), "cu90" => Ok(TorchBackend::Cu90), "cu80" => Ok(TorchBackend::Cu80), + "rocm6.3" => Ok(TorchBackend::Rocm63), + "rocm6.2.4" => Ok(TorchBackend::Rocm624), + "rocm6.2" => Ok(TorchBackend::Rocm62), + "rocm6.1" => Ok(TorchBackend::Rocm61), + "rocm6.0" => Ok(TorchBackend::Rocm60), + "rocm5.7" => Ok(TorchBackend::Rocm57), + "rocm5.6" => Ok(TorchBackend::Rocm56), + "rocm5.5" => Ok(TorchBackend::Rocm55), + "rocm5.4.2" => Ok(TorchBackend::Rocm542), + "rocm5.4" => Ok(TorchBackend::Rocm54), + "rocm5.3" => Ok(TorchBackend::Rocm53), + "rocm5.2" => Ok(TorchBackend::Rocm52), + "rocm5.1.1" => Ok(TorchBackend::Rocm511), + "rocm4.2" => Ok(TorchBackend::Rocm42), + "rocm4.1" => Ok(TorchBackend::Rocm41), + "rocm4.0.1" => Ok(TorchBackend::Rocm401), _ => Err(format!("Unknown PyTorch backend: {s}")), } } @@ -501,3 +693,35 @@ static CU90_INDEX_URL: LazyLock = LazyLock::new(|| IndexUrl::from_str("https://download.pytorch.org/whl/cu90").unwrap()); static CU80_INDEX_URL: LazyLock = LazyLock::new(|| IndexUrl::from_str("https://download.pytorch.org/whl/cu80").unwrap()); +static ROCM63_INDEX_URL: LazyLock = + LazyLock::new(|| IndexUrl::from_str("https://download.pytorch.org/whl/rocm6.3").unwrap()); +static ROCM624_INDEX_URL: LazyLock = + LazyLock::new(|| IndexUrl::from_str("https://download.pytorch.org/whl/rocm6.2.4").unwrap()); +static ROCM62_INDEX_URL: LazyLock = + LazyLock::new(|| IndexUrl::from_str("https://download.pytorch.org/whl/rocm6.2").unwrap()); +static ROCM61_INDEX_URL: LazyLock = + LazyLock::new(|| IndexUrl::from_str("https://download.pytorch.org/whl/rocm6.1").unwrap()); +static ROCM60_INDEX_URL: LazyLock = + LazyLock::new(|| IndexUrl::from_str("https://download.pytorch.org/whl/rocm6.0").unwrap()); +static ROCM57_INDEX_URL: LazyLock = + LazyLock::new(|| IndexUrl::from_str("https://download.pytorch.org/whl/rocm5.7").unwrap()); +static ROCM56_INDEX_URL: LazyLock = + LazyLock::new(|| IndexUrl::from_str("https://download.pytorch.org/whl/rocm5.6").unwrap()); +static ROCM55_INDEX_URL: LazyLock = + LazyLock::new(|| IndexUrl::from_str("https://download.pytorch.org/whl/rocm5.5").unwrap()); +static ROCM542_INDEX_URL: LazyLock = + LazyLock::new(|| IndexUrl::from_str("https://download.pytorch.org/whl/rocm5.4.2").unwrap()); +static ROCM54_INDEX_URL: LazyLock = + LazyLock::new(|| IndexUrl::from_str("https://download.pytorch.org/whl/rocm5.4").unwrap()); +static ROCM53_INDEX_URL: LazyLock = + LazyLock::new(|| IndexUrl::from_str("https://download.pytorch.org/whl/rocm5.3").unwrap()); +static ROCM52_INDEX_URL: LazyLock = + LazyLock::new(|| IndexUrl::from_str("https://download.pytorch.org/whl/rocm5.2").unwrap()); +static ROCM511_INDEX_URL: LazyLock = + LazyLock::new(|| IndexUrl::from_str("https://download.pytorch.org/whl/rocm5.1.1").unwrap()); +static ROCM42_INDEX_URL: LazyLock = + LazyLock::new(|| IndexUrl::from_str("https://download.pytorch.org/whl/rocm4.2").unwrap()); +static ROCM41_INDEX_URL: LazyLock = + LazyLock::new(|| IndexUrl::from_str("https://download.pytorch.org/whl/rocm4.1").unwrap()); +static ROCM401_INDEX_URL: LazyLock = + LazyLock::new(|| IndexUrl::from_str("https://download.pytorch.org/whl/rocm4.0.1").unwrap()); diff --git a/docs/reference/cli.md b/docs/reference/cli.md index d434b954b..9ae05a8e0 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -3349,6 +3349,22 @@ by --python-version.

  • cu91: Use the PyTorch index for CUDA 9.1
  • cu90: Use the PyTorch index for CUDA 9.0
  • cu80: Use the PyTorch index for CUDA 8.0
  • +
  • rocm6.3: Use the PyTorch index for ROCm 6.3
  • +
  • rocm6.2.4: Use the PyTorch index for ROCm 6.2.4
  • +
  • rocm6.2: Use the PyTorch index for ROCm 6.2
  • +
  • rocm6.1: Use the PyTorch index for ROCm 6.1
  • +
  • rocm6.0: Use the PyTorch index for ROCm 6.0
  • +
  • rocm5.7: Use the PyTorch index for ROCm 5.7
  • +
  • rocm5.6: Use the PyTorch index for ROCm 5.6
  • +
  • rocm5.5: Use the PyTorch index for ROCm 5.5
  • +
  • rocm5.4.2: Use the PyTorch index for ROCm 5.4.2
  • +
  • rocm5.4: Use the PyTorch index for ROCm 5.4
  • +
  • rocm5.3: Use the PyTorch index for ROCm 5.3
  • +
  • rocm5.2: Use the PyTorch index for ROCm 5.2
  • +
  • rocm5.1.1: Use the PyTorch index for ROCm 5.1.1
  • +
  • rocm4.2: Use the PyTorch index for ROCm 4.2
  • +
  • rocm4.1: Use the PyTorch index for ROCm 4.1
  • +
  • rocm4.0.1: Use the PyTorch index for ROCm 4.0.1
  • --universal

    Perform a universal resolution, attempting to generate a single requirements.txt output file that is compatible with all operating systems, architectures, and Python implementations.

    In universal mode, the current Python version (or user-provided --python-version) will be treated as a lower bound. For example, --universal --python-version 3.7 would produce a universal resolution for Python 3.7 and later.

    Implies --no-strip-markers.

    @@ -3590,6 +3606,22 @@ be used with caution, as it can modify the system Python installation.

  • cu91: Use the PyTorch index for CUDA 9.1
  • cu90: Use the PyTorch index for CUDA 9.0
  • cu80: Use the PyTorch index for CUDA 8.0
  • +
  • rocm6.3: Use the PyTorch index for ROCm 6.3
  • +
  • rocm6.2.4: Use the PyTorch index for ROCm 6.2.4
  • +
  • rocm6.2: Use the PyTorch index for ROCm 6.2
  • +
  • rocm6.1: Use the PyTorch index for ROCm 6.1
  • +
  • rocm6.0: Use the PyTorch index for ROCm 6.0
  • +
  • rocm5.7: Use the PyTorch index for ROCm 5.7
  • +
  • rocm5.6: Use the PyTorch index for ROCm 5.6
  • +
  • rocm5.5: Use the PyTorch index for ROCm 5.5
  • +
  • rocm5.4.2: Use the PyTorch index for ROCm 5.4.2
  • +
  • rocm5.4: Use the PyTorch index for ROCm 5.4
  • +
  • rocm5.3: Use the PyTorch index for ROCm 5.3
  • +
  • rocm5.2: Use the PyTorch index for ROCm 5.2
  • +
  • rocm5.1.1: Use the PyTorch index for ROCm 5.1.1
  • +
  • rocm4.2: Use the PyTorch index for ROCm 4.2
  • +
  • rocm4.1: Use the PyTorch index for ROCm 4.1
  • +
  • rocm4.0.1: Use the PyTorch index for ROCm 4.0.1
  • --verbose, -v

    Use verbose output.

    You can configure fine-grained logging using the RUST_LOG environment variable. (https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives)

    @@ -3864,6 +3896,22 @@ should be used with caution, as it can modify the system Python installation.

    cu91: Use the PyTorch index for CUDA 9.1
  • cu90: Use the PyTorch index for CUDA 9.0
  • cu80: Use the PyTorch index for CUDA 8.0
  • +
  • rocm6.3: Use the PyTorch index for ROCm 6.3
  • +
  • rocm6.2.4: Use the PyTorch index for ROCm 6.2.4
  • +
  • rocm6.2: Use the PyTorch index for ROCm 6.2
  • +
  • rocm6.1: Use the PyTorch index for ROCm 6.1
  • +
  • rocm6.0: Use the PyTorch index for ROCm 6.0
  • +
  • rocm5.7: Use the PyTorch index for ROCm 5.7
  • +
  • rocm5.6: Use the PyTorch index for ROCm 5.6
  • +
  • rocm5.5: Use the PyTorch index for ROCm 5.5
  • +
  • rocm5.4.2: Use the PyTorch index for ROCm 5.4.2
  • +
  • rocm5.4: Use the PyTorch index for ROCm 5.4
  • +
  • rocm5.3: Use the PyTorch index for ROCm 5.3
  • +
  • rocm5.2: Use the PyTorch index for ROCm 5.2
  • +
  • rocm5.1.1: Use the PyTorch index for ROCm 5.1.1
  • +
  • rocm4.2: Use the PyTorch index for ROCm 4.2
  • +
  • rocm4.1: Use the PyTorch index for ROCm 4.1
  • +
  • rocm4.0.1: Use the PyTorch index for ROCm 4.0.1
  • --upgrade, -U

    Allow package upgrades, ignoring pinned versions in any existing output file. Implies --refresh

    --upgrade-package, -P upgrade-package

    Allow upgrades for a specific package, ignoring pinned versions in any existing output file. Implies --refresh-package

    --user
    --verbose, -v

    Use verbose output.

    diff --git a/uv.schema.json b/uv.schema.json index 33c1ff1f5..0d2b47490 100644 --- a/uv.schema.json +++ b/uv.schema.json @@ -2589,6 +2589,118 @@ "enum": [ "cu80" ] + }, + { + "description": "Use the PyTorch index for ROCm 6.3.", + "type": "string", + "enum": [ + "rocm6.3" + ] + }, + { + "description": "Use the PyTorch index for ROCm 6.2.4.", + "type": "string", + "enum": [ + "rocm6.2.4" + ] + }, + { + "description": "Use the PyTorch index for ROCm 6.2.", + "type": "string", + "enum": [ + "rocm6.2" + ] + }, + { + "description": "Use the PyTorch index for ROCm 6.1.", + "type": "string", + "enum": [ + "rocm6.1" + ] + }, + { + "description": "Use the PyTorch index for ROCm 6.0.", + "type": "string", + "enum": [ + "rocm6.0" + ] + }, + { + "description": "Use the PyTorch index for ROCm 5.7.", + "type": "string", + "enum": [ + "rocm5.7" + ] + }, + { + "description": "Use the PyTorch index for ROCm 5.6.", + "type": "string", + "enum": [ + "rocm5.6" + ] + }, + { + "description": "Use the PyTorch index for ROCm 5.5.", + "type": "string", + "enum": [ + "rocm5.5" + ] + }, + { + "description": "Use the PyTorch index for ROCm 5.4.2.", + "type": "string", + "enum": [ + "rocm5.4.2" + ] + }, + { + "description": "Use the PyTorch index for ROCm 5.4.", + "type": "string", + "enum": [ + "rocm5.4" + ] + }, + { + "description": "Use the PyTorch index for ROCm 5.3.", + "type": "string", + "enum": [ + "rocm5.3" + ] + }, + { + "description": "Use the PyTorch index for ROCm 5.2.", + "type": "string", + "enum": [ + "rocm5.2" + ] + }, + { + "description": "Use the PyTorch index for ROCm 5.1.1.", + "type": "string", + "enum": [ + "rocm5.1.1" + ] + }, + { + "description": "Use the PyTorch index for ROCm 4.2.", + "type": "string", + "enum": [ + "rocm4.2" + ] + }, + { + "description": "Use the PyTorch index for ROCm 4.1.", + "type": "string", + "enum": [ + "rocm4.1" + ] + }, + { + "description": "Use the PyTorch index for ROCm 4.0.1.", + "type": "string", + "enum": [ + "rocm4.0.1" + ] } ] }, From ee0ba65eb22c6bba59ea216ff07aeb170f8d3f2a Mon Sep 17 00:00:00 2001 From: konsti Date: Wed, 18 Jun 2025 15:06:09 +0200 Subject: [PATCH 016/349] Unify test venv `python` command creation (#14117) Refactoring in preparation for https://github.com/astral-sh/uv/pull/14080 --- crates/uv/tests/it/build.rs | 3 +- crates/uv/tests/it/build_backend.rs | 49 +- crates/uv/tests/it/common/mod.rs | 46 +- crates/uv/tests/it/pip_install.rs | 5 +- crates/uv/tests/it/pip_install_scenarios.rs | 764 ++++--------------- crates/uv/tests/it/pip_sync.rs | 42 +- crates/uv/tests/it/pip_uninstall.rs | 59 +- scripts/scenarios/templates/compile.mustache | 4 +- scripts/scenarios/templates/install.mustache | 49 +- scripts/scenarios/templates/lock.mustache | 2 +- 10 files changed, 212 insertions(+), 811 deletions(-) diff --git a/crates/uv/tests/it/build.rs b/crates/uv/tests/it/build.rs index 4fe7ca9cb..706c1a681 100644 --- a/crates/uv/tests/it/build.rs +++ b/crates/uv/tests/it/build.rs @@ -7,7 +7,6 @@ use indoc::indoc; use insta::assert_snapshot; use predicates::prelude::predicate; use std::env::current_dir; -use std::process::Command; use zip::ZipArchive; #[test] @@ -1857,7 +1856,7 @@ fn build_unconfigured_setuptools() -> Result<()> { + greet==0.1.0 (from file://[TEMP_DIR]/) "###); - uv_snapshot!(context.filters(), Command::new(context.interpreter()).arg("-c").arg("import greet"), @r###" + uv_snapshot!(context.filters(), context.python_command().arg("-c").arg("import greet"), @r###" success: true exit_code: 0 ----- stdout ----- diff --git a/crates/uv/tests/it/build_backend.rs b/crates/uv/tests/it/build_backend.rs index a806dc989..c2d99ba3e 100644 --- a/crates/uv/tests/it/build_backend.rs +++ b/crates/uv/tests/it/build_backend.rs @@ -50,13 +50,9 @@ fn built_by_uv_direct_wheel() -> Result<()> { .assert() .success(); - uv_snapshot!(context - .run() - .arg("python") + uv_snapshot!(context.python_command() .arg("-c") - .arg(BUILT_BY_UV_TEST_SCRIPT) - // Python on windows - .env(EnvVars::PYTHONUTF8, "1"), @r###" + .arg(BUILT_BY_UV_TEST_SCRIPT), @r###" success: true exit_code: 0 ----- stdout ----- @@ -138,13 +134,9 @@ fn built_by_uv_direct() -> Result<()> { drop(wheel_dir); - uv_snapshot!(context - .run() - .arg("python") + uv_snapshot!(context.python_command() .arg("-c") - .arg(BUILT_BY_UV_TEST_SCRIPT) - // Python on windows - .env(EnvVars::PYTHONUTF8, "1"), @r###" + .arg(BUILT_BY_UV_TEST_SCRIPT), @r###" success: true exit_code: 0 ----- stdout ----- @@ -169,7 +161,8 @@ fn built_by_uv_editable() -> Result<()> { // Without the editable, pytest fails. context.pip_install().arg("pytest").assert().success(); - Command::new(context.interpreter()) + context + .python_command() .arg("-m") .arg("pytest") .current_dir(built_by_uv) @@ -200,7 +193,7 @@ fn built_by_uv_editable() -> Result<()> { drop(wheel_dir); // Now, pytest passes. - uv_snapshot!(Command::new(context.interpreter()) + uv_snapshot!(context.python_command() .arg("-m") .arg("pytest") // Avoid showing absolute paths and column dependent layout @@ -340,11 +333,9 @@ fn rename_module() -> Result<()> { .success(); // Importing the module with the `module-name` name succeeds. - uv_snapshot!(Command::new(context.interpreter()) + uv_snapshot!(context.python_command() .arg("-c") - .arg("import bar") - // Python on windows - .env(EnvVars::PYTHONUTF8, "1"), @r###" + .arg("import bar"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -354,11 +345,9 @@ fn rename_module() -> Result<()> { "###); // Importing the package name fails, it was overridden by `module-name`. - uv_snapshot!(Command::new(context.interpreter()) + uv_snapshot!(context.python_command() .arg("-c") - .arg("import foo") - // Python on windows - .env(EnvVars::PYTHONUTF8, "1"), @r###" + .arg("import foo"), @r###" success: false exit_code: 1 ----- stdout ----- @@ -419,11 +408,9 @@ fn rename_module_editable_build() -> Result<()> { .success(); // Importing the module with the `module-name` name succeeds. - uv_snapshot!(Command::new(context.interpreter()) + uv_snapshot!(context.python_command() .arg("-c") - .arg("import bar") - // Python on windows - .env(EnvVars::PYTHONUTF8, "1"), @r###" + .arg("import bar"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -514,11 +501,9 @@ fn build_module_name_normalization() -> Result<()> { .assert() .success(); - uv_snapshot!(Command::new(context.interpreter()) + uv_snapshot!(context.python_command() .arg("-c") - .arg("import Django_plugin") - // Python on windows - .env(EnvVars::PYTHONUTF8, "1"), @r" + .arg("import Django_plugin"), @r" success: true exit_code: 0 ----- stdout ----- @@ -728,7 +713,7 @@ fn complex_namespace_packages() -> Result<()> { " ); - uv_snapshot!(Command::new(context.interpreter()) + uv_snapshot!(context.python_command() .arg("-c") .arg("from complex_project.part_b import two; print(two())"), @r" @@ -769,7 +754,7 @@ fn complex_namespace_packages() -> Result<()> { " ); - uv_snapshot!(Command::new(context.interpreter()) + uv_snapshot!(context.python_command() .arg("-c") .arg("from complex_project.part_b import two; print(two())"), @r" diff --git a/crates/uv/tests/it/common/mod.rs b/crates/uv/tests/it/common/mod.rs index 66eb21729..f997561a9 100644 --- a/crates/uv/tests/it/common/mod.rs +++ b/crates/uv/tests/it/common/mod.rs @@ -1085,15 +1085,30 @@ impl TestContext { } pub fn interpreter(&self) -> PathBuf { - venv_to_interpreter(&self.venv) + let venv = &self.venv; + if cfg!(unix) { + venv.join("bin").join("python") + } else if cfg!(windows) { + venv.join("Scripts").join("python.exe") + } else { + unimplemented!("Only Windows and Unix are supported") + } + } + + pub fn python_command(&self) -> Command { + let mut command = self.new_command_with(&self.interpreter()); + command + // Our tests change files in <1s, so we must disable CPython bytecode caching or we'll get stale files + // https://github.com/python/cpython/issues/75953 + .arg("-B") + // Python on windows + .env(EnvVars::PYTHONUTF8, "1"); + command } /// Run the given python code and check whether it succeeds. pub fn assert_command(&self, command: &str) -> Assert { - self.new_command_with(&venv_to_interpreter(&self.venv)) - // Our tests change files in <1s, so we must disable CPython bytecode caching or we'll get stale files - // https://github.com/python/cpython/issues/75953 - .arg("-B") + self.python_command() .arg("-c") .arg(command) .current_dir(&self.temp_dir) @@ -1102,10 +1117,7 @@ impl TestContext { /// Run the given python file and check whether it succeeds. pub fn assert_file(&self, file: impl AsRef) -> Assert { - self.new_command_with(&venv_to_interpreter(&self.venv)) - // Our tests change files in <1s, so we must disable CPython bytecode caching or we'll get stale files - // https://github.com/python/cpython/issues/75953 - .arg("-B") + self.python_command() .arg(file.as_ref()) .current_dir(&self.temp_dir) .assert() @@ -1120,6 +1132,12 @@ impl TestContext { .stdout(version); } + /// Assert a package is not installed. + pub fn assert_not_installed(&self, package: &'static str) { + self.assert_command(format!("import {package}").as_str()) + .failure(); + } + /// Generate various escaped regex patterns for the given path. pub fn path_patterns(path: impl AsRef) -> Vec { let mut patterns = Vec::new(); @@ -1347,16 +1365,6 @@ pub fn venv_bin_path(venv: impl AsRef) -> PathBuf { } } -pub fn venv_to_interpreter(venv: &Path) -> PathBuf { - if cfg!(unix) { - venv.join("bin").join("python") - } else if cfg!(windows) { - venv.join("Scripts").join("python.exe") - } else { - unimplemented!("Only Windows and Unix are supported") - } -} - /// Get the path to the python interpreter for a specific python version. pub fn get_python(version: &PythonVersion) -> PathBuf { ManagedPythonInstallations::from_settings(None) diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index 569edf00e..e0876b23c 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -19,7 +19,7 @@ use wiremock::{ use crate::common::{self, decode_token}; use crate::common::{ DEFAULT_PYTHON_VERSION, TestContext, build_vendor_links_url, download_to_disk, get_bin, - uv_snapshot, venv_bin_path, venv_to_interpreter, + uv_snapshot, venv_bin_path, }; use uv_fs::Simplified; use uv_static::EnvVars; @@ -9083,8 +9083,7 @@ fn build_tag() { ); // Ensure that we choose the highest build tag (5). - uv_snapshot!(Command::new(venv_to_interpreter(&context.venv)) - .arg("-B") + uv_snapshot!(context.python_command() .arg("-c") .arg("import build_tag; build_tag.main()") .current_dir(&context.temp_dir), @r###" diff --git a/crates/uv/tests/it/pip_install_scenarios.rs b/crates/uv/tests/it/pip_install_scenarios.rs index 1a95b1caa..153d5a8fb 100644 --- a/crates/uv/tests/it/pip_install_scenarios.rs +++ b/crates/uv/tests/it/pip_install_scenarios.rs @@ -5,52 +5,20 @@ //! #![cfg(all(feature = "python", feature = "pypi", unix))] -use std::path::Path; use std::process::Command; -use assert_cmd::assert::Assert; -use assert_cmd::prelude::*; - use uv_static::EnvVars; -use crate::common::{ - TestContext, build_vendor_links_url, get_bin, packse_index_url, uv_snapshot, - venv_to_interpreter, -}; - -fn assert_command(venv: &Path, command: &str, temp_dir: &Path) -> Assert { - Command::new(venv_to_interpreter(venv)) - .arg("-c") - .arg(command) - .current_dir(temp_dir) - .assert() -} - -fn assert_installed(venv: &Path, package: &'static str, version: &'static str, temp_dir: &Path) { - assert_command( - venv, - format!("import {package} as package; print(package.__version__, end='')").as_str(), - temp_dir, - ) - .success() - .stdout(version); -} - -fn assert_not_installed(venv: &Path, package: &'static str, temp_dir: &Path) { - assert_command(venv, format!("import {package}").as_str(), temp_dir).failure(); -} +use crate::common::{TestContext, build_vendor_links_url, packse_index_url, uv_snapshot}; /// Create a `pip install` command with options shared across all scenarios. fn command(context: &TestContext) -> Command { - let mut command = Command::new(get_bin()); + let mut command = context.pip_install(); command - .arg("pip") - .arg("install") .arg("--index-url") .arg(packse_index_url()) .arg("--find-links") .arg(build_vendor_links_url()); - context.add_shared_options(&mut command, true); command.env_remove(EnvVars::UV_EXCLUDE_NEWER); command } @@ -88,11 +56,7 @@ fn requires_exact_version_does_not_exist() { ╰─▶ Because there is no version of package-a==2.0.0 and you require package-a==2.0.0, we can conclude that your requirements are unsatisfiable. "); - assert_not_installed( - &context.venv, - "requires_exact_version_does_not_exist_a", - &context.temp_dir, - ); + context.assert_not_installed("requires_exact_version_does_not_exist_a"); } /// The user requires a version of `a` greater than `1.0.0` but only smaller or equal versions exist @@ -130,11 +94,7 @@ fn requires_greater_version_does_not_exist() { ╰─▶ Because only package-a<=1.0.0 is available and you require package-a>1.0.0, we can conclude that your requirements are unsatisfiable. "); - assert_not_installed( - &context.venv, - "requires_greater_version_does_not_exist_a", - &context.temp_dir, - ); + context.assert_not_installed("requires_greater_version_does_not_exist_a"); } /// The user requires a version of `a` less than `1.0.0` but only larger versions exist @@ -174,11 +134,7 @@ fn requires_less_version_does_not_exist() { ╰─▶ Because only package-a>=2.0.0 is available and you require package-a<2.0.0, we can conclude that your requirements are unsatisfiable. "); - assert_not_installed( - &context.venv, - "requires_less_version_does_not_exist_a", - &context.temp_dir, - ); + context.assert_not_installed("requires_less_version_does_not_exist_a"); } /// The user requires any version of package `a` which does not exist. @@ -211,11 +167,7 @@ fn requires_package_does_not_exist() { ╰─▶ Because package-a was not found in the package registry and you require package-a, we can conclude that your requirements are unsatisfiable. "); - assert_not_installed( - &context.venv, - "requires_package_does_not_exist_a", - &context.temp_dir, - ); + context.assert_not_installed("requires_package_does_not_exist_a"); } /// The user requires package `a` but `a` requires package `b` which does not exist @@ -254,11 +206,7 @@ fn transitive_requires_package_does_not_exist() { And because only package-a==1.0.0 is available and you require package-a, we can conclude that your requirements are unsatisfiable. "); - assert_not_installed( - &context.venv, - "transitive_requires_package_does_not_exist_a", - &context.temp_dir, - ); + context.assert_not_installed("transitive_requires_package_does_not_exist_a"); } /// There is a non-contiguous range of compatible versions for the requested package `a`, but another dependency `c` excludes the range. This is the same as `dependency-excludes-range-of-compatible-versions` but some of the versions of `a` are incompatible for another reason e.g. dependency on non-existent package `d`. @@ -376,21 +324,12 @@ fn dependency_excludes_non_contiguous_range_of_compatible_versions() { "); // Only the `2.x` versions of `a` are available since `a==1.0.0` and `a==3.0.0` require incompatible versions of `b`, but all available versions of `c` exclude that range of `a` so resolution fails. - assert_not_installed( - &context.venv, - "dependency_excludes_non_contiguous_range_of_compatible_versions_a", - &context.temp_dir, - ); - assert_not_installed( - &context.venv, - "dependency_excludes_non_contiguous_range_of_compatible_versions_b", - &context.temp_dir, - ); - assert_not_installed( - &context.venv, - "dependency_excludes_non_contiguous_range_of_compatible_versions_c", - &context.temp_dir, - ); + context + .assert_not_installed("dependency_excludes_non_contiguous_range_of_compatible_versions_a"); + context + .assert_not_installed("dependency_excludes_non_contiguous_range_of_compatible_versions_b"); + context + .assert_not_installed("dependency_excludes_non_contiguous_range_of_compatible_versions_c"); } /// There is a range of compatible versions for the requested package `a`, but another dependency `c` excludes that range. @@ -499,21 +438,9 @@ fn dependency_excludes_range_of_compatible_versions() { "); // Only the `2.x` versions of `a` are available since `a==1.0.0` and `a==3.0.0` require incompatible versions of `b`, but all available versions of `c` exclude that range of `a` so resolution fails. - assert_not_installed( - &context.venv, - "dependency_excludes_range_of_compatible_versions_a", - &context.temp_dir, - ); - assert_not_installed( - &context.venv, - "dependency_excludes_range_of_compatible_versions_b", - &context.temp_dir, - ); - assert_not_installed( - &context.venv, - "dependency_excludes_range_of_compatible_versions_c", - &context.temp_dir, - ); + context.assert_not_installed("dependency_excludes_range_of_compatible_versions_a"); + context.assert_not_installed("dependency_excludes_range_of_compatible_versions_b"); + context.assert_not_installed("dependency_excludes_range_of_compatible_versions_c"); } /// Only one version of the requested package `a` is compatible, but the user has banned that version. @@ -586,16 +513,8 @@ fn excluded_only_compatible_version() { "); // Only `a==1.2.0` is available since `a==1.0.0` and `a==3.0.0` require incompatible versions of `b`. The user has excluded that version of `a` so resolution fails. - assert_not_installed( - &context.venv, - "excluded_only_compatible_version_a", - &context.temp_dir, - ); - assert_not_installed( - &context.venv, - "excluded_only_compatible_version_b", - &context.temp_dir, - ); + context.assert_not_installed("excluded_only_compatible_version_a"); + context.assert_not_installed("excluded_only_compatible_version_b"); } /// Only one version of the requested package is available, but the user has banned that version. @@ -635,7 +554,7 @@ fn excluded_only_version() { "); // Only `a==1.0.0` is available but the user excluded it. - assert_not_installed(&context.venv, "excluded_only_version_a", &context.temp_dir); + context.assert_not_installed("excluded_only_version_a"); } /// Multiple optional dependencies are requested for the package via an 'all' extra. @@ -701,24 +620,9 @@ fn all_extras_required() { + package-c==1.0.0 "); - assert_installed( - &context.venv, - "all_extras_required_a", - "1.0.0", - &context.temp_dir, - ); - assert_installed( - &context.venv, - "all_extras_required_b", - "1.0.0", - &context.temp_dir, - ); - assert_installed( - &context.venv, - "all_extras_required_c", - "1.0.0", - &context.temp_dir, - ); + context.assert_installed("all_extras_required_a", "1.0.0"); + context.assert_installed("all_extras_required_b", "1.0.0"); + context.assert_installed("all_extras_required_c", "1.0.0"); } /// Optional dependencies are requested for the package, the extra is only available on an older version. @@ -771,12 +675,7 @@ fn extra_does_not_exist_backtrack() { "); // The resolver should not backtrack to `a==1.0.0` because missing extras are allowed during resolution. `b` should not be installed. - assert_installed( - &context.venv, - "extra_does_not_exist_backtrack_a", - "3.0.0", - &context.temp_dir, - ); + context.assert_installed("extra_does_not_exist_backtrack_a", "3.0.0"); } /// One of two incompatible optional dependencies are requested for the package. @@ -829,18 +728,8 @@ fn extra_incompatible_with_extra_not_requested() { "); // Because the user does not request both extras, it is okay that one is incompatible with the other. - assert_installed( - &context.venv, - "extra_incompatible_with_extra_not_requested_a", - "1.0.0", - &context.temp_dir, - ); - assert_installed( - &context.venv, - "extra_incompatible_with_extra_not_requested_b", - "2.0.0", - &context.temp_dir, - ); + context.assert_installed("extra_incompatible_with_extra_not_requested_a", "1.0.0"); + context.assert_installed("extra_incompatible_with_extra_not_requested_b", "2.0.0"); } /// Multiple optional dependencies are requested for the package, but they have conflicting requirements with each other. @@ -892,11 +781,7 @@ fn extra_incompatible_with_extra() { "); // Because both `extra_b` and `extra_c` are requested and they require incompatible versions of `b`, `a` cannot be installed. - assert_not_installed( - &context.venv, - "extra_incompatible_with_extra_a", - &context.temp_dir, - ); + context.assert_not_installed("extra_incompatible_with_extra_a"); } /// Optional dependencies are requested for the package, but the extra is not compatible with other requested versions. @@ -946,16 +831,8 @@ fn extra_incompatible_with_root() { "); // Because the user requested `b==2.0.0` but the requested extra requires `b==1.0.0`, the dependencies cannot be satisfied. - assert_not_installed( - &context.venv, - "extra_incompatible_with_root_a", - &context.temp_dir, - ); - assert_not_installed( - &context.venv, - "extra_incompatible_with_root_b", - &context.temp_dir, - ); + context.assert_not_installed("extra_incompatible_with_root_a"); + context.assert_not_installed("extra_incompatible_with_root_b"); } /// Optional dependencies are requested for the package. @@ -1001,18 +878,8 @@ fn extra_required() { + package-b==1.0.0 "); - assert_installed( - &context.venv, - "extra_required_a", - "1.0.0", - &context.temp_dir, - ); - assert_installed( - &context.venv, - "extra_required_b", - "1.0.0", - &context.temp_dir, - ); + context.assert_installed("extra_required_a", "1.0.0"); + context.assert_installed("extra_required_b", "1.0.0"); } /// Optional dependencies are requested for the package, but the extra does not exist. @@ -1052,7 +919,7 @@ fn missing_extra() { "); // Missing extras are ignored during resolution. - assert_installed(&context.venv, "missing_extra_a", "1.0.0", &context.temp_dir); + context.assert_installed("missing_extra_a", "1.0.0"); } /// Multiple optional dependencies are requested for the package. @@ -1106,24 +973,9 @@ fn multiple_extras_required() { + package-c==1.0.0 "); - assert_installed( - &context.venv, - "multiple_extras_required_a", - "1.0.0", - &context.temp_dir, - ); - assert_installed( - &context.venv, - "multiple_extras_required_b", - "1.0.0", - &context.temp_dir, - ); - assert_installed( - &context.venv, - "multiple_extras_required_c", - "1.0.0", - &context.temp_dir, - ); + context.assert_installed("multiple_extras_required_a", "1.0.0"); + context.assert_installed("multiple_extras_required_b", "1.0.0"); + context.assert_installed("multiple_extras_required_c", "1.0.0"); } /// The user requires two incompatible, existing versions of package `a` @@ -1164,16 +1016,8 @@ fn direct_incompatible_versions() { ╰─▶ Because you require package-a==1.0.0 and package-a==2.0.0, we can conclude that your requirements are unsatisfiable. "); - assert_not_installed( - &context.venv, - "direct_incompatible_versions_a", - &context.temp_dir, - ); - assert_not_installed( - &context.venv, - "direct_incompatible_versions_a", - &context.temp_dir, - ); + context.assert_not_installed("direct_incompatible_versions_a"); + context.assert_not_installed("direct_incompatible_versions_a"); } /// The user requires `a`, which requires two incompatible, existing versions of package `b` @@ -1214,11 +1058,7 @@ fn transitive_incompatible_versions() { And because you require package-a==1.0.0, we can conclude that your requirements are unsatisfiable. "); - assert_not_installed( - &context.venv, - "transitive_incompatible_versions_a", - &context.temp_dir, - ); + context.assert_not_installed("transitive_incompatible_versions_a"); } /// The user requires packages `a` and `b` but `a` requires a different version of `b` @@ -1265,16 +1105,8 @@ fn transitive_incompatible_with_root_version() { And because you require package-a and package-b==1.0.0, we can conclude that your requirements are unsatisfiable. "); - assert_not_installed( - &context.venv, - "transitive_incompatible_with_root_version_a", - &context.temp_dir, - ); - assert_not_installed( - &context.venv, - "transitive_incompatible_with_root_version_b", - &context.temp_dir, - ); + context.assert_not_installed("transitive_incompatible_with_root_version_a"); + context.assert_not_installed("transitive_incompatible_with_root_version_b"); } /// The user requires package `a` and `b`; `a` and `b` require different versions of `c` @@ -1327,16 +1159,8 @@ fn transitive_incompatible_with_transitive() { And because you require package-a and package-b, we can conclude that your requirements are unsatisfiable. "); - assert_not_installed( - &context.venv, - "transitive_incompatible_with_transitive_a", - &context.temp_dir, - ); - assert_not_installed( - &context.venv, - "transitive_incompatible_with_transitive_b", - &context.temp_dir, - ); + context.assert_not_installed("transitive_incompatible_with_transitive_a"); + context.assert_not_installed("transitive_incompatible_with_transitive_b"); } /// A local version should be included in inclusive ordered comparisons. @@ -1378,12 +1202,7 @@ fn local_greater_than_or_equal() { "); // The version '1.2.3+foo' satisfies the constraint '>=1.2.3'. - assert_installed( - &context.venv, - "local_greater_than_or_equal_a", - "1.2.3+foo", - &context.temp_dir, - ); + context.assert_installed("local_greater_than_or_equal_a", "1.2.3+foo"); } /// A local version should be excluded in exclusive ordered comparisons. @@ -1419,7 +1238,7 @@ fn local_greater_than() { ╰─▶ Because only package-a==1.2.3+foo is available and you require package-a>1.2.3, we can conclude that your requirements are unsatisfiable. "); - assert_not_installed(&context.venv, "local_greater_than_a", &context.temp_dir); + context.assert_not_installed("local_greater_than_a"); } /// A local version should be included in inclusive ordered comparisons. @@ -1461,12 +1280,7 @@ fn local_less_than_or_equal() { "); // The version '1.2.3+foo' satisfies the constraint '<=1.2.3'. - assert_installed( - &context.venv, - "local_less_than_or_equal_a", - "1.2.3+foo", - &context.temp_dir, - ); + context.assert_installed("local_less_than_or_equal_a", "1.2.3+foo"); } /// A local version should be excluded in exclusive ordered comparisons. @@ -1502,7 +1316,7 @@ fn local_less_than() { ╰─▶ Because only package-a==1.2.3+foo is available and you require package-a<1.2.3, we can conclude that your requirements are unsatisfiable. "); - assert_not_installed(&context.venv, "local_less_than_a", &context.temp_dir); + context.assert_not_installed("local_less_than_a"); } /// Tests that we can select an older version with a local segment when newer versions are incompatible. @@ -1546,12 +1360,7 @@ fn local_not_latest() { + package-a==1.2.1+foo "); - assert_installed( - &context.venv, - "local_not_latest_a", - "1.2.1+foo", - &context.temp_dir, - ); + context.assert_installed("local_not_latest_a", "1.2.1+foo"); } /// If there is a 1.2.3 version with an sdist published and no compatible wheels, then the sdist will be used. @@ -1593,12 +1402,7 @@ fn local_not_used_with_sdist() { "); // The version '1.2.3' with an sdist satisfies the constraint '==1.2.3'. - assert_installed( - &context.venv, - "local_not_used_with_sdist_a", - "1.2.3+foo", - &context.temp_dir, - ); + context.assert_installed("local_not_used_with_sdist_a", "1.2.3+foo"); } /// A simple version constraint should not exclude published versions with local segments. @@ -1640,12 +1444,7 @@ fn local_simple() { "); // The version '1.2.3+foo' satisfies the constraint '==1.2.3'. - assert_installed( - &context.venv, - "local_simple_a", - "1.2.3+foo", - &context.temp_dir, - ); + context.assert_installed("local_simple_a", "1.2.3+foo"); } /// A dependency depends on a conflicting local version of a direct dependency, but we can backtrack to a compatible version. @@ -1701,18 +1500,8 @@ fn local_transitive_backtrack() { "); // Backtracking to '1.0.0' gives us compatible local versions of b. - assert_installed( - &context.venv, - "local_transitive_backtrack_a", - "1.0.0", - &context.temp_dir, - ); - assert_installed( - &context.venv, - "local_transitive_backtrack_b", - "2.0.0+foo", - &context.temp_dir, - ); + context.assert_installed("local_transitive_backtrack_a", "1.0.0"); + context.assert_installed("local_transitive_backtrack_b", "2.0.0+foo"); } /// A dependency depends on a conflicting local version of a direct dependency. @@ -1759,16 +1548,8 @@ fn local_transitive_conflicting() { And because you require package-a and package-b==2.0.0+foo, we can conclude that your requirements are unsatisfiable. "); - assert_not_installed( - &context.venv, - "local_transitive_conflicting_a", - &context.temp_dir, - ); - assert_not_installed( - &context.venv, - "local_transitive_conflicting_b", - &context.temp_dir, - ); + context.assert_not_installed("local_transitive_conflicting_a"); + context.assert_not_installed("local_transitive_conflicting_b"); } /// A transitive dependency has both a non-local and local version published, but the non-local version is unusable. @@ -1819,18 +1600,8 @@ fn local_transitive_confounding() { "); // The version '2.0.0+foo' satisfies the constraint '==2.0.0'. - assert_installed( - &context.venv, - "local_transitive_confounding_a", - "1.0.0", - &context.temp_dir, - ); - assert_installed( - &context.venv, - "local_transitive_confounding_b", - "2.0.0+foo", - &context.temp_dir, - ); + context.assert_installed("local_transitive_confounding_a", "1.0.0"); + context.assert_installed("local_transitive_confounding_b", "2.0.0+foo"); } /// A transitive constraint on a local version should match an inclusive ordered operator. @@ -1881,18 +1652,8 @@ fn local_transitive_greater_than_or_equal() { "); // The version '2.0.0+foo' satisfies both >=2.0.0 and ==2.0.0+foo. - assert_installed( - &context.venv, - "local_transitive_greater_than_or_equal_a", - "1.0.0", - &context.temp_dir, - ); - assert_installed( - &context.venv, - "local_transitive_greater_than_or_equal_b", - "2.0.0+foo", - &context.temp_dir, - ); + context.assert_installed("local_transitive_greater_than_or_equal_a", "1.0.0"); + context.assert_installed("local_transitive_greater_than_or_equal_b", "2.0.0+foo"); } /// A transitive constraint on a local version should not match an exclusive ordered operator. @@ -1939,16 +1700,8 @@ fn local_transitive_greater_than() { And because you require package-a and package-b==2.0.0+foo, we can conclude that your requirements are unsatisfiable. "); - assert_not_installed( - &context.venv, - "local_transitive_greater_than_a", - &context.temp_dir, - ); - assert_not_installed( - &context.venv, - "local_transitive_greater_than_b", - &context.temp_dir, - ); + context.assert_not_installed("local_transitive_greater_than_a"); + context.assert_not_installed("local_transitive_greater_than_b"); } /// A transitive constraint on a local version should match an inclusive ordered operator. @@ -1999,18 +1752,8 @@ fn local_transitive_less_than_or_equal() { "); // The version '2.0.0+foo' satisfies both <=2.0.0 and ==2.0.0+foo. - assert_installed( - &context.venv, - "local_transitive_less_than_or_equal_a", - "1.0.0", - &context.temp_dir, - ); - assert_installed( - &context.venv, - "local_transitive_less_than_or_equal_b", - "2.0.0+foo", - &context.temp_dir, - ); + context.assert_installed("local_transitive_less_than_or_equal_a", "1.0.0"); + context.assert_installed("local_transitive_less_than_or_equal_b", "2.0.0+foo"); } /// A transitive constraint on a local version should not match an exclusive ordered operator. @@ -2057,16 +1800,8 @@ fn local_transitive_less_than() { And because you require package-a and package-b==2.0.0+foo, we can conclude that your requirements are unsatisfiable. "); - assert_not_installed( - &context.venv, - "local_transitive_less_than_a", - &context.temp_dir, - ); - assert_not_installed( - &context.venv, - "local_transitive_less_than_b", - &context.temp_dir, - ); + context.assert_not_installed("local_transitive_less_than_a"); + context.assert_not_installed("local_transitive_less_than_b"); } /// A simple version constraint should not exclude published versions with local segments. @@ -2117,18 +1852,8 @@ fn local_transitive() { "); // The version '2.0.0+foo' satisfies both ==2.0.0 and ==2.0.0+foo. - assert_installed( - &context.venv, - "local_transitive_a", - "1.0.0", - &context.temp_dir, - ); - assert_installed( - &context.venv, - "local_transitive_b", - "2.0.0+foo", - &context.temp_dir, - ); + context.assert_installed("local_transitive_a", "1.0.0"); + context.assert_installed("local_transitive_b", "2.0.0+foo"); } /// Even if there is a 1.2.3 version published, if it is unavailable for some reason (no sdist and no compatible wheels in this case), a 1.2.3 version with a local segment should be usable instead. @@ -2170,12 +1895,7 @@ fn local_used_without_sdist() { "); // The version '1.2.3+foo' satisfies the constraint '==1.2.3'. - assert_installed( - &context.venv, - "local_used_without_sdist_a", - "1.2.3+foo", - &context.temp_dir, - ); + context.assert_installed("local_used_without_sdist_a", "1.2.3+foo"); } /// An equal version constraint should match a post-release version if the post-release version is available. @@ -2216,12 +1936,7 @@ fn post_equal_available() { "); // The version '1.2.3.post0' satisfies the constraint '==1.2.3.post0'. - assert_installed( - &context.venv, - "post_equal_available_a", - "1.2.3.post0", - &context.temp_dir, - ); + context.assert_installed("post_equal_available_a", "1.2.3.post0"); } /// An equal version constraint should not match a post-release version if the post-release version is not available. @@ -2259,11 +1974,7 @@ fn post_equal_not_available() { ╰─▶ Because there is no version of package-a==1.2.3.post0 and you require package-a==1.2.3.post0, we can conclude that your requirements are unsatisfiable. "); - assert_not_installed( - &context.venv, - "post_equal_not_available_a", - &context.temp_dir, - ); + context.assert_not_installed("post_equal_not_available_a"); } /// A greater-than-or-equal version constraint should match a post-release version if the constraint is itself a post-release version. @@ -2305,12 +2016,7 @@ fn post_greater_than_or_equal_post() { "); // The version '1.2.3.post1' satisfies the constraint '>=1.2.3.post0'. - assert_installed( - &context.venv, - "post_greater_than_or_equal_post_a", - "1.2.3.post1", - &context.temp_dir, - ); + context.assert_installed("post_greater_than_or_equal_post_a", "1.2.3.post1"); } /// A greater-than-or-equal version constraint should match a post-release version. @@ -2349,12 +2055,7 @@ fn post_greater_than_or_equal() { "); // The version '1.2.3.post1' satisfies the constraint '>=1.2.3'. - assert_installed( - &context.venv, - "post_greater_than_or_equal_a", - "1.2.3.post1", - &context.temp_dir, - ); + context.assert_installed("post_greater_than_or_equal_a", "1.2.3.post1"); } /// A greater-than version constraint should not match a post-release version if the post-release version is not available. @@ -2394,11 +2095,7 @@ fn post_greater_than_post_not_available() { ╰─▶ Because only package-a<=1.2.3.post1 is available and you require package-a>=1.2.3.post3, we can conclude that your requirements are unsatisfiable. "); - assert_not_installed( - &context.venv, - "post_greater_than_post_not_available_a", - &context.temp_dir, - ); + context.assert_not_installed("post_greater_than_post_not_available_a"); } /// A greater-than version constraint should match a post-release version if the constraint is itself a post-release version. @@ -2439,12 +2136,7 @@ fn post_greater_than_post() { "); // The version '1.2.3.post1' satisfies the constraint '>1.2.3.post0'. - assert_installed( - &context.venv, - "post_greater_than_post_a", - "1.2.3.post1", - &context.temp_dir, - ); + context.assert_installed("post_greater_than_post_a", "1.2.3.post1"); } /// A greater-than version constraint should not match a post-release version. @@ -2480,7 +2172,7 @@ fn post_greater_than() { ╰─▶ Because only package-a==1.2.3.post1 is available and you require package-a>1.2.3, we can conclude that your requirements are unsatisfiable. "); - assert_not_installed(&context.venv, "post_greater_than_a", &context.temp_dir); + context.assert_not_installed("post_greater_than_a"); } /// A less-than-or-equal version constraint should not match a post-release version. @@ -2516,11 +2208,7 @@ fn post_less_than_or_equal() { ╰─▶ Because only package-a==1.2.3.post1 is available and you require package-a<=1.2.3, we can conclude that your requirements are unsatisfiable. "); - assert_not_installed( - &context.venv, - "post_less_than_or_equal_a", - &context.temp_dir, - ); + context.assert_not_installed("post_less_than_or_equal_a"); } /// A less-than version constraint should not match a post-release version. @@ -2556,7 +2244,7 @@ fn post_less_than() { ╰─▶ Because only package-a==1.2.3.post1 is available and you require package-a<1.2.3, we can conclude that your requirements are unsatisfiable. "); - assert_not_installed(&context.venv, "post_less_than_a", &context.temp_dir); + context.assert_not_installed("post_less_than_a"); } /// A greater-than version constraint should not match a post-release version with a local version identifier. @@ -2594,11 +2282,7 @@ fn post_local_greater_than_post() { ╰─▶ Because only package-a<=1.2.3.post1+local is available and you require package-a>=1.2.3.post2, we can conclude that your requirements are unsatisfiable. "); - assert_not_installed( - &context.venv, - "post_local_greater_than_post_a", - &context.temp_dir, - ); + context.assert_not_installed("post_local_greater_than_post_a"); } /// A greater-than version constraint should not match a post-release version with a local version identifier. @@ -2636,11 +2320,7 @@ fn post_local_greater_than() { ╰─▶ Because only package-a<=1.2.3.post1+local is available and you require package-a>1.2.3, we can conclude that your requirements are unsatisfiable. "); - assert_not_installed( - &context.venv, - "post_local_greater_than_a", - &context.temp_dir, - ); + context.assert_not_installed("post_local_greater_than_a"); } /// A simple version constraint should not match a post-release version. @@ -2676,7 +2356,7 @@ fn post_simple() { ╰─▶ Because there is no version of package-a==1.2.3 and you require package-a==1.2.3, we can conclude that your requirements are unsatisfiable. "); - assert_not_installed(&context.venv, "post_simple_a", &context.temp_dir); + context.assert_not_installed("post_simple_a"); } /// The user requires `a` which has multiple prereleases available with different labels. @@ -2721,12 +2401,7 @@ fn package_multiple_prereleases_kinds() { "); // Release candidates should be the highest precedence prerelease kind. - assert_installed( - &context.venv, - "package_multiple_prereleases_kinds_a", - "1.0.0rc1", - &context.temp_dir, - ); + context.assert_installed("package_multiple_prereleases_kinds_a", "1.0.0rc1"); } /// The user requires `a` which has multiple alphas available. @@ -2771,12 +2446,7 @@ fn package_multiple_prereleases_numbers() { "); // The latest alpha version should be selected. - assert_installed( - &context.venv, - "package_multiple_prereleases_numbers_a", - "1.0.0a3", - &context.temp_dir, - ); + context.assert_installed("package_multiple_prereleases_numbers_a", "1.0.0a3"); } /// The user requires a non-prerelease version of `a` which only has prerelease versions available. There are pre-releases on the boundary of their range. @@ -2819,12 +2489,7 @@ fn package_only_prereleases_boundary() { "); // Since there are only prerelease versions of `a` available, a prerelease is allowed. Since the user did not explicitly request a pre-release, pre-releases at the boundary should not be selected. - assert_installed( - &context.venv, - "package_only_prereleases_boundary_a", - "0.1.0a1", - &context.temp_dir, - ); + context.assert_installed("package_only_prereleases_boundary_a", "0.1.0a1"); } /// The user requires a version of package `a` which only matches prerelease versions but they did not include a prerelease specifier. @@ -2865,11 +2530,7 @@ fn package_only_prereleases_in_range() { "); // Since there are stable versions of `a` available, prerelease versions should not be selected without explicit opt-in. - assert_not_installed( - &context.venv, - "package_only_prereleases_in_range_a", - &context.temp_dir, - ); + context.assert_not_installed("package_only_prereleases_in_range_a"); } /// The user requires any version of package `a` which only has prerelease versions available. @@ -2908,12 +2569,7 @@ fn package_only_prereleases() { "); // Since there are only prerelease versions of `a` available, it should be installed even though the user did not include a prerelease specifier. - assert_installed( - &context.venv, - "package_only_prereleases_a", - "1.0.0a1", - &context.temp_dir, - ); + context.assert_installed("package_only_prereleases_a", "1.0.0a1"); } /// The user requires a version of `a` with a prerelease specifier and both prerelease and stable releases are available. @@ -2961,12 +2617,7 @@ fn package_prerelease_specified_mixed_available() { "); // Since the user provided a prerelease specifier, the latest prerelease version should be selected. - assert_installed( - &context.venv, - "package_prerelease_specified_mixed_available_a", - "1.0.0a1", - &context.temp_dir, - ); + context.assert_installed("package_prerelease_specified_mixed_available_a", "1.0.0a1"); } /// The user requires a version of `a` with a prerelease specifier and only stable releases are available. @@ -3014,11 +2665,9 @@ fn package_prerelease_specified_only_final_available() { "); // The latest stable version should be selected. - assert_installed( - &context.venv, + context.assert_installed( "package_prerelease_specified_only_final_available_a", "0.3.0", - &context.temp_dir, ); } @@ -3067,11 +2716,9 @@ fn package_prerelease_specified_only_prerelease_available() { "); // The latest prerelease version should be selected. - assert_installed( - &context.venv, + context.assert_installed( "package_prerelease_specified_only_prerelease_available_a", "0.3.0a1", - &context.temp_dir, ); } @@ -3116,12 +2763,7 @@ fn package_prereleases_boundary() { "); // Since the user did not use a pre-release specifier, pre-releases at the boundary should not be selected even though pre-releases are allowed. - assert_installed( - &context.venv, - "package_prereleases_boundary_a", - "0.1.0", - &context.temp_dir, - ); + context.assert_installed("package_prereleases_boundary_a", "0.1.0"); } /// The user requires a non-prerelease version of `a` but has enabled pre-releases. There are pre-releases on the boundary of their range. @@ -3165,12 +2807,7 @@ fn package_prereleases_global_boundary() { "); // Since the user did not use a pre-release specifier, pre-releases at the boundary should not be selected even though pre-releases are allowed. - assert_installed( - &context.venv, - "package_prereleases_global_boundary_a", - "0.1.0", - &context.temp_dir, - ); + context.assert_installed("package_prereleases_global_boundary_a", "0.1.0"); } /// The user requires a prerelease version of `a`. There are pre-releases on the boundary of their range. @@ -3220,12 +2857,7 @@ fn package_prereleases_specifier_boundary() { "); // Since the user used a pre-release specifier, pre-releases at the boundary should be selected. - assert_installed( - &context.venv, - "package_prereleases_specifier_boundary_a", - "0.2.0a1", - &context.temp_dir, - ); + context.assert_installed("package_prereleases_specifier_boundary_a", "0.2.0a1"); } /// The user requires a version of package `a` which only matches prerelease versions. They did not include a prerelease specifier for the package, but they opted into prereleases globally. @@ -3269,11 +2901,9 @@ fn requires_package_only_prereleases_in_range_global_opt_in() { + package-a==1.0.0a1 "); - assert_installed( - &context.venv, + context.assert_installed( "requires_package_only_prereleases_in_range_global_opt_in_a", "1.0.0a1", - &context.temp_dir, ); } @@ -3315,12 +2945,7 @@ fn requires_package_prerelease_and_final_any() { "); // Since the user did not provide a prerelease specifier, the older stable version should be selected. - assert_installed( - &context.venv, - "requires_package_prerelease_and_final_any_a", - "0.1.0", - &context.temp_dir, - ); + context.assert_installed("requires_package_prerelease_and_final_any_a", "0.1.0"); } /// The user requires package `a` which has a dependency on a package which only matches prerelease versions; the user has opted into allowing prereleases in `b` explicitly. @@ -3374,17 +2999,13 @@ fn transitive_package_only_prereleases_in_range_opt_in() { "); // Since the user included a dependency on `b` with a prerelease specifier, a prerelease version can be selected. - assert_installed( - &context.venv, + context.assert_installed( "transitive_package_only_prereleases_in_range_opt_in_a", "0.1.0", - &context.temp_dir, ); - assert_installed( - &context.venv, + context.assert_installed( "transitive_package_only_prereleases_in_range_opt_in_b", "1.0.0a1", - &context.temp_dir, ); } @@ -3432,11 +3053,7 @@ fn transitive_package_only_prereleases_in_range() { "); // Since there are stable versions of `b` available, the prerelease version should not be selected without explicit opt-in. The available version is excluded by the range requested by the user. - assert_not_installed( - &context.venv, - "transitive_package_only_prereleases_in_range_a", - &context.temp_dir, - ); + context.assert_not_installed("transitive_package_only_prereleases_in_range_a"); } /// The user requires any version of package `a` which requires `b` which only has prerelease versions available. @@ -3481,18 +3098,8 @@ fn transitive_package_only_prereleases() { "); // Since there are only prerelease versions of `b` available, it should be selected even though the user did not opt-in to prereleases. - assert_installed( - &context.venv, - "transitive_package_only_prereleases_a", - "0.1.0", - &context.temp_dir, - ); - assert_installed( - &context.venv, - "transitive_package_only_prereleases_b", - "1.0.0a1", - &context.temp_dir, - ); + context.assert_installed("transitive_package_only_prereleases_a", "0.1.0"); + context.assert_installed("transitive_package_only_prereleases_b", "1.0.0a1"); } /// A transitive dependency has both a prerelease and a stable selector, but can only be satisfied by a prerelease. There are many prerelease versions and some are excluded. @@ -3605,16 +3212,10 @@ fn transitive_prerelease_and_stable_dependency_many_versions_holes() { "); // Since the user did not explicitly opt-in to a prerelease, it cannot be selected. - assert_not_installed( - &context.venv, - "transitive_prerelease_and_stable_dependency_many_versions_holes_a", - &context.temp_dir, - ); - assert_not_installed( - &context.venv, - "transitive_prerelease_and_stable_dependency_many_versions_holes_b", - &context.temp_dir, - ); + context + .assert_not_installed("transitive_prerelease_and_stable_dependency_many_versions_holes_a"); + context + .assert_not_installed("transitive_prerelease_and_stable_dependency_many_versions_holes_b"); } /// A transitive dependency has both a prerelease and a stable selector, but can only be satisfied by a prerelease. There are many prerelease versions. @@ -3716,16 +3317,8 @@ fn transitive_prerelease_and_stable_dependency_many_versions() { "); // Since the user did not explicitly opt-in to a prerelease, it cannot be selected. - assert_not_installed( - &context.venv, - "transitive_prerelease_and_stable_dependency_many_versions_a", - &context.temp_dir, - ); - assert_not_installed( - &context.venv, - "transitive_prerelease_and_stable_dependency_many_versions_b", - &context.temp_dir, - ); + context.assert_not_installed("transitive_prerelease_and_stable_dependency_many_versions_a"); + context.assert_not_installed("transitive_prerelease_and_stable_dependency_many_versions_b"); } /// A transitive dependency has both a prerelease and a stable selector, but can only be satisfied by a prerelease. The user includes an opt-in to prereleases of the transitive dependency. @@ -3788,23 +3381,17 @@ fn transitive_prerelease_and_stable_dependency_opt_in() { "); // Since the user explicitly opted-in to a prerelease for `c`, it can be installed. - assert_installed( - &context.venv, + context.assert_installed( "transitive_prerelease_and_stable_dependency_opt_in_a", "1.0.0", - &context.temp_dir, ); - assert_installed( - &context.venv, + context.assert_installed( "transitive_prerelease_and_stable_dependency_opt_in_b", "1.0.0", - &context.temp_dir, ); - assert_installed( - &context.venv, + context.assert_installed( "transitive_prerelease_and_stable_dependency_opt_in_c", "2.0.0b1", - &context.temp_dir, ); } @@ -3860,16 +3447,8 @@ fn transitive_prerelease_and_stable_dependency() { "); // Since the user did not explicitly opt-in to a prerelease, it cannot be selected. - assert_not_installed( - &context.venv, - "transitive_prerelease_and_stable_dependency_a", - &context.temp_dir, - ); - assert_not_installed( - &context.venv, - "transitive_prerelease_and_stable_dependency_b", - &context.temp_dir, - ); + context.assert_not_installed("transitive_prerelease_and_stable_dependency_a"); + context.assert_not_installed("transitive_prerelease_and_stable_dependency_b"); } /// The user requires a package where recent versions require a Python version greater than the current version, but an older version is compatible. @@ -3915,12 +3494,7 @@ fn python_greater_than_current_backtrack() { + package-a==1.0.0 "); - assert_installed( - &context.venv, - "python_greater_than_current_backtrack_a", - "1.0.0", - &context.temp_dir, - ); + context.assert_installed("python_greater_than_current_backtrack_a", "1.0.0"); } /// The user requires a package where recent versions require a Python version greater than the current version, but an excluded older version is compatible. @@ -3975,11 +3549,7 @@ fn python_greater_than_current_excluded() { And because you require package-a>=2.0.0, we can conclude that your requirements are unsatisfiable. "); - assert_not_installed( - &context.venv, - "python_greater_than_current_excluded_a", - &context.temp_dir, - ); + context.assert_not_installed("python_greater_than_current_excluded_a"); } /// The user requires a package which has many versions which all require a Python version greater than the current version @@ -4037,11 +3607,7 @@ fn python_greater_than_current_many() { ╰─▶ Because there is no version of package-a==1.0.0 and you require package-a==1.0.0, we can conclude that your requirements are unsatisfiable. "); - assert_not_installed( - &context.venv, - "python_greater_than_current_many_a", - &context.temp_dir, - ); + context.assert_not_installed("python_greater_than_current_many_a"); } /// The user requires a package which requires a Python version with a patch version greater than the current patch version @@ -4079,11 +3645,7 @@ fn python_greater_than_current_patch() { And because you require package-a==1.0.0, we can conclude that your requirements are unsatisfiable. "); - assert_not_installed( - &context.venv, - "python_greater_than_current_patch_a", - &context.temp_dir, - ); + context.assert_not_installed("python_greater_than_current_patch_a"); } /// The user requires a package which requires a Python version greater than the current version @@ -4120,11 +3682,7 @@ fn python_greater_than_current() { And because you require package-a==1.0.0, we can conclude that your requirements are unsatisfiable. "); - assert_not_installed( - &context.venv, - "python_greater_than_current_a", - &context.temp_dir, - ); + context.assert_not_installed("python_greater_than_current_a"); } /// The user requires a package which requires a Python version less than the current version @@ -4199,11 +3757,7 @@ fn python_version_does_not_exist() { And because you require package-a==1.0.0, we can conclude that your requirements are unsatisfiable. "); - assert_not_installed( - &context.venv, - "python_version_does_not_exist_a", - &context.temp_dir, - ); + context.assert_not_installed("python_version_does_not_exist_a"); } /// Both wheels and source distributions are available, and the user has disabled binaries. @@ -4323,11 +3877,7 @@ fn no_sdist_no_wheels_with_matching_abi() { hint: You require CPython 3.12 (`cp312`), but we only found wheels for `package-a` (v1.0.0) with the following Python ABI tag: `graalpy240_310_native` "); - assert_not_installed( - &context.venv, - "no_sdist_no_wheels_with_matching_abi_a", - &context.temp_dir, - ); + context.assert_not_installed("no_sdist_no_wheels_with_matching_abi_a"); } /// No wheels with matching platform tags are available, nor are any source distributions available @@ -4367,11 +3917,7 @@ fn no_sdist_no_wheels_with_matching_platform() { hint: Wheels are available for `package-a` (v1.0.0) on the following platform: `macosx_10_0_ppc64` "); - assert_not_installed( - &context.venv, - "no_sdist_no_wheels_with_matching_platform_a", - &context.temp_dir, - ); + context.assert_not_installed("no_sdist_no_wheels_with_matching_platform_a"); } /// No wheels with matching Python tags are available, nor are any source distributions available @@ -4411,11 +3957,7 @@ fn no_sdist_no_wheels_with_matching_python() { hint: You require CPython 3.12 (`cp312`), but we only found wheels for `package-a` (v1.0.0) with the following Python implementation tag: `graalpy310` "); - assert_not_installed( - &context.venv, - "no_sdist_no_wheels_with_matching_python_a", - &context.temp_dir, - ); + context.assert_not_installed("no_sdist_no_wheels_with_matching_python_a"); } /// No wheels are available, only source distributions but the user has disabled builds. @@ -4456,7 +3998,7 @@ fn no_wheels_no_build() { hint: Wheels are required for `package-a` because building from source is disabled for `package-a` (i.e., with `--no-build-package package-a`) "); - assert_not_installed(&context.venv, "no_wheels_no_build_a", &context.temp_dir); + context.assert_not_installed("no_wheels_no_build_a"); } /// No wheels with matching platform tags are available, just source distributions. @@ -4569,7 +4111,7 @@ fn only_wheels_no_binary() { hint: A source distribution is required for `package-a` because using pre-built wheels is disabled for `package-a` (i.e., with `--no-binary-package package-a`) "); - assert_not_installed(&context.venv, "only_wheels_no_binary_a", &context.temp_dir); + context.assert_not_installed("only_wheels_no_binary_a"); } /// No source distributions are available, only wheels. @@ -4684,11 +4226,7 @@ fn package_only_yanked_in_range() { "); // Since there are other versions of `a` available, yanked versions should not be selected without explicit opt-in. - assert_not_installed( - &context.venv, - "package_only_yanked_in_range_a", - &context.temp_dir, - ); + context.assert_not_installed("package_only_yanked_in_range_a"); } /// The user requires any version of package `a` which only has yanked versions available. @@ -4726,7 +4264,7 @@ fn package_only_yanked() { "); // Yanked versions should not be installed, even if they are the only one available. - assert_not_installed(&context.venv, "package_only_yanked_a", &context.temp_dir); + context.assert_not_installed("package_only_yanked_a"); } /// The user requires any version of `a` and both yanked and unyanked releases are available. @@ -4772,12 +4310,7 @@ fn package_yanked_specified_mixed_available() { "); // The latest unyanked version should be selected. - assert_installed( - &context.venv, - "package_yanked_specified_mixed_available_a", - "0.3.0", - &context.temp_dir, - ); + context.assert_installed("package_yanked_specified_mixed_available_a", "0.3.0"); } /// The user requires any version of package `a` has a yanked version available and an older unyanked version. @@ -4818,12 +4351,7 @@ fn requires_package_yanked_and_unyanked_any() { "); // The unyanked version should be selected. - assert_installed( - &context.venv, - "requires_package_yanked_and_unyanked_any_a", - "0.1.0", - &context.temp_dir, - ); + context.assert_installed("requires_package_yanked_and_unyanked_any_a", "0.1.0"); } /// The user requires package `a` which has a dependency on a package which only matches yanked versions; the user has opted into allowing the yanked version of `b` explicitly. @@ -4877,18 +4405,8 @@ fn transitive_package_only_yanked_in_range_opt_in() { "#); // Since the user included a dependency on `b` with an exact specifier, the yanked version can be selected. - assert_installed( - &context.venv, - "transitive_package_only_yanked_in_range_opt_in_a", - "0.1.0", - &context.temp_dir, - ); - assert_installed( - &context.venv, - "transitive_package_only_yanked_in_range_opt_in_b", - "1.0.0", - &context.temp_dir, - ); + context.assert_installed("transitive_package_only_yanked_in_range_opt_in_a", "0.1.0"); + context.assert_installed("transitive_package_only_yanked_in_range_opt_in_b", "1.0.0"); } /// The user requires package `a` which has a dependency on a package which only matches yanked versions. @@ -4937,11 +4455,7 @@ fn transitive_package_only_yanked_in_range() { "); // Yanked versions should not be installed, even if they are the only valid version in a range. - assert_not_installed( - &context.venv, - "transitive_package_only_yanked_in_range_a", - &context.temp_dir, - ); + context.assert_not_installed("transitive_package_only_yanked_in_range_a"); } /// The user requires any version of package `a` which requires `b` which only has yanked versions available. @@ -4985,11 +4499,7 @@ fn transitive_package_only_yanked() { "); // Yanked versions should not be installed, even if they are the only one available. - assert_not_installed( - &context.venv, - "transitive_package_only_yanked_a", - &context.temp_dir, - ); + context.assert_not_installed("transitive_package_only_yanked_a"); } /// A transitive dependency has both a yanked and an unyanked version, but can only be satisfied by a yanked. The user includes an opt-in to the yanked version of the transitive dependency. @@ -5052,23 +4562,17 @@ fn transitive_yanked_and_unyanked_dependency_opt_in() { "#); // Since the user explicitly selected the yanked version of `c`, it can be installed. - assert_installed( - &context.venv, + context.assert_installed( "transitive_yanked_and_unyanked_dependency_opt_in_a", "1.0.0", - &context.temp_dir, ); - assert_installed( - &context.venv, + context.assert_installed( "transitive_yanked_and_unyanked_dependency_opt_in_b", "1.0.0", - &context.temp_dir, ); - assert_installed( - &context.venv, + context.assert_installed( "transitive_yanked_and_unyanked_dependency_opt_in_c", "2.0.0", - &context.temp_dir, ); } @@ -5122,14 +4626,6 @@ fn transitive_yanked_and_unyanked_dependency() { "); // Since the user did not explicitly select the yanked version, it cannot be used. - assert_not_installed( - &context.venv, - "transitive_yanked_and_unyanked_dependency_a", - &context.temp_dir, - ); - assert_not_installed( - &context.venv, - "transitive_yanked_and_unyanked_dependency_b", - &context.temp_dir, - ); + context.assert_not_installed("transitive_yanked_and_unyanked_dependency_a"); + context.assert_not_installed("transitive_yanked_and_unyanked_dependency_b"); } diff --git a/crates/uv/tests/it/pip_sync.rs b/crates/uv/tests/it/pip_sync.rs index 32252396e..43cbc26c7 100644 --- a/crates/uv/tests/it/pip_sync.rs +++ b/crates/uv/tests/it/pip_sync.rs @@ -1,6 +1,4 @@ use std::env::consts::EXE_SUFFIX; -use std::path::Path; -use std::process::Command; use anyhow::Result; use assert_cmd::prelude::*; @@ -11,24 +9,10 @@ use indoc::indoc; use predicates::Predicate; use url::Url; -use crate::common::{ - TestContext, download_to_disk, site_packages_path, uv_snapshot, venv_to_interpreter, -}; +use crate::common::{TestContext, download_to_disk, site_packages_path, uv_snapshot}; use uv_fs::{Simplified, copy_dir_all}; use uv_static::EnvVars; -fn check_command(venv: &Path, command: &str, temp_dir: &Path) { - Command::new(venv_to_interpreter(venv)) - // Our tests change files in <1s, so we must disable CPython bytecode caching or we'll get stale files - // https://github.com/python/cpython/issues/75953 - .arg("-B") - .arg("-c") - .arg(command) - .current_dir(temp_dir) - .assert() - .success(); -} - #[test] fn missing_requirements_txt() { let context = TestContext::new("3.12"); @@ -463,7 +447,13 @@ fn link() -> Result<()> { "### ); - check_command(&context2.venv, "import iniconfig", &context2.temp_dir); + context2 + .python_command() + .arg("-c") + .arg("import iniconfig") + .current_dir(&context2.temp_dir) + .assert() + .success(); Ok(()) } @@ -5221,8 +5211,8 @@ fn target_built_distribution() -> Result<()> { context.assert_command("import iniconfig").failure(); // Ensure that we can import the package by augmenting the `PYTHONPATH`. - Command::new(venv_to_interpreter(&context.venv)) - .arg("-B") + context + .python_command() .arg("-c") .arg("import iniconfig") .env(EnvVars::PYTHONPATH, context.temp_dir.child("target").path()) @@ -5326,8 +5316,8 @@ fn target_source_distribution() -> Result<()> { context.assert_command("import iniconfig").failure(); // Ensure that we can import the package by augmenting the `PYTHONPATH`. - Command::new(venv_to_interpreter(&context.venv)) - .arg("-B") + context + .python_command() .arg("-c") .arg("import iniconfig") .env(EnvVars::PYTHONPATH, context.temp_dir.child("target").path()) @@ -5397,8 +5387,8 @@ fn target_no_build_isolation() -> Result<()> { context.assert_command("import wheel").failure(); // Ensure that we can import the package by augmenting the `PYTHONPATH`. - Command::new(venv_to_interpreter(&context.venv)) - .arg("-B") + context + .python_command() .arg("-c") .arg("import wheel") .env(EnvVars::PYTHONPATH, context.temp_dir.child("target").path()) @@ -5474,8 +5464,8 @@ fn prefix() -> Result<()> { context.assert_command("import iniconfig").failure(); // Ensure that we can import the package by augmenting the `PYTHONPATH`. - Command::new(venv_to_interpreter(&context.venv)) - .arg("-B") + context + .python_command() .arg("-c") .arg("import iniconfig") .env( diff --git a/crates/uv/tests/it/pip_uninstall.rs b/crates/uv/tests/it/pip_uninstall.rs index 3c1c0d717..5e6cbf6f9 100644 --- a/crates/uv/tests/it/pip_uninstall.rs +++ b/crates/uv/tests/it/pip_uninstall.rs @@ -5,7 +5,7 @@ use assert_cmd::prelude::*; use assert_fs::fixture::ChildPath; use assert_fs::prelude::*; -use crate::common::{TestContext, get_bin, uv_snapshot, venv_to_interpreter}; +use crate::common::{TestContext, get_bin, uv_snapshot}; #[test] fn no_arguments() { @@ -113,12 +113,7 @@ fn uninstall() -> Result<()> { .assert() .success(); - Command::new(venv_to_interpreter(&context.venv)) - .arg("-c") - .arg("import markupsafe") - .current_dir(&context.temp_dir) - .assert() - .success(); + context.assert_command("import markupsafe").success(); uv_snapshot!(context.pip_uninstall() .arg("MarkupSafe"), @r###" @@ -132,12 +127,7 @@ fn uninstall() -> Result<()> { "### ); - Command::new(venv_to_interpreter(&context.venv)) - .arg("-c") - .arg("import markupsafe") - .current_dir(&context.temp_dir) - .assert() - .failure(); + context.assert_command("import markupsafe").failure(); Ok(()) } @@ -156,12 +146,7 @@ fn missing_record() -> Result<()> { .assert() .success(); - Command::new(venv_to_interpreter(&context.venv)) - .arg("-c") - .arg("import markupsafe") - .current_dir(&context.temp_dir) - .assert() - .success(); + context.assert_command("import markupsafe").success(); // Delete the RECORD file. let dist_info = context.site_packages().join("MarkupSafe-2.1.3.dist-info"); @@ -202,11 +187,7 @@ fn uninstall_editable_by_name() -> Result<()> { .assert() .success(); - Command::new(venv_to_interpreter(&context.venv)) - .arg("-c") - .arg("import poetry_editable") - .assert() - .success(); + context.assert_command("import poetry_editable").success(); // Uninstall the editable by name. uv_snapshot!(context.filters(), context.pip_uninstall() @@ -221,11 +202,7 @@ fn uninstall_editable_by_name() -> Result<()> { "### ); - Command::new(venv_to_interpreter(&context.venv)) - .arg("-c") - .arg("import poetry_editable") - .assert() - .failure(); + context.assert_command("import poetry_editable").failure(); Ok(()) } @@ -251,11 +228,7 @@ fn uninstall_by_path() -> Result<()> { .assert() .success(); - Command::new(venv_to_interpreter(&context.venv)) - .arg("-c") - .arg("import poetry_editable") - .assert() - .success(); + context.assert_command("import poetry_editable").success(); // Uninstall the editable by path. uv_snapshot!(context.filters(), context.pip_uninstall() @@ -270,11 +243,7 @@ fn uninstall_by_path() -> Result<()> { "### ); - Command::new(venv_to_interpreter(&context.venv)) - .arg("-c") - .arg("import poetry_editable") - .assert() - .failure(); + context.assert_command("import poetry_editable").failure(); Ok(()) } @@ -300,11 +269,7 @@ fn uninstall_duplicate_by_path() -> Result<()> { .assert() .success(); - Command::new(venv_to_interpreter(&context.venv)) - .arg("-c") - .arg("import poetry_editable") - .assert() - .success(); + context.assert_command("import poetry_editable").success(); // Uninstall the editable by both path and name. uv_snapshot!(context.filters(), context.pip_uninstall() @@ -320,11 +285,7 @@ fn uninstall_duplicate_by_path() -> Result<()> { "### ); - Command::new(venv_to_interpreter(&context.venv)) - .arg("-c") - .arg("import poetry_editable") - .assert() - .failure(); + context.assert_command("import poetry_editable").failure(); Ok(()) } diff --git a/scripts/scenarios/templates/compile.mustache b/scripts/scenarios/templates/compile.mustache index aa6db8529..2a3202662 100644 --- a/scripts/scenarios/templates/compile.mustache +++ b/scripts/scenarios/templates/compile.mustache @@ -16,8 +16,8 @@ use predicates::prelude::predicate; use uv_static::EnvVars; use crate::common::{ - build_vendor_links_url, get_bin, packse_index_url, python_path_with_versions, uv_snapshot, - TestContext, + TestContext, build_vendor_links_url, get_bin, packse_index_url, python_path_with_versions, + uv_snapshot, }; /// Provision python binaries and return a `pip compile` command with options shared across all scenarios. diff --git a/scripts/scenarios/templates/install.mustache b/scripts/scenarios/templates/install.mustache index 15f48077e..8f1c477b2 100644 --- a/scripts/scenarios/templates/install.mustache +++ b/scripts/scenarios/templates/install.mustache @@ -5,52 +5,20 @@ //! #![cfg(all(feature = "python", feature = "pypi", unix))] -use std::path::Path; use std::process::Command; -use assert_cmd::assert::Assert; -use assert_cmd::prelude::*; - use uv_static::EnvVars; -use crate::common::{ - build_vendor_links_url, get_bin, packse_index_url, uv_snapshot, venv_to_interpreter, - TestContext, -}; - -fn assert_command(venv: &Path, command: &str, temp_dir: &Path) -> Assert { - Command::new(venv_to_interpreter(venv)) - .arg("-c") - .arg(command) - .current_dir(temp_dir) - .assert() -} - -fn assert_installed(venv: &Path, package: &'static str, version: &'static str, temp_dir: &Path) { - assert_command( - venv, - format!("import {package} as package; print(package.__version__, end='')").as_str(), - temp_dir, - ) - .success() - .stdout(version); -} - -fn assert_not_installed(venv: &Path, package: &'static str, temp_dir: &Path) { - assert_command(venv, format!("import {package}").as_str(), temp_dir).failure(); -} +use crate::common::{TestContext, build_vendor_links_url, packse_index_url, uv_snapshot}; /// Create a `pip install` command with options shared across all scenarios. fn command(context: &TestContext) -> Command { - let mut command = Command::new(get_bin()); + let mut command = context.pip_install(); command - .arg("pip") - .arg("install") .arg("--index-url") .arg(packse_index_url()) .arg("--find-links") .arg(build_vendor_links_url()); - context.add_shared_options(&mut command, true); command.env_remove(EnvVars::UV_EXCLUDE_NEWER); command } @@ -93,25 +61,20 @@ fn {{module_name}}() { {{/resolver_options.python_platform}} {{#root.requires}} .arg("{{requirement}}") - {{/root.requires}}, @r###" - "###); + {{/root.requires}}, @r#" + "#); {{#expected.explanation}} // {{expected.explanation}} {{/expected.explanation}} {{#expected.satisfiable}} {{#expected.packages}} - assert_installed( - &context.venv, - "{{module_name}}", - "{{version}}", - &context.temp_dir - ); + context.assert_installed("{{module_name}}", "{{version}}"); {{/expected.packages}} {{/expected.satisfiable}} {{^expected.satisfiable}} {{#root.requires}} - assert_not_installed(&context.venv, "{{module_name}}", &context.temp_dir); + context.assert_not_installed("{{module_name}}"); {{/root.requires}} {{/expected.satisfiable}} } diff --git a/scripts/scenarios/templates/lock.mustache b/scripts/scenarios/templates/lock.mustache index 7d80b8f9b..74deb3764 100644 --- a/scripts/scenarios/templates/lock.mustache +++ b/scripts/scenarios/templates/lock.mustache @@ -15,7 +15,7 @@ use insta::assert_snapshot; use uv_static::EnvVars; -use crate::common::{packse_index_url, TestContext, uv_snapshot}; +use crate::common::{TestContext, packse_index_url, uv_snapshot}; {{#scenarios}} From 611a13c8415dc1739ee7c33a7375e846871bc4b2 Mon Sep 17 00:00:00 2001 From: John Mumm Date: Wed, 18 Jun 2025 10:30:12 -0400 Subject: [PATCH 017/349] Fix benchmark compilation failure: `cannot find attribute clap in this scope` (#14128) [Two benchmark jobs](https://github.com/astral-sh/uv/actions/runs/15732775460/job/44337710992?pr=14126) were failing with `error: cannot find attribute clap in this scope` based on #14120. This updates the recently added `#[clap(name = rocm...` lines to use `cfg_attr(feature = "clap",`. --- crates/uv-torch/src/backend.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/crates/uv-torch/src/backend.rs b/crates/uv-torch/src/backend.rs index 263ea07bd..958ea3d9e 100644 --- a/crates/uv-torch/src/backend.rs +++ b/crates/uv-torch/src/backend.rs @@ -109,67 +109,67 @@ pub enum TorchMode { Cu80, /// Use the PyTorch index for ROCm 6.3. #[serde(rename = "rocm6.3")] - #[clap(name = "rocm6.3")] + #[cfg_attr(feature = "clap", clap(name = "rocm6.3"))] Rocm63, /// Use the PyTorch index for ROCm 6.2.4. #[serde(rename = "rocm6.2.4")] - #[clap(name = "rocm6.2.4")] + #[cfg_attr(feature = "clap", clap(name = "rocm6.2.4"))] Rocm624, /// Use the PyTorch index for ROCm 6.2. #[serde(rename = "rocm6.2")] - #[clap(name = "rocm6.2")] + #[cfg_attr(feature = "clap", clap(name = "rocm6.2"))] Rocm62, /// Use the PyTorch index for ROCm 6.1. #[serde(rename = "rocm6.1")] - #[clap(name = "rocm6.1")] + #[cfg_attr(feature = "clap", clap(name = "rocm6.1"))] Rocm61, /// Use the PyTorch index for ROCm 6.0. #[serde(rename = "rocm6.0")] - #[clap(name = "rocm6.0")] + #[cfg_attr(feature = "clap", clap(name = "rocm6.0"))] Rocm60, /// Use the PyTorch index for ROCm 5.7. #[serde(rename = "rocm5.7")] - #[clap(name = "rocm5.7")] + #[cfg_attr(feature = "clap", clap(name = "rocm5.7"))] Rocm57, /// Use the PyTorch index for ROCm 5.6. #[serde(rename = "rocm5.6")] - #[clap(name = "rocm5.6")] + #[cfg_attr(feature = "clap", clap(name = "rocm5.6"))] Rocm56, /// Use the PyTorch index for ROCm 5.5. #[serde(rename = "rocm5.5")] - #[clap(name = "rocm5.5")] + #[cfg_attr(feature = "clap", clap(name = "rocm5.5"))] Rocm55, /// Use the PyTorch index for ROCm 5.4.2. #[serde(rename = "rocm5.4.2")] - #[clap(name = "rocm5.4.2")] + #[cfg_attr(feature = "clap", clap(name = "rocm5.4.2"))] Rocm542, /// Use the PyTorch index for ROCm 5.4. #[serde(rename = "rocm5.4")] - #[clap(name = "rocm5.4")] + #[cfg_attr(feature = "clap", clap(name = "rocm5.4"))] Rocm54, /// Use the PyTorch index for ROCm 5.3. #[serde(rename = "rocm5.3")] - #[clap(name = "rocm5.3")] + #[cfg_attr(feature = "clap", clap(name = "rocm5.3"))] Rocm53, /// Use the PyTorch index for ROCm 5.2. #[serde(rename = "rocm5.2")] - #[clap(name = "rocm5.2")] + #[cfg_attr(feature = "clap", clap(name = "rocm5.2"))] Rocm52, /// Use the PyTorch index for ROCm 5.1.1. #[serde(rename = "rocm5.1.1")] - #[clap(name = "rocm5.1.1")] + #[cfg_attr(feature = "clap", clap(name = "rocm5.1.1"))] Rocm511, /// Use the PyTorch index for ROCm 4.2. #[serde(rename = "rocm4.2")] - #[clap(name = "rocm4.2")] + #[cfg_attr(feature = "clap", clap(name = "rocm4.2"))] Rocm42, /// Use the PyTorch index for ROCm 4.1. #[serde(rename = "rocm4.1")] - #[clap(name = "rocm4.1")] + #[cfg_attr(feature = "clap", clap(name = "rocm4.1"))] Rocm41, /// Use the PyTorch index for ROCm 4.0.1. #[serde(rename = "rocm4.0.1")] - #[clap(name = "rocm4.0.1")] + #[cfg_attr(feature = "clap", clap(name = "rocm4.0.1"))] Rocm401, } From 75d4cd30d6bbb13e2d871d614abe2065c46e9b4a Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 18 Jun 2025 09:55:09 -0500 Subject: [PATCH 018/349] Use Depot for Windows `cargo test` (#14122) Replaces https://github.com/astral-sh/uv/pull/12320 Switches to Depot for the large Windows runner we use for `cargo test`. The runtime goes from 8m 20s -> 6m 44s (total) and 7m 18s -> 4m 41s (test run) which are 20% and 35% speedups respectively. A few things got marginally slower, like Python installs went from 11s -> 38s, the Rust cache went from 15s -> 30s, and drive setup went from 7s -> 20s. --- .github/workflows/ci.yml | 2 +- .github/workflows/setup-dev-drive.ps1 | 45 ++++++++++++++++++++++----- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index feaa38210..459495600 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -266,7 +266,7 @@ jobs: timeout-minutes: 15 needs: determine_changes if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }} - runs-on: github-windows-2025-x86_64-16 + runs-on: depot-windows-2022-16 name: "cargo test | windows" steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 diff --git a/.github/workflows/setup-dev-drive.ps1 b/.github/workflows/setup-dev-drive.ps1 index e0e2a765b..e003cc359 100644 --- a/.github/workflows/setup-dev-drive.ps1 +++ b/.github/workflows/setup-dev-drive.ps1 @@ -1,13 +1,43 @@ # Configures a drive for testing in CI. +# +# When using standard GitHub Actions runners, a `D:` drive is present and has +# similar or better performance characteristics than a ReFS dev drive. Sometimes +# using a larger runner is still more performant (e.g., when running the test +# suite) and we need to create a dev drive. This script automatically configures +# the appropriate drive. +# +# When using GitHub Actions' "larger runners", the `D:` drive is not present and +# we create a DevDrive mount on `C:`. This is purported to be more performant +# than an ReFS drive, though we did not see a change when we switched over. +# +# When using Depot runners, the underling infrastructure is EC2, which does not +# support Hyper-V. The `New-VHD` commandlet only works with Hyper-V, but we can +# create a ReFS drive using `diskpart` and `format` directory. We cannot use a +# DevDrive, as that also requires Hyper-V. The Depot runners use `D:` already, +# so we must check if it's a Depot runner first, and we use `V:` as the target +# instead. -# When not using a GitHub Actions "larger runner", the `D:` drive is present and -# has similar or better performance characteristics than a ReFS dev drive. -# Sometimes using a larger runner is still more performant (e.g., when running -# the test suite) and we need to create a dev drive. This script automatically -# configures the appropriate drive. -# Note we use `Get-PSDrive` is not sufficient because the drive letter is assigned. -if (Test-Path "D:\") { +if ($env:DEPOT_RUNNER -eq "1") { + Write-Output "DEPOT_RUNNER detected, setting up custom dev drive..." + + # Create VHD and configure drive using diskpart + $vhdPath = "C:\uv_dev_drive.vhdx" + @" +create vdisk file="$vhdPath" maximum=20480 type=expandable +attach vdisk +create partition primary +active +assign letter=V +"@ | diskpart + + # Format the drive as ReFS + format V: /fs:ReFS /q /y + $Drive = "V:" + + Write-Output "Custom dev drive created at $Drive" +} elseif (Test-Path "D:\") { + # Note `Get-PSDrive` is not sufficient because the drive letter is assigned. Write-Output "Using existing drive at D:" $Drive = "D:" } else { @@ -61,4 +91,3 @@ Write-Output ` "UV_WORKSPACE=$($Drive)/uv" ` "PATH=$($Drive)/.cargo/bin;$env:PATH" ` >> $env:GITHUB_ENV - From 1fc65a1d9de51b2dd567733208f4595dc442db89 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 18 Jun 2025 11:30:37 -0500 Subject: [PATCH 019/349] Publish to DockerHub (#14088) The primary motivation here is to avoid confusion with non-official repositories, e.g., https://github.com/astral-sh/uv/issues/13958 which could lead to attacks against our users. Resolves - https://github.com/astral-sh/uv/issues/12679 - #8699 --- .github/workflows/build-docker.yml | 34 ++++++++++++++++++------------ 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index e310add68..4f19fb3df 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -37,7 +37,8 @@ on: - .github/workflows/build-docker.yml env: - UV_BASE_IMG: ghcr.io/${{ github.repository_owner }}/uv + UV_GHCR_IMAGE: ghcr.io/${{ github.repository_owner }}/uv + UV_DOCKERHUB_IMAGE: docker.io/astral/uv jobs: docker-plan: @@ -84,13 +85,12 @@ jobs: with: submodules: recursive - # Login to DockerHub first, to avoid rate-limiting + # Login to DockerHub (when not pushing, it's to avoid rate-limiting) - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 - # PRs from forks don't have access to secrets, disable this step in that case. if: ${{ github.event.pull_request.head.repo.full_name == 'astral-sh/uv' }} with: - username: astralshbot - password: ${{ secrets.DOCKERHUB_TOKEN_RO }} + username: ${{ needs.docker-plan.outputs.push == 'true' && 'astral' || 'astralshbot' }} + password: ${{ needs.docker-plan.outputs.push == 'true' && secrets.DOCKERHUB_TOKEN_RW || secrets.DOCKERHUB_TOKEN_RO }} - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 with: @@ -117,7 +117,9 @@ jobs: id: meta uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0 with: - images: ${{ env.UV_BASE_IMG }} + images: | + ${{ env.UV_GHCR_IMAGE }} + ${{ env.UV_DOCKERHUB_IMAGE }} # Defining this makes sure the org.opencontainers.image.version OCI label becomes the actual release version and not the branch name tags: | type=raw,value=dry-run,enable=${{ needs.docker-plan.outputs.push == 'false' }} @@ -186,12 +188,12 @@ jobs: - python:3.9-slim-bookworm,python3.9-bookworm-slim - python:3.8-slim-bookworm,python3.8-bookworm-slim steps: - # Login to DockerHub first, to avoid rate-limiting + # Login to DockerHub (when not pushing, it's to avoid rate-limiting) - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 if: ${{ github.event.pull_request.head.repo.full_name == 'astral-sh/uv' }} with: - username: astralshbot - password: ${{ secrets.DOCKERHUB_TOKEN_RO }} + username: ${{ needs.docker-plan.outputs.push == 'true' && 'astral' || 'astralshbot' }} + password: ${{ needs.docker-plan.outputs.push == 'true' && secrets.DOCKERHUB_TOKEN_RW || secrets.DOCKERHUB_TOKEN_RO }} - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 with: @@ -212,7 +214,7 @@ jobs: # Generate Dockerfile content cat < Dockerfile FROM ${BASE_IMAGE} - COPY --from=${{ env.UV_BASE_IMG }}:latest /uv /uvx /usr/local/bin/ + COPY --from=${{ env.UV_GHCR_IMAGE }}:latest /uv /uvx /usr/local/bin/ ENTRYPOINT [] CMD ["/usr/local/bin/uv"] EOF @@ -245,7 +247,9 @@ jobs: env: DOCKER_METADATA_ANNOTATIONS_LEVELS: index with: - images: ${{ env.UV_BASE_IMG }} + images: | + ${{ env.UV_GHCR_IMAGE }} + ${{ env.UV_DOCKERHUB_IMAGE }} flavor: | latest=false tags: | @@ -266,7 +270,7 @@ jobs: - name: Generate artifact attestation uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0 with: - subject-name: ${{ env.UV_BASE_IMG }} + subject-name: ${{ env.UV_GHCR_IMAGE }} subject-digest: ${{ steps.build-and-push.outputs.digest }} # Re-tag the base image, to ensure it's shown as the newest on the registry UI @@ -289,12 +293,16 @@ jobs: - name: Push tags env: - IMAGE: ${{ env.UV_BASE_IMG }} + IMAGE: ${{ env.UV_GHCR_IMAGE }} DIGEST: ${{ needs.docker-publish-base.outputs.image-digest }} TAGS: ${{ needs.docker-publish-base.outputs.image-tags }} run: | docker pull "${IMAGE}@${DIGEST}" for tag in $TAGS; do + # Skip re-tag for DockerHub + if [[ "$tag" == "${{ env.UV_DOCKERHUB_IMAGE }}"* ]]; then + continue + fi docker tag "${IMAGE}@${DIGEST}" "${tag}" docker push "${tag}" done From e1046242e7449dabd6af61df26187e1848c3ec55 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 18 Jun 2025 13:46:30 -0500 Subject: [PATCH 020/349] Fix Docker attestations (#14133) These regressed in #14088 and were found during my test publish from a fork. --- .github/workflows/build-docker.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 4f19fb3df..1f5229aef 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -73,8 +73,9 @@ jobs: runs-on: ubuntu-latest permissions: contents: read - id-token: write # for Depot OIDC - packages: write # for GHCR + id-token: write # for Depot OIDC and GHCR signing + packages: write # for GHCR image pushes + attestations: write # for GHCR attestations environment: name: release outputs: @@ -141,7 +142,7 @@ jobs: if: ${{ needs.docker-plan.outputs.push == 'true' }} uses: actions/attest-build-provenance@c074443f1aee8d4aeeae555aebba3282517141b2 # v2.2.3 with: - subject-name: ${{ env.UV_BASE_IMG }} + subject-name: ${{ env.UV_GHCR_IMAGE }} subject-digest: ${{ steps.build.outputs.digest }} docker-publish-extra: @@ -154,9 +155,9 @@ jobs: - docker-publish-base if: ${{ needs.docker-plan.outputs.push == 'true' }} permissions: - packages: write - attestations: write # needed to push image attestations to the Github attestation store - id-token: write # needed for signing the images with GitHub OIDC Token + id-token: write # for Depot OIDC and GHCR signing + packages: write # for GHCR image pushes + attestations: write # for GHCR attestations strategy: fail-fast: false matrix: From c3e4b6380608a0425982f3082a5403e7249a7532 Mon Sep 17 00:00:00 2001 From: Jack O'Connor Date: Wed, 18 Jun 2025 12:12:56 -0700 Subject: [PATCH 021/349] document the way member sources shadow workspace sources Closes https://github.com/astral-sh/uv/issues/14093. --- docs/concepts/projects/workspaces.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/concepts/projects/workspaces.md b/docs/concepts/projects/workspaces.md index 942cea8c2..4b2d670b4 100644 --- a/docs/concepts/projects/workspaces.md +++ b/docs/concepts/projects/workspaces.md @@ -113,6 +113,13 @@ build-backend = "hatchling.build" Every workspace member would, by default, install `tqdm` from GitHub, unless a specific member overrides the `tqdm` entry in its own `tool.uv.sources` table. +!!! note + + If a workspace member provides `tool.uv.sources` for some dependency, it will ignore any + `tool.uv.sources` for the same dependency in the workspace root, even if the member's source is + limited by a [marker](dependencies.md#platform-specific-sources) that doesn't match the current + platform. + ## Workspace layouts The most common workspace layout can be thought of as a root project with a series of accompanying From cc8d5a92154717f5c6baccb21b45cc1cd0dccca1 Mon Sep 17 00:00:00 2001 From: Jack O'Connor Date: Thu, 19 Jun 2025 14:47:22 -0700 Subject: [PATCH 022/349] handle an existing shebang in `uv init --script` (#14141) Closes https://github.com/astral-sh/uv/issues/14085. --- Cargo.lock | 2 ++ crates/uv-scripts/Cargo.toml | 2 ++ crates/uv-scripts/src/lib.rs | 23 +++++++++++--- crates/uv-warnings/src/lib.rs | 8 ++--- crates/uv/tests/it/init.rs | 59 +++++++++++++++++++++++++++++++++++ docs/guides/scripts.md | 2 ++ 6 files changed, 88 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 264a17e55..77a17a4f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5742,6 +5742,7 @@ dependencies = [ "fs-err 3.1.1", "indoc", "memchr", + "regex", "serde", "thiserror 2.0.12", "toml", @@ -5751,6 +5752,7 @@ dependencies = [ "uv-pypi-types", "uv-redacted", "uv-settings", + "uv-warnings", "uv-workspace", ] diff --git a/crates/uv-scripts/Cargo.toml b/crates/uv-scripts/Cargo.toml index 993633918..124eb1fea 100644 --- a/crates/uv-scripts/Cargo.toml +++ b/crates/uv-scripts/Cargo.toml @@ -16,11 +16,13 @@ uv-pep508 = { workspace = true } uv-pypi-types = { workspace = true } uv-redacted = { workspace = true } uv-settings = { workspace = true } +uv-warnings = { workspace = true } uv-workspace = { workspace = true } fs-err = { workspace = true, features = ["tokio"] } indoc = { workspace = true } memchr = { workspace = true } +regex = { workspace = true } serde = { workspace = true, features = ["derive"] } thiserror = { workspace = true } toml = { workspace = true } diff --git a/crates/uv-scripts/src/lib.rs b/crates/uv-scripts/src/lib.rs index 1023b4141..b80cdc219 100644 --- a/crates/uv-scripts/src/lib.rs +++ b/crates/uv-scripts/src/lib.rs @@ -14,6 +14,7 @@ use uv_pep508::PackageName; use uv_pypi_types::VerbatimParsedUrl; use uv_redacted::DisplaySafeUrl; use uv_settings::{GlobalOptions, ResolverInstallerOptions}; +use uv_warnings::warn_user; use uv_workspace::pyproject::Sources; static FINDER: LazyLock = LazyLock::new(|| Finder::new(b"# /// script")); @@ -238,11 +239,25 @@ impl Pep723Script { let metadata = serialize_metadata(&default_metadata); let script = if let Some(existing_contents) = existing_contents { + let (mut shebang, contents) = extract_shebang(&existing_contents)?; + if !shebang.is_empty() { + shebang.push_str("\n#\n"); + // If the shebang doesn't contain `uv`, it's probably something like + // `#! /usr/bin/env python`, which isn't going to respect the inline metadata. + // Issue a warning for users who might not know that. + // TODO: There are a lot of mistakes we could consider detecting here, like + // `uv run` without `--script` when the file doesn't end in `.py`. + if !regex::Regex::new(r"\buv\b").unwrap().is_match(&shebang) { + warn_user!( + "If you execute {} directly, it might ignore its inline metadata.\nConsider replacing its shebang with: {}", + file.to_string_lossy().cyan(), + "#!/usr/bin/env -S uv run --script".cyan(), + ); + } + } indoc::formatdoc! {r" - {metadata} - {content} - ", - content = String::from_utf8(existing_contents).map_err(|err| Pep723Error::Utf8(err.utf8_error()))?} + {shebang}{metadata} + {contents}" } } else { indoc::formatdoc! {r#" {metadata} diff --git a/crates/uv-warnings/src/lib.rs b/crates/uv-warnings/src/lib.rs index 9ed4c646e..5f2287cac 100644 --- a/crates/uv-warnings/src/lib.rs +++ b/crates/uv-warnings/src/lib.rs @@ -24,7 +24,7 @@ pub fn disable() { /// Warn a user, if warnings are enabled. #[macro_export] macro_rules! warn_user { - ($($arg:tt)*) => { + ($($arg:tt)*) => {{ use $crate::anstream::eprintln; use $crate::owo_colors::OwoColorize; @@ -33,7 +33,7 @@ macro_rules! warn_user { let formatted = message.bold(); eprintln!("{}{} {formatted}", "warning".yellow().bold(), ":".bold()); } - }; + }}; } pub static WARNINGS: LazyLock>> = LazyLock::new(Mutex::default); @@ -42,7 +42,7 @@ pub static WARNINGS: LazyLock>> = LazyLock::new(Mutex::d /// message. #[macro_export] macro_rules! warn_user_once { - ($($arg:tt)*) => { + ($($arg:tt)*) => {{ use $crate::anstream::eprintln; use $crate::owo_colors::OwoColorize; @@ -54,5 +54,5 @@ macro_rules! warn_user_once { } } } - }; + }}; } diff --git a/crates/uv/tests/it/init.rs b/crates/uv/tests/it/init.rs index e9e5e54a7..c5993d670 100644 --- a/crates/uv/tests/it/init.rs +++ b/crates/uv/tests/it/init.rs @@ -929,6 +929,65 @@ fn init_script_file_conflicts() -> Result<()> { Ok(()) } +// Init script should not trash an existing shebang. +#[test] +fn init_script_shebang() -> Result<()> { + let context = TestContext::new("3.12"); + + let script_path = context.temp_dir.child("script.py"); + + let contents = "#! /usr/bin/env python3\nprint(\"Hello, world!\")"; + fs_err::write(&script_path, contents)?; + uv_snapshot!(context.filters(), context.init().arg("--script").arg("script.py"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: If you execute script.py directly, it might ignore its inline metadata. + Consider replacing its shebang with: #!/usr/bin/env -S uv run --script + Initialized script at `script.py` + "); + let resulting_script = fs_err::read_to_string(&script_path)?; + assert_snapshot!(resulting_script, @r#" + #! /usr/bin/env python3 + # + # /// script + # requires-python = ">=3.12" + # dependencies = [] + # /// + + print("Hello, world!") + "# + ); + + // If the shebang already contains `uv`, the result is the same, but we suppress the warning. + let contents = "#!/usr/bin/env -S uv run --script\nprint(\"Hello, world!\")"; + fs_err::write(&script_path, contents)?; + uv_snapshot!(context.filters(), context.init().arg("--script").arg("script.py"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Initialized script at `script.py` + "); + let resulting_script = fs_err::read_to_string(&script_path)?; + assert_snapshot!(resulting_script, @r#" + #!/usr/bin/env -S uv run --script + # + # /// script + # requires-python = ">=3.12" + # dependencies = [] + # /// + + print("Hello, world!") + "# + ); + + Ok(()) +} + /// Run `uv init --lib` with an existing py.typed file #[test] fn init_py_typed_exists() -> Result<()> { diff --git a/docs/guides/scripts.md b/docs/guides/scripts.md index 7142db155..26d85e76d 100644 --- a/docs/guides/scripts.md +++ b/docs/guides/scripts.md @@ -241,10 +241,12 @@ Declaration of dependencies is also supported in this context, for example: ```python title="example" #!/usr/bin/env -S uv run --script +# # /// script # requires-python = ">=3.12" # dependencies = ["httpx"] # /// + import httpx print(httpx.get("https://example.com")) From 62365d4ec86c146fc1d2050bff3fa70e53abfa08 Mon Sep 17 00:00:00 2001 From: John Mumm Date: Fri, 20 Jun 2025 03:21:32 -0400 Subject: [PATCH 023/349] Support netrc and same-origin credential propagation on index redirects (#14126) This PR is a combination of #12920 and #13754. Prior to these changes, following a redirect when searching indexes would bypass our authentication middleware. This PR updates uv to support propagating credentials through our middleware on same-origin redirects and to support netrc credentials for both same- and cross-origin redirects. It does not handle the case described in #11097 where the redirect location itself includes credentials (e.g., `https://user:pass@redirect-location.com`). That will be addressed in follow-up work. This includes unit tests for the new redirect logic and integration tests for credential propagation. The automated external registries test is also passing for AWS CodeArtifact, Azure Artifacts, GCP Artifact Registry, JFrog Artifactory, GitLab, Cloudsmith, and Gemfury. --- Cargo.lock | 1 + crates/uv-client/Cargo.toml | 1 + crates/uv-client/src/base_client.rs | 574 ++++++++++++++++++++++- crates/uv-client/src/cached_client.rs | 2 - crates/uv-client/src/lib.rs | 2 +- crates/uv-client/src/registry_client.rs | 243 +++++++++- crates/uv-distribution/src/source/mod.rs | 7 +- crates/uv-git/src/resolver.rs | 4 +- crates/uv-publish/src/lib.rs | 27 +- crates/uv/tests/it/common/mod.rs | 4 +- crates/uv/tests/it/edit.rs | 191 ++++++++ 11 files changed, 1022 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 77a17a4f3..a30a0cbe1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4946,6 +4946,7 @@ dependencies = [ "uv-torch", "uv-version", "uv-warnings", + "wiremock", ] [[package]] diff --git a/crates/uv-client/Cargo.toml b/crates/uv-client/Cargo.toml index 81d1909fe..bc7fc611f 100644 --- a/crates/uv-client/Cargo.toml +++ b/crates/uv-client/Cargo.toml @@ -65,3 +65,4 @@ hyper = { version = "1.4.1", features = ["server", "http1"] } hyper-util = { version = "0.1.8", features = ["tokio"] } insta = { version = "1.40.0", features = ["filters", "json", "redactions"] } tokio = { workspace = true } +wiremock = { workspace = true } diff --git a/crates/uv-client/src/base_client.rs b/crates/uv-client/src/base_client.rs index f5fda246d..85c384b0d 100644 --- a/crates/uv-client/src/base_client.rs +++ b/crates/uv-client/src/base_client.rs @@ -6,14 +6,23 @@ use std::sync::Arc; use std::time::Duration; use std::{env, io, iter}; +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 itertools::Itertools; -use reqwest::{Client, ClientBuilder, Proxy, Response}; +use reqwest::{Client, ClientBuilder, IntoUrl, Proxy, Request, Response, multipart}; use reqwest_middleware::{ClientWithMiddleware, Middleware}; use reqwest_retry::policies::ExponentialBackoff; use reqwest_retry::{ DefaultRetryableStrategy, RetryTransientMiddleware, Retryable, RetryableStrategy, }; use tracing::{debug, trace}; +use url::ParseError; use url::Url; use uv_auth::{AuthMiddleware, Indexes}; @@ -32,6 +41,10 @@ use crate::middleware::OfflineMiddleware; use crate::tls::read_identity; pub const DEFAULT_RETRIES: u32 = 3; +/// Maximum number of redirects to follow before giving up. +/// +/// This is the default used by [`reqwest`]. +const DEFAULT_MAX_REDIRECTS: u32 = 10; /// Selectively skip parts or the entire auth middleware. #[derive(Debug, Clone, Copy, Default)] @@ -61,6 +74,31 @@ pub struct BaseClientBuilder<'a> { default_timeout: Duration, extra_middleware: Option, proxies: Vec, + redirect_policy: RedirectPolicy, + /// Whether credentials should be propagated during cross-origin redirects. + /// + /// A policy allowing propagation is insecure and should only be available for test code. + cross_origin_credential_policy: CrossOriginCredentialsPolicy, +} + +/// The policy for handling HTTP redirects. +#[derive(Debug, Default, Clone, Copy)] +pub enum RedirectPolicy { + /// Use reqwest's built-in redirect handling. This bypasses our custom middleware + /// on redirect. + #[default] + BypassMiddleware, + /// Handle redirects manually, re-triggering our custom middleware for each request. + RetriggerMiddleware, +} + +impl RedirectPolicy { + pub fn reqwest_policy(self) -> reqwest::redirect::Policy { + match self { + RedirectPolicy::BypassMiddleware => reqwest::redirect::Policy::default(), + RedirectPolicy::RetriggerMiddleware => reqwest::redirect::Policy::none(), + } + } } /// A list of user-defined middlewares to be applied to the client. @@ -96,6 +134,8 @@ impl BaseClientBuilder<'_> { default_timeout: Duration::from_secs(30), extra_middleware: None, proxies: vec![], + redirect_policy: RedirectPolicy::default(), + cross_origin_credential_policy: CrossOriginCredentialsPolicy::Secure, } } } @@ -173,6 +213,24 @@ impl<'a> BaseClientBuilder<'a> { self } + #[must_use] + pub fn redirect(mut self, policy: RedirectPolicy) -> Self { + self.redirect_policy = policy; + self + } + + /// Allows credentials to be propagated on cross-origin redirects. + /// + /// WARNING: This should only be available for tests. In production code, propagating credentials + /// during cross-origin redirects can lead to security vulnerabilities including credential + /// leakage to untrusted domains. + #[cfg(test)] + #[must_use] + pub fn allow_cross_origin_credentials(mut self) -> Self { + self.cross_origin_credential_policy = CrossOriginCredentialsPolicy::Insecure; + self + } + pub fn is_offline(&self) -> bool { matches!(self.connectivity, Connectivity::Offline) } @@ -229,6 +287,7 @@ impl<'a> BaseClientBuilder<'a> { timeout, ssl_cert_file_exists, Security::Secure, + self.redirect_policy, ); // Create an insecure client that accepts invalid certificates. @@ -237,11 +296,20 @@ impl<'a> BaseClientBuilder<'a> { timeout, ssl_cert_file_exists, Security::Insecure, + self.redirect_policy, ); // Wrap in any relevant middleware and handle connectivity. - let client = self.apply_middleware(raw_client.clone()); - let dangerous_client = self.apply_middleware(raw_dangerous_client.clone()); + let client = RedirectClientWithMiddleware { + client: self.apply_middleware(raw_client.clone()), + redirect_policy: self.redirect_policy, + cross_origin_credentials_policy: self.cross_origin_credential_policy, + }; + let dangerous_client = RedirectClientWithMiddleware { + client: self.apply_middleware(raw_dangerous_client.clone()), + redirect_policy: self.redirect_policy, + cross_origin_credentials_policy: self.cross_origin_credential_policy, + }; BaseClient { connectivity: self.connectivity, @@ -258,8 +326,16 @@ impl<'a> BaseClientBuilder<'a> { /// Share the underlying client between two different middleware configurations. pub fn wrap_existing(&self, existing: &BaseClient) -> BaseClient { // Wrap in any relevant middleware and handle connectivity. - let client = self.apply_middleware(existing.raw_client.clone()); - let dangerous_client = self.apply_middleware(existing.raw_dangerous_client.clone()); + let client = RedirectClientWithMiddleware { + client: self.apply_middleware(existing.raw_client.clone()), + redirect_policy: self.redirect_policy, + cross_origin_credentials_policy: self.cross_origin_credential_policy, + }; + let dangerous_client = RedirectClientWithMiddleware { + client: self.apply_middleware(existing.raw_dangerous_client.clone()), + redirect_policy: self.redirect_policy, + cross_origin_credentials_policy: self.cross_origin_credential_policy, + }; BaseClient { connectivity: self.connectivity, @@ -279,6 +355,7 @@ impl<'a> BaseClientBuilder<'a> { timeout: Duration, ssl_cert_file_exists: bool, security: Security, + redirect_policy: RedirectPolicy, ) -> Client { // Configure the builder. let client_builder = ClientBuilder::new() @@ -286,7 +363,8 @@ impl<'a> BaseClientBuilder<'a> { .user_agent(user_agent) .pool_max_idle_per_host(20) .read_timeout(timeout) - .tls_built_in_root_certs(false); + .tls_built_in_root_certs(false) + .redirect(redirect_policy.reqwest_policy()); // If necessary, accept invalid certificates. let client_builder = match security { @@ -381,9 +459,9 @@ impl<'a> BaseClientBuilder<'a> { #[derive(Debug, Clone)] pub struct BaseClient { /// The underlying HTTP client that enforces valid certificates. - client: ClientWithMiddleware, + client: RedirectClientWithMiddleware, /// The underlying HTTP client that accepts invalid certificates. - dangerous_client: ClientWithMiddleware, + dangerous_client: RedirectClientWithMiddleware, /// The HTTP client without middleware. raw_client: Client, /// The HTTP client that accepts invalid certificates without middleware. @@ -408,7 +486,7 @@ enum Security { impl BaseClient { /// Selects the appropriate client based on the host's trustworthiness. - pub fn for_host(&self, url: &DisplaySafeUrl) -> &ClientWithMiddleware { + pub fn for_host(&self, url: &DisplaySafeUrl) -> &RedirectClientWithMiddleware { if self.disable_ssl(url) { &self.dangerous_client } else { @@ -416,6 +494,12 @@ impl BaseClient { } } + /// Executes a request, applying redirect policy. + pub async fn execute(&self, req: Request) -> reqwest_middleware::Result { + let client = self.for_host(&DisplaySafeUrl::from(req.url().clone())); + client.execute(req).await + } + /// Returns `true` if the host is trusted to use the insecure client. pub fn disable_ssl(&self, url: &DisplaySafeUrl) -> bool { self.allow_insecure_host @@ -439,6 +523,316 @@ impl BaseClient { } } +/// Wrapper around [`ClientWithMiddleware`] that manages redirects. +#[derive(Debug, Clone)] +pub struct RedirectClientWithMiddleware { + client: ClientWithMiddleware, + redirect_policy: RedirectPolicy, + /// Whether credentials should be preserved during cross-origin redirects. + /// + /// WARNING: This should only be available for tests. In production code, preserving credentials + /// during cross-origin redirects can lead to security vulnerabilities including credential + /// leakage to untrusted domains. + cross_origin_credentials_policy: CrossOriginCredentialsPolicy, +} + +impl RedirectClientWithMiddleware { + /// Convenience method to make a `GET` request to a URL. + pub fn get(&self, url: U) -> RequestBuilder { + RequestBuilder::new(self.client.get(url), self) + } + + /// Convenience method to make a `POST` request to a URL. + pub fn post(&self, url: U) -> RequestBuilder { + RequestBuilder::new(self.client.post(url), self) + } + + /// Convenience method to make a `HEAD` request to a URL. + pub fn head(&self, url: U) -> RequestBuilder { + RequestBuilder::new(self.client.head(url), self) + } + + /// Executes a request, applying the redirect policy. + pub async fn execute(&self, req: Request) -> reqwest_middleware::Result { + match self.redirect_policy { + RedirectPolicy::BypassMiddleware => self.client.execute(req).await, + RedirectPolicy::RetriggerMiddleware => self.execute_with_redirect_handling(req).await, + } + } + + /// Executes a request. If the response is a redirect (one of HTTP 301, 302, 303, 307, or 308), the + /// request is executed again with the redirect location URL (up to a maximum number of + /// redirects). + /// + /// Unlike the built-in reqwest redirect policies, this sends the redirect request through the + /// entire middleware pipeline again. + /// + /// See RFC 7231 7.1.2 for details on + /// redirect semantics. + async fn execute_with_redirect_handling( + &self, + req: Request, + ) -> reqwest_middleware::Result { + let mut request = req; + let mut redirects = 0; + let max_redirects = DEFAULT_MAX_REDIRECTS; + + loop { + let result = self + .client + .execute(request.try_clone().expect("HTTP request must be cloneable")) + .await; + let Ok(response) = result else { + return result; + }; + + if redirects >= max_redirects { + return Ok(response); + } + + let Some(redirect_request) = + request_into_redirect(request, &response, self.cross_origin_credentials_policy)? + else { + return Ok(response); + }; + + redirects += 1; + request = redirect_request; + } + } + + pub fn raw_client(&self) -> &ClientWithMiddleware { + &self.client + } +} + +impl From for ClientWithMiddleware { + fn from(item: RedirectClientWithMiddleware) -> ClientWithMiddleware { + item.client + } +} + +/// Check if this is should be a redirect and, if so, return a new redirect request. +/// +/// This implementation is based on the [`reqwest`] crate redirect implementation. +/// It takes ownership of the original [`Request`] and mutates it to create the new +/// redirect [`Request`]. +fn request_into_redirect( + mut req: Request, + res: &Response, + cross_origin_credentials_policy: CrossOriginCredentialsPolicy, +) -> reqwest_middleware::Result> { + let original_req_url = DisplaySafeUrl::from(req.url().clone()); + let status = res.status(); + let should_redirect = match status { + StatusCode::MOVED_PERMANENTLY + | StatusCode::FOUND + | StatusCode::TEMPORARY_REDIRECT + | StatusCode::PERMANENT_REDIRECT => true, + StatusCode::SEE_OTHER => { + // Per RFC 7231, HTTP 303 is intended for the user agent + // to perform a GET or HEAD request to the redirect target. + // Historically, some browsers also changed method from POST + // to GET on 301 or 302, but this is not required by RFC 7231 + // and was not intended by the HTTP spec. + *req.body_mut() = None; + for header in &[ + TRANSFER_ENCODING, + CONTENT_ENCODING, + CONTENT_TYPE, + CONTENT_LENGTH, + ] { + req.headers_mut().remove(header); + } + + match *req.method() { + Method::GET | Method::HEAD => {} + _ => { + *req.method_mut() = Method::GET; + } + } + true + } + _ => false, + }; + if !should_redirect { + return Ok(None); + } + + let location = res + .headers() + .get(LOCATION) + .ok_or(reqwest_middleware::Error::Middleware(anyhow!( + "Server returned redirect (HTTP {status}) without destination URL. This may indicate a server configuration issue" + )))? + .to_str() + .map_err(|_| { + reqwest_middleware::Error::Middleware(anyhow!( + "Invalid HTTP {status} 'Location' value: must only contain visible ascii characters" + )) + })?; + + let mut redirect_url = match DisplaySafeUrl::parse(location) { + Ok(url) => url, + // Per RFC 7231, URLs should be resolved against the request URL. + Err(ParseError::RelativeUrlWithoutBase) => original_req_url.join(location).map_err(|err| { + reqwest_middleware::Error::Middleware(anyhow!( + "Invalid HTTP {status} 'Location' value `{location}` relative to `{original_req_url}`: {err}" + )) + })?, + Err(err) => { + return Err(reqwest_middleware::Error::Middleware(anyhow!( + "Invalid HTTP {status} 'Location' value `{location}`: {err}" + ))); + } + }; + // Per RFC 7231, fragments must be propagated + if let Some(fragment) = original_req_url.fragment() { + redirect_url.set_fragment(Some(fragment)); + } + + // Ensure the URL is a valid HTTP URI. + if let Err(err) = redirect_url.as_str().parse::() { + return Err(reqwest_middleware::Error::Middleware(anyhow!( + "HTTP {status} 'Location' value `{redirect_url}` is not a valid HTTP URI: {err}" + ))); + } + + if redirect_url.scheme() != "http" && redirect_url.scheme() != "https" { + return Err(reqwest_middleware::Error::Middleware(anyhow!( + "Invalid HTTP {status} 'Location' value `{redirect_url}`: scheme needs to be https or http" + ))); + } + + let mut headers = HeaderMap::new(); + std::mem::swap(req.headers_mut(), &mut headers); + + let cross_host = redirect_url.host_str() != original_req_url.host_str() + || redirect_url.port_or_known_default() != original_req_url.port_or_known_default(); + if cross_host { + if cross_origin_credentials_policy == CrossOriginCredentialsPolicy::Secure { + debug!("Received a cross-origin redirect. Removing sensitive headers."); + headers.remove(AUTHORIZATION); + headers.remove(COOKIE); + headers.remove(PROXY_AUTHORIZATION); + headers.remove(WWW_AUTHENTICATE); + } + // If the redirect request is not a cross-origin request and the original request already + // had a Referer header, attempt to set the Referer header for the redirect request. + } else if headers.contains_key(REFERER) { + if let Some(referer) = make_referer(&redirect_url, &original_req_url) { + headers.insert(REFERER, referer); + } + } + + std::mem::swap(req.headers_mut(), &mut headers); + *req.url_mut() = Url::from(redirect_url); + debug!( + "Received HTTP {status}. Redirecting to {}", + DisplaySafeUrl::ref_cast(req.url()) + ); + Ok(Some(req)) +} + +/// Return a Referer [`HeaderValue`] according to RFC 7231. +/// +/// Return [`None`] if https has been downgraded in the redirect location. +fn make_referer( + redirect_url: &DisplaySafeUrl, + original_url: &DisplaySafeUrl, +) -> Option { + if redirect_url.scheme() == "http" && original_url.scheme() == "https" { + return None; + } + + let mut referer = original_url.clone(); + referer.remove_credentials(); + referer.set_fragment(None); + referer.as_str().parse().ok() +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] +pub(crate) enum CrossOriginCredentialsPolicy { + /// Do not propagate credentials on cross-origin requests. + #[default] + Secure, + + /// Propagate credentials on cross-origin requests. + /// + /// WARNING: This should only be available for tests. In production code, preserving credentials + /// during cross-origin redirects can lead to security vulnerabilities including credential + /// leakage to untrusted domains. + #[cfg(test)] + Insecure, +} + +/// A builder to construct the properties of a `Request`. +/// +/// This wraps [`reqwest_middleware::RequestBuilder`] to ensure that the [`BaseClient`] +/// redirect policy is respected if `send()` is called. +#[derive(Debug)] +#[must_use] +pub struct RequestBuilder<'a> { + builder: reqwest_middleware::RequestBuilder, + client: &'a RedirectClientWithMiddleware, +} + +impl<'a> RequestBuilder<'a> { + pub fn new( + builder: reqwest_middleware::RequestBuilder, + client: &'a RedirectClientWithMiddleware, + ) -> Self { + Self { builder, client } + } + + /// Add a `Header` to this Request. + pub fn header(mut self, key: K, value: V) -> Self + where + HeaderName: TryFrom, + >::Error: Into, + HeaderValue: TryFrom, + >::Error: Into, + { + self.builder = self.builder.header(key, value); + self + } + + /// Add a set of Headers to the existing ones on this Request. + /// + /// The headers will be merged in to any already set. + pub fn headers(mut self, headers: HeaderMap) -> Self { + self.builder = self.builder.headers(headers); + self + } + + #[cfg(not(target_arch = "wasm32"))] + pub fn version(mut self, version: reqwest::Version) -> Self { + self.builder = self.builder.version(version); + self + } + + #[cfg_attr(docsrs, doc(cfg(feature = "multipart")))] + pub fn multipart(mut self, multipart: multipart::Form) -> Self { + self.builder = self.builder.multipart(multipart); + self + } + + /// Build a `Request`. + pub fn build(self) -> reqwest::Result { + self.builder.build() + } + + /// Constructs the Request and sends it to the target URL, returning a + /// future Response. + pub async fn send(self) -> reqwest_middleware::Result { + self.client.execute(self.build()?).await + } + + pub fn raw_builder(&self) -> &reqwest_middleware::RequestBuilder { + &self.builder + } +} + /// Extends [`DefaultRetryableStrategy`], to log transient request failures and additional retry cases. pub struct UvRetryableStrategy; @@ -528,3 +922,165 @@ fn find_source(orig: &dyn Error) -> Option<&E> { fn find_sources(orig: &dyn Error) -> impl Iterator { iter::successors(find_source::(orig), |&err| find_source(err)) } + +#[cfg(test)] +mod tests { + use super::*; + use anyhow::Result; + + use reqwest::{Client, Method}; + 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] { + let server = MockServer::start().await; + Mock::given(method("GET")) + .respond_with( + ResponseTemplate::new(*status) + .insert_header("location", format!("{}/redirect", server.uri())), + ) + .mount(&server) + .await; + + let request = Client::new() + .get(server.uri()) + .basic_auth("username", Some("password")) + .build() + .unwrap(); + + assert!(request.headers().contains_key(AUTHORIZATION)); + + let response = Client::builder() + .redirect(reqwest::redirect::Policy::none()) + .build() + .unwrap() + .execute(request.try_clone().unwrap()) + .await + .unwrap(); + + let redirect_request = + request_into_redirect(request, &response, CrossOriginCredentialsPolicy::Secure)? + .unwrap(); + assert!(redirect_request.headers().contains_key(AUTHORIZATION)); + } + + Ok(()) + } + + #[tokio::test] + async fn test_redirect_removes_authorization_header_on_cross_origin() -> Result<()> { + for status in &[301, 302, 303, 307, 308] { + let server = MockServer::start().await; + Mock::given(method("GET")) + .respond_with( + ResponseTemplate::new(*status) + .insert_header("location", "https://cross-origin.com/simple"), + ) + .mount(&server) + .await; + + let request = Client::new() + .get(server.uri()) + .basic_auth("username", Some("password")) + .build() + .unwrap(); + + assert!(request.headers().contains_key(AUTHORIZATION)); + + let response = Client::builder() + .redirect(reqwest::redirect::Policy::none()) + .build() + .unwrap() + .execute(request.try_clone().unwrap()) + .await + .unwrap(); + + let redirect_request = + request_into_redirect(request, &response, CrossOriginCredentialsPolicy::Secure)? + .unwrap(); + assert!(!redirect_request.headers().contains_key(AUTHORIZATION)); + } + + Ok(()) + } + + #[tokio::test] + async fn test_redirect_303_changes_post_to_get() -> Result<()> { + let server = MockServer::start().await; + Mock::given(method("POST")) + .respond_with( + ResponseTemplate::new(303) + .insert_header("location", format!("{}/redirect", server.uri())), + ) + .mount(&server) + .await; + + let request = Client::new() + .post(server.uri()) + .basic_auth("username", Some("password")) + .build() + .unwrap(); + + assert_eq!(request.method(), Method::POST); + + let response = Client::builder() + .redirect(reqwest::redirect::Policy::none()) + .build() + .unwrap() + .execute(request.try_clone().unwrap()) + .await + .unwrap(); + + let redirect_request = + request_into_redirect(request, &response, CrossOriginCredentialsPolicy::Secure)? + .unwrap(); + assert_eq!(redirect_request.method(), Method::GET); + + Ok(()) + } + + #[tokio::test] + async fn test_redirect_no_referer_if_disabled() -> Result<()> { + for status in &[301, 302, 303, 307, 308] { + let server = MockServer::start().await; + Mock::given(method("GET")) + .respond_with( + ResponseTemplate::new(*status) + .insert_header("location", format!("{}/redirect", server.uri())), + ) + .mount(&server) + .await; + + let request = Client::builder() + .referer(false) + .build() + .unwrap() + .get(server.uri()) + .basic_auth("username", Some("password")) + .build() + .unwrap(); + + assert!(!request.headers().contains_key(REFERER)); + + let response = Client::builder() + .redirect(reqwest::redirect::Policy::none()) + .build() + .unwrap() + .execute(request.try_clone().unwrap()) + .await + .unwrap(); + + let redirect_request = + request_into_redirect(request, &response, CrossOriginCredentialsPolicy::Secure)? + .unwrap(); + + assert!(!redirect_request.headers().contains_key(REFERER)); + } + + Ok(()) + } +} diff --git a/crates/uv-client/src/cached_client.rs b/crates/uv-client/src/cached_client.rs index d19f95ec7..ee3314d1c 100644 --- a/crates/uv-client/src/cached_client.rs +++ b/crates/uv-client/src/cached_client.rs @@ -523,7 +523,6 @@ impl CachedClient { debug!("Sending revalidation request for: {url}"); let response = self .0 - .for_host(&url) .execute(req) .instrument(info_span!("revalidation_request", url = url.as_str())) .await @@ -564,7 +563,6 @@ impl CachedClient { let cache_policy_builder = CachePolicyBuilder::new(&req); let response = self .0 - .for_host(&url) .execute(req) .await .map_err(|err| ErrorKind::from_reqwest_middleware(url.clone(), err))?; diff --git a/crates/uv-client/src/lib.rs b/crates/uv-client/src/lib.rs index 3ea33204c..e42c86620 100644 --- a/crates/uv-client/src/lib.rs +++ b/crates/uv-client/src/lib.rs @@ -1,6 +1,6 @@ pub use base_client::{ AuthIntegration, BaseClient, BaseClientBuilder, DEFAULT_RETRIES, ExtraMiddleware, - UvRetryableStrategy, is_extended_transient_error, + RedirectClientWithMiddleware, RequestBuilder, UvRetryableStrategy, is_extended_transient_error, }; pub use cached_client::{CacheControl, CachedClient, CachedClientError, DataWithCachePolicy}; pub use error::{Error, ErrorKind, WrappedReqwestError}; diff --git a/crates/uv-client/src/registry_client.rs b/crates/uv-client/src/registry_client.rs index 6271b7d20..b53e1ed9a 100644 --- a/crates/uv-client/src/registry_client.rs +++ b/crates/uv-client/src/registry_client.rs @@ -10,7 +10,6 @@ use futures::{FutureExt, StreamExt, TryStreamExt}; use http::{HeaderMap, StatusCode}; use itertools::Either; use reqwest::{Proxy, Response}; -use reqwest_middleware::ClientWithMiddleware; use rustc_hash::FxHashMap; use tokio::sync::{Mutex, Semaphore}; use tracing::{Instrument, debug, info_span, instrument, trace, warn}; @@ -35,13 +34,16 @@ use uv_redacted::DisplaySafeUrl; use uv_small_str::SmallString; use uv_torch::TorchStrategy; -use crate::base_client::{BaseClientBuilder, ExtraMiddleware}; +use crate::base_client::{BaseClientBuilder, ExtraMiddleware, RedirectPolicy}; use crate::cached_client::CacheControl; use crate::flat_index::FlatIndexEntry; use crate::html::SimpleHtml; use crate::remote_metadata::wheel_metadata_from_remote_zip; use crate::rkyvutil::OwnedArchive; -use crate::{BaseClient, CachedClient, Error, ErrorKind, FlatIndexClient, FlatIndexEntries}; +use crate::{ + BaseClient, CachedClient, Error, ErrorKind, FlatIndexClient, FlatIndexEntries, + RedirectClientWithMiddleware, +}; /// A builder for an [`RegistryClient`]. #[derive(Debug, Clone)] @@ -149,9 +151,23 @@ impl<'a> RegistryClientBuilder<'a> { self } + /// Allows credentials to be propagated on cross-origin redirects. + /// + /// WARNING: This should only be available for tests. In production code, propagating credentials + /// during cross-origin redirects can lead to security vulnerabilities including credential + /// leakage to untrusted domains. + #[cfg(test)] + #[must_use] + pub fn allow_cross_origin_credentials(mut self) -> Self { + self.base_client_builder = self.base_client_builder.allow_cross_origin_credentials(); + self + } + pub fn build(self) -> RegistryClient { // Build a base client - let builder = self.base_client_builder; + let builder = self + .base_client_builder + .redirect(RedirectPolicy::RetriggerMiddleware); let client = builder.build(); @@ -248,7 +264,7 @@ impl RegistryClient { } /// Return the [`BaseClient`] used by this client. - pub fn uncached_client(&self, url: &DisplaySafeUrl) -> &ClientWithMiddleware { + pub fn uncached_client(&self, url: &DisplaySafeUrl) -> &RedirectClientWithMiddleware { self.client.uncached().for_host(url) } @@ -1215,12 +1231,229 @@ impl Connectivity { mod tests { use std::str::FromStr; + use url::Url; use uv_normalize::PackageName; use uv_pypi_types::{JoinRelativeError, SimpleJson}; use uv_redacted::DisplaySafeUrl; use crate::{SimpleMetadata, SimpleMetadatum, html::SimpleHtml}; + use uv_cache::Cache; + use wiremock::matchers::{basic_auth, method, path_regex}; + use wiremock::{Mock, MockServer, ResponseTemplate}; + + use crate::RegistryClientBuilder; + + type Error = Box; + + async fn start_test_server(username: &'static str, password: &'static str) -> MockServer { + let server = MockServer::start().await; + + Mock::given(method("GET")) + .and(basic_auth(username, password)) + .respond_with(ResponseTemplate::new(200)) + .mount(&server) + .await; + + Mock::given(method("GET")) + .respond_with(ResponseTemplate::new(401)) + .mount(&server) + .await; + + server + } + + #[tokio::test] + async fn test_redirect_to_server_with_credentials() -> Result<(), Error> { + let username = "user"; + let password = "password"; + + let auth_server = start_test_server(username, password).await; + let auth_base_url = DisplaySafeUrl::parse(&auth_server.uri())?; + + let redirect_server = MockServer::start().await; + + // Configure the redirect server to respond with a 302 to the auth server + Mock::given(method("GET")) + .respond_with( + ResponseTemplate::new(302).insert_header("Location", format!("{auth_base_url}")), + ) + .mount(&redirect_server) + .await; + + let redirect_server_url = DisplaySafeUrl::parse(&redirect_server.uri())?; + + let cache = Cache::temp()?; + let registry_client = RegistryClientBuilder::new(cache) + .allow_cross_origin_credentials() + .build(); + let client = registry_client.cached_client().uncached(); + + assert_eq!( + client + .for_host(&redirect_server_url) + .get(redirect_server.uri()) + .send() + .await? + .status(), + 401, + "Requests should fail if credentials are missing" + ); + + let mut url = redirect_server_url.clone(); + let _ = url.set_username(username); + let _ = url.set_password(Some(password)); + + assert_eq!( + client + .for_host(&redirect_server_url) + .get(Url::from(url)) + .send() + .await? + .status(), + 200, + "Requests should succeed if credentials are present" + ); + + Ok(()) + } + + #[tokio::test] + async fn test_redirect_root_relative_url() -> Result<(), Error> { + let username = "user"; + let password = "password"; + + let redirect_server = MockServer::start().await; + + // Configure the redirect server to respond with a 307 with a relative URL. + Mock::given(method("GET")) + .and(path_regex("/foo/")) + .respond_with( + ResponseTemplate::new(307).insert_header("Location", "/bar/baz/".to_string()), + ) + .mount(&redirect_server) + .await; + + Mock::given(method("GET")) + .and(path_regex("/bar/baz/")) + .and(basic_auth(username, password)) + .respond_with(ResponseTemplate::new(200)) + .mount(&redirect_server) + .await; + + let redirect_server_url = DisplaySafeUrl::parse(&redirect_server.uri())?.join("foo/")?; + + let cache = Cache::temp()?; + let registry_client = RegistryClientBuilder::new(cache) + .allow_cross_origin_credentials() + .build(); + let client = registry_client.cached_client().uncached(); + + let mut url = redirect_server_url.clone(); + let _ = url.set_username(username); + let _ = url.set_password(Some(password)); + + assert_eq!( + client + .for_host(&url) + .get(Url::from(url)) + .send() + .await? + .status(), + 200, + "Requests should succeed for relative URL" + ); + + Ok(()) + } + + #[tokio::test] + async fn test_redirect_relative_url() -> Result<(), Error> { + let username = "user"; + let password = "password"; + + let redirect_server = MockServer::start().await; + + // Configure the redirect server to respond with a 307 with a relative URL. + Mock::given(method("GET")) + .and(path_regex("/foo/bar/baz/")) + .and(basic_auth(username, password)) + .respond_with(ResponseTemplate::new(200)) + .mount(&redirect_server) + .await; + + Mock::given(method("GET")) + .and(path_regex("/foo/")) + .and(basic_auth(username, password)) + .respond_with( + ResponseTemplate::new(307).insert_header("Location", "bar/baz/".to_string()), + ) + .mount(&redirect_server) + .await; + + let cache = Cache::temp()?; + let registry_client = RegistryClientBuilder::new(cache) + .allow_cross_origin_credentials() + .build(); + let client = registry_client.cached_client().uncached(); + + let redirect_server_url = DisplaySafeUrl::parse(&redirect_server.uri())?.join("foo/")?; + let mut url = redirect_server_url.clone(); + let _ = url.set_username(username); + let _ = url.set_password(Some(password)); + + assert_eq!( + client + .for_host(&url) + .get(Url::from(url)) + .send() + .await? + .status(), + 200, + "Requests should succeed for relative URL" + ); + + Ok(()) + } + + #[tokio::test] + async fn test_redirect_preserve_fragment() -> Result<(), Error> { + let redirect_server = MockServer::start().await; + + // Configure the redirect server to respond with a 307 with a relative URL. + Mock::given(method("GET")) + .respond_with(ResponseTemplate::new(307).insert_header("Location", "/foo".to_string())) + .mount(&redirect_server) + .await; + + Mock::given(method("GET")) + .and(path_regex("/foo")) + .respond_with(ResponseTemplate::new(200)) + .mount(&redirect_server) + .await; + + let cache = Cache::temp()?; + let registry_client = RegistryClientBuilder::new(cache).build(); + let client = registry_client.cached_client().uncached(); + + let mut url = DisplaySafeUrl::parse(&redirect_server.uri())?; + url.set_fragment(Some("fragment")); + + assert_eq!( + client + .for_host(&url) + .get(Url::from(url.clone())) + .send() + .await? + .url() + .to_string(), + format!("{}/foo#fragment", redirect_server.uri()), + "Requests should preserve fragment" + ); + + Ok(()) + } + #[test] fn ignore_failing_files() { // 1.7.7 has an invalid requires-python field (double comma), 1.7.8 is valid diff --git a/crates/uv-distribution/src/source/mod.rs b/crates/uv-distribution/src/source/mod.rs index 2cf148f2b..90d77bd90 100644 --- a/crates/uv-distribution/src/source/mod.rs +++ b/crates/uv-distribution/src/source/mod.rs @@ -1583,7 +1583,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { client .unmanaged .uncached_client(resource.git.repository()) - .clone(), + .raw_client(), ) .await { @@ -1866,7 +1866,10 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .git() .github_fast_path( git, - client.unmanaged.uncached_client(git.repository()).clone(), + client + .unmanaged + .uncached_client(git.repository()) + .raw_client(), ) .await? .is_some() diff --git a/crates/uv-git/src/resolver.rs b/crates/uv-git/src/resolver.rs index fd90ff587..d404390f3 100644 --- a/crates/uv-git/src/resolver.rs +++ b/crates/uv-git/src/resolver.rs @@ -53,7 +53,7 @@ impl GitResolver { pub async fn github_fast_path( &self, url: &GitUrl, - client: ClientWithMiddleware, + client: &ClientWithMiddleware, ) -> Result, GitResolverError> { if std::env::var_os(EnvVars::UV_NO_GITHUB_FAST_PATH).is_some() { return Ok(None); @@ -117,7 +117,7 @@ impl GitResolver { pub async fn fetch( &self, url: &GitUrl, - client: ClientWithMiddleware, + client: impl Into, disable_ssl: bool, offline: bool, cache: PathBuf, diff --git a/crates/uv-publish/src/lib.rs b/crates/uv-publish/src/lib.rs index dd8358439..ec19713cc 100644 --- a/crates/uv-publish/src/lib.rs +++ b/crates/uv-publish/src/lib.rs @@ -12,7 +12,6 @@ use itertools::Itertools; use reqwest::header::AUTHORIZATION; use reqwest::multipart::Part; use reqwest::{Body, Response, StatusCode}; -use reqwest_middleware::RequestBuilder; use reqwest_retry::policies::ExponentialBackoff; use reqwest_retry::{RetryPolicy, Retryable, RetryableStrategy}; use rustc_hash::FxHashSet; @@ -29,7 +28,7 @@ use uv_auth::Credentials; use uv_cache::{Cache, Refresh}; use uv_client::{ BaseClient, DEFAULT_RETRIES, MetadataFormat, OwnedArchive, RegistryClientBuilder, - UvRetryableStrategy, + RequestBuilder, UvRetryableStrategy, }; use uv_configuration::{KeyringProviderType, TrustedPublishing}; use uv_distribution_filename::{DistFilename, SourceDistExtension, SourceDistFilename}; @@ -330,7 +329,9 @@ pub async fn check_trusted_publishing( debug!( "Running on GitHub Actions without explicit credentials, checking for trusted publishing" ); - match trusted_publishing::get_token(registry, client.for_host(registry)).await { + match trusted_publishing::get_token(registry, client.for_host(registry).raw_client()) + .await + { Ok(token) => Ok(TrustedPublishResult::Configured(token)), Err(err) => { // TODO(konsti): It would be useful if we could differentiate between actual errors @@ -364,7 +365,9 @@ pub async fn check_trusted_publishing( ); } - let token = trusted_publishing::get_token(registry, client.for_host(registry)).await?; + let token = + trusted_publishing::get_token(registry, client.for_host(registry).raw_client()) + .await?; Ok(TrustedPublishResult::Configured(token)) } TrustedPublishing::Never => Ok(TrustedPublishResult::Skipped), @@ -748,16 +751,16 @@ async fn form_metadata( /// Build the upload request. /// /// Returns the request and the reporter progress bar id. -async fn build_request( +async fn build_request<'a>( file: &Path, raw_filename: &str, filename: &DistFilename, registry: &DisplaySafeUrl, - client: &BaseClient, + client: &'a BaseClient, credentials: &Credentials, form_metadata: &[(&'static str, String)], reporter: Arc, -) -> Result<(RequestBuilder, usize), PublishPrepareError> { +) -> Result<(RequestBuilder<'a>, usize), PublishPrepareError> { let mut form = reqwest::multipart::Form::new(); for (key, value) in form_metadata { form = form.text(*key, value.clone()); @@ -969,12 +972,13 @@ mod tests { project_urls: Source, https://github.com/unknown/tqdm "###); + let client = BaseClientBuilder::new().build(); let (request, _) = build_request( &file, raw_filename, &filename, &DisplaySafeUrl::parse("https://example.org/upload").unwrap(), - &BaseClientBuilder::new().build(), + &client, &Credentials::basic(Some("ferris".to_string()), Some("F3RR!S".to_string())), &form_metadata, Arc::new(DummyReporter), @@ -985,7 +989,7 @@ mod tests { insta::with_settings!({ filters => [("boundary=[0-9a-f-]+", "boundary=[...]")], }, { - assert_debug_snapshot!(&request, @r#" + assert_debug_snapshot!(&request.raw_builder(), @r#" RequestBuilder { inner: RequestBuilder { method: POST, @@ -1118,12 +1122,13 @@ mod tests { requires_dist: requests ; extra == 'telegram' "###); + let client = BaseClientBuilder::new().build(); let (request, _) = build_request( &file, raw_filename, &filename, &DisplaySafeUrl::parse("https://example.org/upload").unwrap(), - &BaseClientBuilder::new().build(), + &client, &Credentials::basic(Some("ferris".to_string()), Some("F3RR!S".to_string())), &form_metadata, Arc::new(DummyReporter), @@ -1134,7 +1139,7 @@ mod tests { insta::with_settings!({ filters => [("boundary=[0-9a-f-]+", "boundary=[...]")], }, { - assert_debug_snapshot!(&request, @r#" + assert_debug_snapshot!(&request.raw_builder(), @r#" RequestBuilder { inner: RequestBuilder { method: POST, diff --git a/crates/uv/tests/it/common/mod.rs b/crates/uv/tests/it/common/mod.rs index f997561a9..7ef7cfff6 100644 --- a/crates/uv/tests/it/common/mod.rs +++ b/crates/uv/tests/it/common/mod.rs @@ -1668,9 +1668,9 @@ pub async fn download_to_disk(url: &str, path: &Path) { .allow_insecure_host(trusted_hosts) .build(); let url = url.parse().unwrap(); - let client = client.for_host(&url); let response = client - .request(http::Method::GET, reqwest::Url::from(url)) + .for_host(&url) + .get(reqwest::Url::from(url)) .send() .await .unwrap(); diff --git a/crates/uv/tests/it/edit.rs b/crates/uv/tests/it/edit.rs index 68eae5110..d117b1e8c 100644 --- a/crates/uv/tests/it/edit.rs +++ b/crates/uv/tests/it/edit.rs @@ -14,6 +14,7 @@ use assert_fs::prelude::*; use indoc::{formatdoc, indoc}; use insta::assert_snapshot; use std::path::Path; +use url::Url; use uv_fs::Simplified; use wiremock::{Mock, MockServer, ResponseTemplate, matchers::method}; @@ -11838,6 +11839,196 @@ fn add_auth_policy_never_without_credentials() -> Result<()> { Ok(()) } +/// If uv receives a 302 redirect to a cross-origin server, it should not forward +/// credentials. In the absence of a netrc entry for the new location, +/// it should fail. +#[tokio::test] +async fn add_redirect_cross_origin() -> Result<()> { + let context = TestContext::new("3.12"); + let filters = context + .filters() + .into_iter() + .chain([(r"127\.0\.0\.1:\d*", "[LOCALHOST]")]) + .collect::>(); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str(indoc! { r#" + [project] + name = "foo" + version = "1.0.0" + requires-python = ">=3.12" + dependencies = [] + "# + })?; + + let redirect_server = MockServer::start().await; + + Mock::given(method("GET")) + .respond_with(|req: &wiremock::Request| { + let redirect_url = redirect_url_to_pypi_proxy(req); + ResponseTemplate::new(302).insert_header("Location", &redirect_url) + }) + .mount(&redirect_server) + .await; + + let mut redirect_url = Url::parse(&redirect_server.uri())?; + let _ = redirect_url.set_username("public"); + let _ = redirect_url.set_password(Some("heron")); + + uv_snapshot!(filters, context.add().arg("--default-index").arg(redirect_url.as_str()).arg("anyio"), @r" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + × No solution found when resolving dependencies: + ╰─▶ Because anyio was not found in the package registry and your project depends on anyio, we can conclude that your project's requirements are unsatisfiable. + + hint: An index URL (http://[LOCALHOST]/) could not be queried due to a lack of valid authentication credentials (401 Unauthorized). + help: If you want to add the package regardless of the failed resolution, provide the `--frozen` flag to skip locking and syncing. + " + ); + + Ok(()) +} + +/// uv currently fails to look up keyring credentials on a cross-origin redirect. +#[tokio::test] +async fn add_redirect_with_keyring_cross_origin() -> Result<()> { + let keyring_context = TestContext::new("3.12"); + + // Install our keyring plugin + keyring_context + .pip_install() + .arg( + keyring_context + .workspace_root + .join("scripts") + .join("packages") + .join("keyring_test_plugin"), + ) + .assert() + .success(); + + let context = TestContext::new("3.12"); + let filters = context + .filters() + .into_iter() + .chain([(r"127\.0\.0\.1:\d*", "[LOCALHOST]")]) + .collect::>(); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str(indoc! { r#" + [project] + name = "foo" + version = "1.0.0" + requires-python = ">=3.12" + dependencies = [] + + [tool.uv] + keyring-provider = "subprocess" + "#, + })?; + + let redirect_server = MockServer::start().await; + + Mock::given(method("GET")) + .respond_with(|req: &wiremock::Request| { + let redirect_url = redirect_url_to_pypi_proxy(req); + ResponseTemplate::new(302).insert_header("Location", &redirect_url) + }) + .mount(&redirect_server) + .await; + + let mut redirect_url = Url::parse(&redirect_server.uri())?; + let _ = redirect_url.set_username("public"); + + uv_snapshot!(filters, context.add().arg("--default-index") + .arg(redirect_url.as_str()) + .arg("anyio") + .env(EnvVars::KEYRING_TEST_CREDENTIALS, r#"{"pypi-proxy.fly.dev": {"public": "heron"}}"#) + .env(EnvVars::PATH, venv_bin_path(&keyring_context.venv)), @r" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + Keyring request for public@http://[LOCALHOST]/ + Keyring request for public@[LOCALHOST] + × No solution found when resolving dependencies: + ╰─▶ Because anyio was not found in the package registry and your project depends on anyio, we can conclude that your project's requirements are unsatisfiable. + + hint: An index URL (http://[LOCALHOST]/) could not be queried due to a lack of valid authentication credentials (401 Unauthorized). + help: If you want to add the package regardless of the failed resolution, provide the `--frozen` flag to skip locking and syncing. + " + ); + + Ok(()) +} + +/// If uv receives a cross-origin 302 redirect, it should use credentials from netrc +/// for the new location. +#[tokio::test] +async fn pip_install_redirect_with_netrc_cross_origin() -> Result<()> { + let context = TestContext::new("3.12"); + let filters = context + .filters() + .into_iter() + .chain([(r"127\.0\.0\.1:\d*", "[LOCALHOST]")]) + .collect::>(); + + let netrc = context.temp_dir.child(".netrc"); + netrc.write_str("machine pypi-proxy.fly.dev login public password heron")?; + + let redirect_server = MockServer::start().await; + + Mock::given(method("GET")) + .respond_with(|req: &wiremock::Request| { + let redirect_url = redirect_url_to_pypi_proxy(req); + ResponseTemplate::new(302).insert_header("Location", &redirect_url) + }) + .mount(&redirect_server) + .await; + + let mut redirect_url = Url::parse(&redirect_server.uri())?; + let _ = redirect_url.set_username("public"); + + uv_snapshot!(filters, context.pip_install() + .arg("anyio") + .arg("--index-url") + .arg(redirect_url.as_str()) + .env(EnvVars::NETRC, netrc.to_str().unwrap()) + .arg("--strict"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 3 packages in [TIME] + Prepared 3 packages in [TIME] + Installed 3 packages in [TIME] + + anyio==4.3.0 + + idna==3.6 + + sniffio==1.3.1 + "### + ); + + context.assert_command("import anyio").success(); + + Ok(()) +} + +fn redirect_url_to_pypi_proxy(req: &wiremock::Request) -> String { + let last_path_segment = req + .url + .path_segments() + .expect("path has segments") + .filter(|segment| !segment.is_empty()) // Filter out empty segments + .next_back() + .expect("path has a package segment"); + format!("https://pypi-proxy.fly.dev/basic-auth/simple/{last_path_segment}/") +} + /// Test the error message when adding a package with multiple existing references in /// `pyproject.toml`. #[test] From e9d5780369da14ecf9f834efc8a17b0118823cfb Mon Sep 17 00:00:00 2001 From: John Mumm Date: Fri, 20 Jun 2025 10:17:13 -0400 Subject: [PATCH 024/349] Support transparent Python patch version upgrades (#13954) > NOTE: The PRs that were merged into this feature branch have all been independently reviewed. But it's also useful to see all of the changes in their final form. I've added comments to significant changes throughout the PR to aid discussion. This PR introduces transparent Python version upgrades to uv, allowing for a smoother experience when upgrading to new patch versions. Previously, upgrading Python patch versions required manual updates to each virtual environment. Now, virtual environments can transparently upgrade to newer patch versions. Due to significant changes in how uv installs and executes managed Python executables, this functionality is initially available behind a `--preview` flag. Once an installation has been made upgradeable through `--preview`, subsequent operations (like `uv venv -p 3.10` or patch upgrades) will work without requiring the flag again. This is accomplished by checking for the existence of a minor version symlink directory (or junction on Windows). ### Features * New `uv python upgrade` command to upgrade installed Python versions to the latest available patch release: ``` # Upgrade specific minor version uv python upgrade 3.12 --preview # Upgrade all installed minor versions uv python upgrade --preview ``` * Transparent upgrades also occur when installing newer patch versions: ``` uv python install 3.10.8 --preview # Automatically upgrades existing 3.10 environments uv python install 3.10.18 ``` * Support for transparently upgradeable Python `bin` installations via `--preview` flag ``` uv python install 3.13 --preview # Automatically upgrades the `bin` installation if there is a newer patch version available uv python upgrade 3.13 --preview ``` * Virtual environments can still be tied to a patch version if desired (ignoring patch upgrades): ``` uv venv -p 3.10.8 ``` ### Implementation Transparent upgrades are implemented using: * Minor version symlink directories (Unix) or junctions (Windows) * On Windows, trampolines simulate paths with junctions * Symlink directory naming follows Python build standalone format: e.g., `cpython-3.10-macos-aarch64-none` * Upgrades are scoped to the minor version key (as represented in the naming format: implementation-minor version+variant-os-arch-libc) * If the context does not provide a patch version request and the interpreter is from a managed CPython installation, the `Interpreter` used by `uv python run` will use the full symlink directory executable path when available, enabling transparently upgradeable environments created with the `venv` module (`uv run python -m venv`) New types: * `PythonMinorVersionLink`: in a sense, the core type for this PR, this is a representation of a minor version symlink directory (or junction on Windows) that points to the highest installed managed CPython patch version for a minor version key. * `PythonInstallationMinorVersionKey`: provides a view into a `PythonInstallationKey` that excludes the patch and prerelease. This is used for grouping installations by minor version key (e.g., to find the highest available patch installation for that minor version key) and for minor version directory naming. ### Compatibility * Supports virtual environments created with: * `uv venv` * `uv run python -m venv` (using managed Python that was installed or upgraded with `--preview`) * Virtual environments created within these environments * Existing virtual environments from before these changes continue to work but aren't transparently upgradeable without being recreated * Supports both standard Python (`python3.10`) and freethreaded Python (`python3.10t`) * Support for transparently upgrades is currently only available for managed CPython installations Closes #7287 Closes #7325 Closes #7892 Closes #9031 Closes #12977 --------- Co-authored-by: Zanie Blue --- Cargo.lock | 8 + crates/uv-build-frontend/src/lib.rs | 4 + crates/uv-cli/src/lib.rs | 62 ++ crates/uv-dev/src/compile.rs | 3 +- crates/uv-dispatch/src/lib.rs | 1 + crates/uv-fs/Cargo.toml | 1 - crates/uv-fs/src/path.rs | 15 - crates/uv-python/Cargo.toml | 4 + crates/uv-python/src/discovery.rs | 111 ++- crates/uv-python/src/downloads.rs | 6 +- crates/uv-python/src/environment.rs | 3 + crates/uv-python/src/installation.rs | 147 +++- crates/uv-python/src/interpreter.rs | 52 +- crates/uv-python/src/lib.rs | 94 ++- crates/uv-python/src/managed.rs | 339 +++++++-- crates/uv-python/src/python_version.rs | 2 +- crates/uv-tool/Cargo.toml | 1 + crates/uv-tool/src/lib.rs | 4 + crates/uv-trampoline/src/bounce.rs | 59 +- .../uv-trampoline-aarch64-console.exe | Bin 39424 -> 39936 bytes .../trampolines/uv-trampoline-aarch64-gui.exe | Bin 40448 -> 40960 bytes .../uv-trampoline-i686-console.exe | Bin 34304 -> 34816 bytes .../trampolines/uv-trampoline-i686-gui.exe | Bin 35328 -> 35840 bytes .../uv-trampoline-x86_64-console.exe | Bin 40448 -> 41472 bytes .../trampolines/uv-trampoline-x86_64-gui.exe | Bin 41472 -> 42496 bytes crates/uv-virtualenv/Cargo.toml | 1 + crates/uv-virtualenv/src/lib.rs | 7 + crates/uv-virtualenv/src/virtualenv.rs | 217 +++--- crates/uv/Cargo.toml | 2 + crates/uv/src/commands/build_frontend.rs | 1 + crates/uv/src/commands/pip/check.rs | 3 + crates/uv/src/commands/pip/compile.rs | 16 +- crates/uv/src/commands/pip/freeze.rs | 3 + crates/uv/src/commands/pip/install.rs | 2 + crates/uv/src/commands/pip/list.rs | 4 +- crates/uv/src/commands/pip/show.rs | 3 + crates/uv/src/commands/pip/sync.rs | 2 + crates/uv/src/commands/pip/tree.rs | 4 +- crates/uv/src/commands/pip/uninstall.rs | 4 +- crates/uv/src/commands/project/add.rs | 4 + crates/uv/src/commands/project/environment.rs | 7 +- crates/uv/src/commands/project/export.rs | 2 + crates/uv/src/commands/project/init.rs | 7 + crates/uv/src/commands/project/lock.rs | 3 + crates/uv/src/commands/project/mod.rs | 26 + crates/uv/src/commands/project/remove.rs | 3 + crates/uv/src/commands/project/run.rs | 12 + crates/uv/src/commands/project/sync.rs | 2 + crates/uv/src/commands/project/tree.rs | 2 + crates/uv/src/commands/project/version.rs | 3 + crates/uv/src/commands/python/find.rs | 6 +- crates/uv/src/commands/python/install.rs | 239 ++++-- crates/uv/src/commands/python/list.rs | 3 + crates/uv/src/commands/python/pin.rs | 7 +- crates/uv/src/commands/python/uninstall.rs | 65 +- crates/uv/src/commands/tool/common.rs | 3 + crates/uv/src/commands/tool/install.rs | 4 +- crates/uv/src/commands/tool/run.rs | 2 + crates/uv/src/commands/tool/upgrade.rs | 3 +- crates/uv/src/commands/venv.rs | 14 +- crates/uv/src/lib.rs | 47 +- crates/uv/src/settings.rs | 59 +- crates/uv/tests/it/common/mod.rs | 10 + crates/uv/tests/it/help.rs | 5 + crates/uv/tests/it/main.rs | 3 + crates/uv/tests/it/python_install.rs | 701 ++++++++++++++++- crates/uv/tests/it/python_upgrade.rs | 703 ++++++++++++++++++ crates/uv/tests/it/sync.rs | 8 +- crates/uv/tests/it/tool_upgrade.rs | 12 +- crates/uv/tests/it/venv.rs | 4 +- docs/concepts/python-versions.md | 66 +- docs/guides/install-python.md | 22 + docs/reference/cli.md | 86 +++ 73 files changed, 3022 insertions(+), 306 deletions(-) create mode 100644 crates/uv/tests/it/python_upgrade.rs diff --git a/Cargo.lock b/Cargo.lock index a30a0cbe1..6c1978cd0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4582,6 +4582,7 @@ dependencies = [ "console", "ctrlc", "dotenvy", + "dunce", "etcetera", "filetime", "flate2", @@ -4589,6 +4590,7 @@ dependencies = [ "futures", "http", "ignore", + "indexmap", "indicatif", "indoc", "insta", @@ -5548,15 +5550,18 @@ dependencies = [ "assert_fs", "clap", "configparser", + "dunce", "fs-err 3.1.1", "futures", "goblin", + "indexmap", "indoc", "insta", "itertools 0.14.0", "once_cell", "owo-colors", "procfs", + "ref-cast", "regex", "reqwest", "reqwest-middleware", @@ -5581,6 +5586,7 @@ dependencies = [ "uv-cache-info", "uv-cache-key", "uv-client", + "uv-configuration", "uv-dirs", "uv-distribution-filename", "uv-extract", @@ -5844,6 +5850,7 @@ dependencies = [ "toml_edit", "tracing", "uv-cache", + "uv-configuration", "uv-dirs", "uv-distribution-types", "uv-fs", @@ -5928,6 +5935,7 @@ dependencies = [ "self-replace", "thiserror 2.0.12", "tracing", + "uv-configuration", "uv-fs", "uv-pypi-types", "uv-python", diff --git a/crates/uv-build-frontend/src/lib.rs b/crates/uv-build-frontend/src/lib.rs index 1c29b2c31..34037ffdd 100644 --- a/crates/uv-build-frontend/src/lib.rs +++ b/crates/uv-build-frontend/src/lib.rs @@ -27,6 +27,7 @@ use tokio::process::Command; use tokio::sync::{Mutex, Semaphore}; use tracing::{Instrument, debug, info_span, instrument}; +use uv_configuration::PreviewMode; use uv_configuration::{BuildKind, BuildOutput, ConfigSettings, SourceStrategy}; use uv_distribution::BuildRequires; use uv_distribution_types::{IndexLocations, Requirement, Resolution}; @@ -278,6 +279,7 @@ impl SourceBuild { mut environment_variables: FxHashMap, level: BuildOutput, concurrent_builds: usize, + preview: PreviewMode, ) -> Result { let temp_dir = build_context.cache().venv_dir()?; @@ -325,6 +327,8 @@ impl SourceBuild { false, false, false, + false, + preview, )? }; diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index bd06f9a82..e58f6c079 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -4722,6 +4722,24 @@ pub enum PythonCommand { /// See `uv help python` to view supported request formats. Install(PythonInstallArgs), + /// Upgrade installed Python versions to the latest supported patch release (requires the + /// `--preview` flag). + /// + /// A target Python minor version to upgrade may be provided, e.g., `3.13`. Multiple versions + /// may be provided to perform more than one upgrade. + /// + /// If no target version is provided, then uv will upgrade all managed CPython versions. + /// + /// During an upgrade, uv will not uninstall outdated patch versions. + /// + /// When an upgrade is performed, virtual environments created by uv will automatically + /// use the new version. However, if the virtual environment was created before the + /// upgrade functionality was added, it will continue to use the old Python version; to enable + /// upgrades, the environment must be recreated. + /// + /// Upgrades are not yet supported for alternative implementations, like PyPy. + Upgrade(PythonUpgradeArgs), + /// Search for a Python installation. /// /// Displays the path to the Python executable. @@ -4907,6 +4925,50 @@ pub struct PythonInstallArgs { pub default: bool, } +#[derive(Args)] +pub struct PythonUpgradeArgs { + /// The directory Python installations are stored in. + /// + /// If provided, `UV_PYTHON_INSTALL_DIR` will need to be set for subsequent operations for uv to + /// discover the Python installation. + /// + /// See `uv python dir` to view the current Python installation directory. Defaults to + /// `~/.local/share/uv/python`. + #[arg(long, short, env = EnvVars::UV_PYTHON_INSTALL_DIR)] + pub install_dir: Option, + + /// The Python minor version(s) to upgrade. + /// + /// If no target version is provided, then uv will upgrade all managed CPython versions. + #[arg(env = EnvVars::UV_PYTHON)] + pub targets: Vec, + + /// Set the URL to use as the source for downloading Python installations. + /// + /// The provided URL will replace + /// `https://github.com/astral-sh/python-build-standalone/releases/download` in, e.g., + /// `https://github.com/astral-sh/python-build-standalone/releases/download/20240713/cpython-3.12.4%2B20240713-aarch64-apple-darwin-install_only.tar.gz`. + /// + /// Distributions can be read from a local directory by using the `file://` URL scheme. + #[arg(long, env = EnvVars::UV_PYTHON_INSTALL_MIRROR)] + pub mirror: Option, + + /// Set the URL to use as the source for downloading PyPy installations. + /// + /// The provided URL will replace `https://downloads.python.org/pypy` in, e.g., + /// `https://downloads.python.org/pypy/pypy3.8-v7.3.7-osx64.tar.bz2`. + /// + /// Distributions can be read from a local directory by using the `file://` URL scheme. + #[arg(long, env = EnvVars::UV_PYPY_INSTALL_MIRROR)] + pub pypy_mirror: Option, + + /// URL pointing to JSON of custom Python installations. + /// + /// Note that currently, only local paths are supported. + #[arg(long, env = EnvVars::UV_PYTHON_DOWNLOADS_JSON_URL)] + pub python_downloads_json_url: Option, +} + #[derive(Args)] pub struct PythonUninstallArgs { /// The directory where the Python was installed. diff --git a/crates/uv-dev/src/compile.rs b/crates/uv-dev/src/compile.rs index 434b5e791..d2b685b23 100644 --- a/crates/uv-dev/src/compile.rs +++ b/crates/uv-dev/src/compile.rs @@ -4,7 +4,7 @@ use clap::Parser; use tracing::info; use uv_cache::{Cache, CacheArgs}; -use uv_configuration::Concurrency; +use uv_configuration::{Concurrency, PreviewMode}; use uv_python::{EnvironmentPreference, PythonEnvironment, PythonRequest}; #[derive(Parser)] @@ -26,6 +26,7 @@ pub(crate) async fn compile(args: CompileArgs) -> anyhow::Result<()> { &PythonRequest::default(), EnvironmentPreference::OnlyVirtual, &cache, + PreviewMode::Disabled, )? .into_interpreter(); interpreter.sys_executable().to_path_buf() diff --git a/crates/uv-dispatch/src/lib.rs b/crates/uv-dispatch/src/lib.rs index 3b0ad5555..207f241ad 100644 --- a/crates/uv-dispatch/src/lib.rs +++ b/crates/uv-dispatch/src/lib.rs @@ -433,6 +433,7 @@ impl BuildContext for BuildDispatch<'_> { self.build_extra_env_vars.clone(), build_output, self.concurrency.builds, + self.preview, ) .boxed_local() .await?; diff --git a/crates/uv-fs/Cargo.toml b/crates/uv-fs/Cargo.toml index 12a5f94b7..fba4910e6 100644 --- a/crates/uv-fs/Cargo.toml +++ b/crates/uv-fs/Cargo.toml @@ -16,7 +16,6 @@ doctest = false workspace = true [dependencies] - dunce = { workspace = true } either = { workspace = true } encoding_rs_io = { workspace = true } diff --git a/crates/uv-fs/src/path.rs b/crates/uv-fs/src/path.rs index 7a75c76c3..90d0ce80a 100644 --- a/crates/uv-fs/src/path.rs +++ b/crates/uv-fs/src/path.rs @@ -277,21 +277,6 @@ fn normalized(path: &Path) -> PathBuf { normalized } -/// Like `fs_err::canonicalize`, but avoids attempting to resolve symlinks on Windows. -pub fn canonicalize_executable(path: impl AsRef) -> std::io::Result { - let path = path.as_ref(); - debug_assert!( - path.is_absolute(), - "path must be absolute: {}", - path.display() - ); - if cfg!(windows) { - Ok(path.to_path_buf()) - } else { - fs_err::canonicalize(path) - } -} - /// Compute a path describing `path` relative to `base`. /// /// `lib/python/site-packages/foo/__init__.py` and `lib/python/site-packages` -> `foo/__init__.py` diff --git a/crates/uv-python/Cargo.toml b/crates/uv-python/Cargo.toml index 59a9829e0..d008b2d4e 100644 --- a/crates/uv-python/Cargo.toml +++ b/crates/uv-python/Cargo.toml @@ -20,6 +20,7 @@ uv-cache = { workspace = true } uv-cache-info = { workspace = true } uv-cache-key = { workspace = true } uv-client = { workspace = true } +uv-configuration = { workspace = true } uv-dirs = { workspace = true } uv-distribution-filename = { workspace = true } uv-extract = { workspace = true } @@ -38,11 +39,14 @@ uv-warnings = { workspace = true } anyhow = { workspace = true } clap = { workspace = true, optional = true } configparser = { workspace = true } +dunce = { workspace = true } fs-err = { workspace = true, features = ["tokio"] } futures = { workspace = true } goblin = { workspace = true, default-features = false } +indexmap = { workspace = true } itertools = { workspace = true } owo-colors = { workspace = true } +ref-cast = { workspace = true } regex = { workspace = true } reqwest = { workspace = true } reqwest-middleware = { workspace = true } diff --git a/crates/uv-python/src/discovery.rs b/crates/uv-python/src/discovery.rs index 27853e3db..eaf4b4830 100644 --- a/crates/uv-python/src/discovery.rs +++ b/crates/uv-python/src/discovery.rs @@ -8,6 +8,7 @@ use std::{env, io, iter}; use std::{path::Path, path::PathBuf, str::FromStr}; use thiserror::Error; use tracing::{debug, instrument, trace}; +use uv_configuration::PreviewMode; use which::{which, which_all}; use uv_cache::Cache; @@ -25,7 +26,7 @@ use crate::implementation::ImplementationName; use crate::installation::PythonInstallation; use crate::interpreter::Error as InterpreterError; use crate::interpreter::{StatusCodeError, UnexpectedResponseError}; -use crate::managed::ManagedPythonInstallations; +use crate::managed::{ManagedPythonInstallations, PythonMinorVersionLink}; #[cfg(windows)] use crate::microsoft_store::find_microsoft_store_pythons; use crate::virtualenv::Error as VirtualEnvError; @@ -35,12 +36,12 @@ use crate::virtualenv::{ }; #[cfg(windows)] use crate::windows_registry::{WindowsPython, registry_pythons}; -use crate::{BrokenSymlink, Interpreter, PythonVersion}; +use crate::{BrokenSymlink, Interpreter, PythonInstallationKey, PythonVersion}; /// A request to find a Python installation. /// /// See [`PythonRequest::from_str`]. -#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[derive(Debug, Clone, PartialEq, Eq, Default, Hash)] pub enum PythonRequest { /// An appropriate default Python installation /// @@ -173,7 +174,7 @@ pub enum PythonVariant { } /// A Python discovery version request. -#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] pub enum VersionRequest { /// Allow an appropriate default Python version. #[default] @@ -334,6 +335,7 @@ fn python_executables_from_installed<'a>( implementation: Option<&'a ImplementationName>, platform: PlatformRequest, preference: PythonPreference, + preview: PreviewMode, ) -> Box> + 'a> { let from_managed_installations = iter::once_with(move || { ManagedPythonInstallations::from_settings(None) @@ -359,7 +361,29 @@ fn python_executables_from_installed<'a>( true }) .inspect(|installation| debug!("Found managed installation `{installation}`")) - .map(|installation| (PythonSource::Managed, installation.executable(false)))) + .map(move |installation| { + // If it's not a patch version request, then attempt to read the stable + // minor version link. + let executable = version + .patch() + .is_none() + .then(|| { + PythonMinorVersionLink::from_installation( + &installation, + preview, + ) + .filter(PythonMinorVersionLink::exists) + .map( + |minor_version_link| { + minor_version_link.symlink_executable.clone() + }, + ) + }) + .flatten() + .unwrap_or_else(|| installation.executable(false)); + (PythonSource::Managed, executable) + }) + ) }) }) .flatten_ok(); @@ -452,6 +476,7 @@ fn python_executables<'a>( platform: PlatformRequest, environments: EnvironmentPreference, preference: PythonPreference, + preview: PreviewMode, ) -> Box> + 'a> { // Always read from `UV_INTERNAL__PARENT_INTERPRETER` — it could be a system interpreter let from_parent_interpreter = iter::once_with(|| { @@ -472,7 +497,7 @@ fn python_executables<'a>( let from_virtual_environments = python_executables_from_virtual_environments(); let from_installed = - python_executables_from_installed(version, implementation, platform, preference); + python_executables_from_installed(version, implementation, platform, preference, preview); // Limit the search to the relevant environment preference; this avoids unnecessary work like // traversal of the file system. Subsequent filtering should be done by the caller with @@ -671,16 +696,23 @@ fn python_interpreters<'a>( environments: EnvironmentPreference, preference: PythonPreference, cache: &'a Cache, + preview: PreviewMode, ) -> impl Iterator> + 'a { python_interpreters_from_executables( // Perform filtering on the discovered executables based on their source. This avoids // unnecessary interpreter queries, which are generally expensive. We'll filter again // with `interpreter_satisfies_environment_preference` after querying. - python_executables(version, implementation, platform, environments, preference).filter_ok( - move |(source, path)| { - source_satisfies_environment_preference(*source, path, environments) - }, - ), + python_executables( + version, + implementation, + platform, + environments, + preference, + preview, + ) + .filter_ok(move |(source, path)| { + source_satisfies_environment_preference(*source, path, environments) + }), cache, ) .filter_ok(move |(source, interpreter)| { @@ -919,6 +951,7 @@ pub fn find_python_installations<'a>( environments: EnvironmentPreference, preference: PythonPreference, cache: &'a Cache, + preview: PreviewMode, ) -> Box> + 'a> { let sources = DiscoveryPreferences { python_preference: preference, @@ -1010,6 +1043,7 @@ pub fn find_python_installations<'a>( environments, preference, cache, + preview, ) .map_ok(|tuple| Ok(PythonInstallation::from_tuple(tuple))) }), @@ -1022,6 +1056,7 @@ pub fn find_python_installations<'a>( environments, preference, cache, + preview, ) .map_ok(|tuple| Ok(PythonInstallation::from_tuple(tuple))) }), @@ -1038,6 +1073,7 @@ pub fn find_python_installations<'a>( environments, preference, cache, + preview, ) .map_ok(|tuple| Ok(PythonInstallation::from_tuple(tuple))) }) @@ -1051,6 +1087,7 @@ pub fn find_python_installations<'a>( environments, preference, cache, + preview, ) .filter_ok(|(_source, interpreter)| { interpreter @@ -1072,6 +1109,7 @@ pub fn find_python_installations<'a>( environments, preference, cache, + preview, ) .filter_ok(|(_source, interpreter)| { interpreter @@ -1096,6 +1134,7 @@ pub fn find_python_installations<'a>( environments, preference, cache, + preview, ) .filter_ok(|(_source, interpreter)| request.satisfied_by_interpreter(interpreter)) .map_ok(|tuple| Ok(PythonInstallation::from_tuple(tuple))) @@ -1113,8 +1152,10 @@ pub(crate) fn find_python_installation( environments: EnvironmentPreference, preference: PythonPreference, cache: &Cache, + preview: PreviewMode, ) -> Result { - let installations = find_python_installations(request, environments, preference, cache); + let installations = + find_python_installations(request, environments, preference, cache, preview); let mut first_prerelease = None; let mut first_error = None; for result in installations { @@ -1210,12 +1251,13 @@ pub(crate) fn find_best_python_installation( environments: EnvironmentPreference, preference: PythonPreference, cache: &Cache, + preview: PreviewMode, ) -> Result { debug!("Starting Python discovery for {}", request); // First, check for an exact match (or the first available version if no Python version was provided) debug!("Looking for exact match for request {request}"); - let result = find_python_installation(request, environments, preference, cache); + let result = find_python_installation(request, environments, preference, cache, preview); match result { Ok(Ok(installation)) => { warn_on_unsupported_python(installation.interpreter()); @@ -1243,7 +1285,7 @@ pub(crate) fn find_best_python_installation( _ => None, } { debug!("Looking for relaxed patch version {request}"); - let result = find_python_installation(&request, environments, preference, cache); + let result = find_python_installation(&request, environments, preference, cache, preview); match result { Ok(Ok(installation)) => { warn_on_unsupported_python(installation.interpreter()); @@ -1260,14 +1302,16 @@ pub(crate) fn find_best_python_installation( debug!("Looking for a default Python installation"); let request = PythonRequest::Default; Ok( - find_python_installation(&request, environments, preference, cache)?.map_err(|err| { - // Use a more general error in this case since we looked for multiple versions - PythonNotFound { - request, - python_preference: err.python_preference, - environment_preference: err.environment_preference, - } - }), + find_python_installation(&request, environments, preference, cache, preview)?.map_err( + |err| { + // Use a more general error in this case since we looked for multiple versions + PythonNotFound { + request, + python_preference: err.python_preference, + environment_preference: err.environment_preference, + } + }, + ), ) } @@ -1645,6 +1689,24 @@ impl PythonRequest { Ok(rest.parse().ok()) } + /// Check if this request includes a specific patch version. + pub fn includes_patch(&self) -> bool { + match self { + PythonRequest::Default => false, + PythonRequest::Any => false, + PythonRequest::Version(version_request) => version_request.patch().is_some(), + PythonRequest::Directory(..) => false, + PythonRequest::File(..) => false, + PythonRequest::ExecutableName(..) => false, + PythonRequest::Implementation(..) => false, + PythonRequest::ImplementationVersion(_, version) => version.patch().is_some(), + PythonRequest::Key(request) => request + .version + .as_ref() + .is_some_and(|request| request.patch().is_some()), + } + } + /// Check if a given interpreter satisfies the interpreter request. pub fn satisfied(&self, interpreter: &Interpreter, cache: &Cache) -> bool { /// Returns `true` if the two paths refer to the same interpreter executable. @@ -2086,6 +2148,11 @@ impl fmt::Display for ExecutableName { } impl VersionRequest { + /// Derive a [`VersionRequest::MajorMinor`] from a [`PythonInstallationKey`] + pub fn major_minor_request_from_key(key: &PythonInstallationKey) -> Self { + Self::MajorMinor(key.major, key.minor, key.variant) + } + /// Return possible executable names for the given version request. pub(crate) fn executable_names( &self, diff --git a/crates/uv-python/src/downloads.rs b/crates/uv-python/src/downloads.rs index fd3f6c417..51f6f1d45 100644 --- a/crates/uv-python/src/downloads.rs +++ b/crates/uv-python/src/downloads.rs @@ -111,14 +111,14 @@ pub enum Error { }, } -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone, Hash)] pub struct ManagedPythonDownload { key: PythonInstallationKey, url: &'static str, sha256: Option<&'static str>, } -#[derive(Debug, Clone, Default, Eq, PartialEq)] +#[derive(Debug, Clone, Default, Eq, PartialEq, Hash)] pub struct PythonDownloadRequest { pub(crate) version: Option, pub(crate) implementation: Option, @@ -131,7 +131,7 @@ pub struct PythonDownloadRequest { pub(crate) prereleases: Option, } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum ArchRequest { Explicit(Arch), Environment(Arch), diff --git a/crates/uv-python/src/environment.rs b/crates/uv-python/src/environment.rs index 34fa3eee9..02f9fd683 100644 --- a/crates/uv-python/src/environment.rs +++ b/crates/uv-python/src/environment.rs @@ -7,6 +7,7 @@ use owo_colors::OwoColorize; use tracing::debug; use uv_cache::Cache; +use uv_configuration::PreviewMode; use uv_fs::{LockedFile, Simplified}; use uv_pep440::Version; @@ -152,6 +153,7 @@ impl PythonEnvironment { request: &PythonRequest, preference: EnvironmentPreference, cache: &Cache, + preview: PreviewMode, ) -> Result { let installation = match find_python_installation( request, @@ -159,6 +161,7 @@ impl PythonEnvironment { // Ignore managed installations when looking for environments PythonPreference::OnlySystem, cache, + preview, )? { Ok(installation) => installation, Err(err) => return Err(EnvironmentNotFound::from(err).into()), diff --git a/crates/uv-python/src/installation.rs b/crates/uv-python/src/installation.rs index 611dc2007..d46643d21 100644 --- a/crates/uv-python/src/installation.rs +++ b/crates/uv-python/src/installation.rs @@ -1,10 +1,14 @@ use std::fmt; +use std::hash::{Hash, Hasher}; use std::str::FromStr; +use indexmap::IndexMap; +use ref_cast::RefCast; use tracing::{debug, info}; use uv_cache::Cache; use uv_client::BaseClientBuilder; +use uv_configuration::PreviewMode; use uv_pep440::{Prerelease, Version}; use crate::discovery::{ @@ -54,8 +58,10 @@ impl PythonInstallation { environments: EnvironmentPreference, preference: PythonPreference, cache: &Cache, + preview: PreviewMode, ) -> Result { - let installation = find_python_installation(request, environments, preference, cache)??; + let installation = + find_python_installation(request, environments, preference, cache, preview)??; Ok(installation) } @@ -66,12 +72,14 @@ impl PythonInstallation { environments: EnvironmentPreference, preference: PythonPreference, cache: &Cache, + preview: PreviewMode, ) -> Result { Ok(find_best_python_installation( request, environments, preference, cache, + preview, )??) } @@ -89,11 +97,12 @@ impl PythonInstallation { python_install_mirror: Option<&str>, pypy_install_mirror: Option<&str>, python_downloads_json_url: Option<&str>, + preview: PreviewMode, ) -> Result { let request = request.unwrap_or(&PythonRequest::Default); // Search for the installation - let err = match Self::find(request, environments, preference, cache) { + let err = match Self::find(request, environments, preference, cache, preview) { Ok(installation) => return Ok(installation), Err(err) => err, }; @@ -129,6 +138,7 @@ impl PythonInstallation { python_install_mirror, pypy_install_mirror, python_downloads_json_url, + preview, ) .await { @@ -149,6 +159,7 @@ impl PythonInstallation { python_install_mirror: Option<&str>, pypy_install_mirror: Option<&str>, python_downloads_json_url: Option<&str>, + preview: PreviewMode, ) -> Result { let installations = ManagedPythonInstallations::from_settings(None)?.init()?; let installations_dir = installations.root(); @@ -180,6 +191,21 @@ impl PythonInstallation { installed.ensure_externally_managed()?; installed.ensure_sysconfig_patched()?; installed.ensure_canonical_executables()?; + + let minor_version = installed.minor_version_key(); + let highest_patch = installations + .find_all()? + .filter(|installation| installation.minor_version_key() == minor_version) + .filter_map(|installation| installation.version().patch()) + .fold(0, std::cmp::max); + if installed + .version() + .patch() + .is_some_and(|p| p >= highest_patch) + { + installed.ensure_minor_version_link(preview)?; + } + if let Err(e) = installed.ensure_dylib_patched() { e.warn_user(&installed); } @@ -340,6 +366,14 @@ impl PythonInstallationKey { format!("{}.{}.{}", self.major, self.minor, self.patch) } + pub fn major(&self) -> u8 { + self.major + } + + pub fn minor(&self) -> u8 { + self.minor + } + pub fn arch(&self) -> &Arch { &self.arch } @@ -490,3 +524,112 @@ impl Ord for PythonInstallationKey { .then_with(|| self.variant.cmp(&other.variant).reverse()) } } + +/// A view into a [`PythonInstallationKey`] that excludes the patch and prerelease versions. +#[derive(Clone, Eq, Ord, PartialOrd, RefCast)] +#[repr(transparent)] +pub struct PythonInstallationMinorVersionKey(PythonInstallationKey); + +impl PythonInstallationMinorVersionKey { + /// Cast a `&PythonInstallationKey` to a `&PythonInstallationMinorVersionKey` using ref-cast. + #[inline] + pub fn ref_cast(key: &PythonInstallationKey) -> &Self { + RefCast::ref_cast(key) + } + + /// Takes an [`IntoIterator`] of [`ManagedPythonInstallation`]s and returns an [`FxHashMap`] from + /// [`PythonInstallationMinorVersionKey`] to the installation with highest [`PythonInstallationKey`] + /// for that minor version key. + #[inline] + pub fn highest_installations_by_minor_version_key<'a, I>( + installations: I, + ) -> IndexMap + where + I: IntoIterator, + { + let mut minor_versions = IndexMap::default(); + for installation in installations { + minor_versions + .entry(installation.minor_version_key().clone()) + .and_modify(|high_installation: &mut ManagedPythonInstallation| { + if installation.key() >= high_installation.key() { + *high_installation = installation.clone(); + } + }) + .or_insert_with(|| installation.clone()); + } + minor_versions + } +} + +impl fmt::Display for PythonInstallationMinorVersionKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Display every field on the wrapped key except the patch + // and prerelease (with special formatting for the variant). + let variant = match self.0.variant { + PythonVariant::Default => String::new(), + PythonVariant::Freethreaded => format!("+{}", self.0.variant), + }; + write!( + f, + "{}-{}.{}{}-{}-{}-{}", + self.0.implementation, + self.0.major, + self.0.minor, + variant, + self.0.os, + self.0.arch, + self.0.libc, + ) + } +} + +impl fmt::Debug for PythonInstallationMinorVersionKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Display every field on the wrapped key except the patch + // and prerelease. + f.debug_struct("PythonInstallationMinorVersionKey") + .field("implementation", &self.0.implementation) + .field("major", &self.0.major) + .field("minor", &self.0.minor) + .field("variant", &self.0.variant) + .field("os", &self.0.os) + .field("arch", &self.0.arch) + .field("libc", &self.0.libc) + .finish() + } +} + +impl PartialEq for PythonInstallationMinorVersionKey { + fn eq(&self, other: &Self) -> bool { + // Compare every field on the wrapped key except the patch + // and prerelease. + self.0.implementation == other.0.implementation + && self.0.major == other.0.major + && self.0.minor == other.0.minor + && self.0.os == other.0.os + && self.0.arch == other.0.arch + && self.0.libc == other.0.libc + && self.0.variant == other.0.variant + } +} + +impl Hash for PythonInstallationMinorVersionKey { + fn hash(&self, state: &mut H) { + // Hash every field on the wrapped key except the patch + // and prerelease. + self.0.implementation.hash(state); + self.0.major.hash(state); + self.0.minor.hash(state); + self.0.os.hash(state); + self.0.arch.hash(state); + self.0.libc.hash(state); + self.0.variant.hash(state); + } +} + +impl From for PythonInstallationMinorVersionKey { + fn from(key: PythonInstallationKey) -> Self { + PythonInstallationMinorVersionKey(key) + } +} diff --git a/crates/uv-python/src/interpreter.rs b/crates/uv-python/src/interpreter.rs index 26d810db5..19e790b7e 100644 --- a/crates/uv-python/src/interpreter.rs +++ b/crates/uv-python/src/interpreter.rs @@ -26,6 +26,7 @@ use uv_platform_tags::{Tags, TagsError}; use uv_pypi_types::{ResolverMarkerEnvironment, Scheme}; use crate::implementation::LenientImplementationName; +use crate::managed::ManagedPythonInstallations; use crate::platform::{Arch, Libc, Os}; use crate::pointer_size::PointerSize; use crate::{ @@ -168,7 +169,7 @@ impl Interpreter { Ok(path) => path, Err(err) => { warn!("Failed to find base Python executable: {err}"); - uv_fs::canonicalize_executable(base_executable)? + canonicalize_executable(base_executable)? } }; Ok(base_python) @@ -263,6 +264,21 @@ impl Interpreter { self.prefix.is_some() } + /// Returns `true` if this interpreter is managed by uv. + /// + /// Returns `false` if we cannot determine the path of the uv managed Python interpreters. + pub fn is_managed(&self) -> bool { + let Ok(installations) = ManagedPythonInstallations::from_settings(None) else { + return false; + }; + + installations + .find_all() + .into_iter() + .flatten() + .any(|install| install.path() == self.sys_base_prefix) + } + /// Returns `Some` if the environment is externally managed, optionally including an error /// message from the `EXTERNALLY-MANAGED` file. /// @@ -483,10 +499,19 @@ impl Interpreter { /// `python-build-standalone`. /// /// See: + #[cfg(unix)] pub fn is_standalone(&self) -> bool { self.standalone } + /// Returns `true` if an [`Interpreter`] may be a `python-build-standalone` interpreter. + // TODO(john): Replace this approach with patching sysconfig on Windows to + // set `PYTHON_BUILD_STANDALONE=1`.` + #[cfg(windows)] + pub fn is_standalone(&self) -> bool { + self.standalone || (self.is_managed() && self.markers().implementation_name() == "cpython") + } + /// Return the [`Layout`] environment used to install wheels into this interpreter. pub fn layout(&self) -> Layout { Layout { @@ -608,6 +633,29 @@ impl Interpreter { } } +/// Calls `fs_err::canonicalize` on Unix. On Windows, avoids attempting to resolve symlinks +/// but will resolve junctions if they are part of a trampoline target. +pub fn canonicalize_executable(path: impl AsRef) -> std::io::Result { + let path = path.as_ref(); + debug_assert!( + path.is_absolute(), + "path must be absolute: {}", + path.display() + ); + + #[cfg(windows)] + { + if let Ok(Some(launcher)) = uv_trampoline_builder::Launcher::try_from_path(path) { + Ok(dunce::canonicalize(launcher.python_path)?) + } else { + Ok(path.to_path_buf()) + } + } + + #[cfg(unix)] + fs_err::canonicalize(path) +} + /// The `EXTERNALLY-MANAGED` file in a Python installation. /// /// See: @@ -935,7 +983,7 @@ impl InterpreterInfo { // We check the timestamp of the canonicalized executable to check if an underlying // interpreter has been modified. - let modified = uv_fs::canonicalize_executable(&absolute) + let modified = canonicalize_executable(&absolute) .and_then(Timestamp::from_path) .map_err(|err| { if err.kind() == io::ErrorKind::NotFound { diff --git a/crates/uv-python/src/lib.rs b/crates/uv-python/src/lib.rs index 0fa2d46ab..d408bc199 100644 --- a/crates/uv-python/src/lib.rs +++ b/crates/uv-python/src/lib.rs @@ -11,9 +11,13 @@ pub use crate::discovery::{ }; pub use crate::downloads::PlatformRequest; pub use crate::environment::{InvalidEnvironmentKind, PythonEnvironment}; -pub use crate::implementation::ImplementationName; -pub use crate::installation::{PythonInstallation, PythonInstallationKey}; -pub use crate::interpreter::{BrokenSymlink, Error as InterpreterError, Interpreter}; +pub use crate::implementation::{ImplementationName, LenientImplementationName}; +pub use crate::installation::{ + PythonInstallation, PythonInstallationKey, PythonInstallationMinorVersionKey, +}; +pub use crate::interpreter::{ + BrokenSymlink, Error as InterpreterError, Interpreter, canonicalize_executable, +}; pub use crate::pointer_size::PointerSize; pub use crate::prefix::Prefix; pub use crate::python_version::PythonVersion; @@ -115,6 +119,7 @@ mod tests { use indoc::{formatdoc, indoc}; use temp_env::with_vars; use test_log::test; + use uv_configuration::PreviewMode; use uv_static::EnvVars; use uv_cache::Cache; @@ -447,6 +452,7 @@ mod tests { EnvironmentPreference::OnlySystem, PythonPreference::default(), &context.cache, + PreviewMode::Disabled, ) }); assert!( @@ -461,6 +467,7 @@ mod tests { EnvironmentPreference::OnlySystem, PythonPreference::default(), &context.cache, + PreviewMode::Disabled, ) }); assert!( @@ -485,6 +492,7 @@ mod tests { EnvironmentPreference::OnlySystem, PythonPreference::default(), &context.cache, + PreviewMode::Disabled, ) }); assert!( @@ -506,6 +514,7 @@ mod tests { EnvironmentPreference::OnlySystem, PythonPreference::default(), &context.cache, + PreviewMode::Disabled, ) })??; assert!( @@ -567,6 +576,7 @@ mod tests { EnvironmentPreference::OnlySystem, PythonPreference::default(), &context.cache, + PreviewMode::Disabled, ) })??; assert!( @@ -598,6 +608,7 @@ mod tests { EnvironmentPreference::OnlySystem, PythonPreference::default(), &context.cache, + PreviewMode::Disabled, ) }); assert!( @@ -634,6 +645,7 @@ mod tests { EnvironmentPreference::OnlySystem, PythonPreference::default(), &context.cache, + PreviewMode::Disabled, ) })??; assert!( @@ -665,6 +677,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert_eq!( @@ -686,6 +699,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert_eq!( @@ -711,6 +725,7 @@ mod tests { EnvironmentPreference::OnlySystem, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert_eq!( @@ -736,6 +751,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert_eq!( @@ -758,6 +774,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; @@ -791,6 +808,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; @@ -824,6 +842,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })?; assert!( @@ -845,6 +864,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })?; assert!( @@ -866,6 +886,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; @@ -899,6 +920,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; @@ -935,6 +957,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert!( @@ -965,6 +988,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert!( @@ -999,6 +1023,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert_eq!( @@ -1024,6 +1049,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert_eq!( @@ -1050,6 +1076,7 @@ mod tests { EnvironmentPreference::OnlyVirtual, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) }, )??; @@ -1074,6 +1101,7 @@ mod tests { EnvironmentPreference::OnlyVirtual, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) }, )?; @@ -1095,6 +1123,7 @@ mod tests { EnvironmentPreference::OnlySystem, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) }, )??; @@ -1117,6 +1146,7 @@ mod tests { EnvironmentPreference::OnlyVirtual, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) }, )??; @@ -1149,6 +1179,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) }, )??; @@ -1169,6 +1200,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) }, )??; @@ -1195,6 +1227,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; @@ -1212,6 +1245,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; @@ -1240,6 +1274,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert_eq!( @@ -1277,6 +1312,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) }, )??; @@ -1304,6 +1340,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) }, )??; @@ -1328,6 +1365,7 @@ mod tests { EnvironmentPreference::ExplicitSystem, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) }, )??; @@ -1352,6 +1390,7 @@ mod tests { EnvironmentPreference::OnlySystem, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) }, )??; @@ -1376,6 +1415,7 @@ mod tests { EnvironmentPreference::OnlyVirtual, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) }, )??; @@ -1413,6 +1453,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) }, )??; @@ -1440,6 +1481,7 @@ mod tests { EnvironmentPreference::OnlySystem, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert_eq!( @@ -1456,6 +1498,7 @@ mod tests { EnvironmentPreference::OnlySystem, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert_eq!( @@ -1472,6 +1515,7 @@ mod tests { EnvironmentPreference::OnlySystem, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })?; assert!( @@ -1493,6 +1537,7 @@ mod tests { EnvironmentPreference::OnlyVirtual, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })?; assert!( @@ -1509,6 +1554,7 @@ mod tests { EnvironmentPreference::OnlySystem, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) }, )?; @@ -1530,6 +1576,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert_eq!( @@ -1544,6 +1591,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })?; assert!( @@ -1557,6 +1605,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })?; assert!( @@ -1585,6 +1634,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert_eq!( @@ -1600,6 +1650,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert_eq!( @@ -1629,6 +1680,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert_eq!( @@ -1644,6 +1696,7 @@ mod tests { EnvironmentPreference::ExplicitSystem, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert_eq!( @@ -1659,6 +1712,7 @@ mod tests { EnvironmentPreference::OnlyVirtual, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert_eq!( @@ -1674,6 +1728,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert_eq!( @@ -1697,6 +1752,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert_eq!( @@ -1711,6 +1767,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert_eq!( @@ -1734,6 +1791,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert_eq!( @@ -1753,6 +1811,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) }, )??; @@ -1781,6 +1840,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert_eq!( @@ -1802,6 +1862,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })?; assert!( @@ -1831,6 +1892,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert_eq!( @@ -1846,6 +1908,7 @@ mod tests { EnvironmentPreference::ExplicitSystem, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })?; assert!( @@ -1872,6 +1935,7 @@ mod tests { EnvironmentPreference::ExplicitSystem, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) }) .unwrap() @@ -1896,6 +1960,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })?; assert!( @@ -1912,6 +1977,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert_eq!( @@ -1926,6 +1992,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert_eq!( @@ -1951,6 +2018,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert_eq!( @@ -1965,6 +2033,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert_eq!( @@ -1990,6 +2059,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert_eq!( @@ -2016,6 +2086,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert_eq!( @@ -2042,6 +2113,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert_eq!( @@ -2068,6 +2140,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert_eq!( @@ -2094,6 +2167,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert_eq!( @@ -2121,6 +2195,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })?; assert!( @@ -2142,6 +2217,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert_eq!( @@ -2156,6 +2232,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert_eq!( @@ -2181,6 +2258,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert_eq!( @@ -2195,6 +2273,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; assert_eq!( @@ -2232,6 +2311,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) }) .unwrap() @@ -2249,6 +2329,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) }) .unwrap() @@ -2290,6 +2371,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) }) .unwrap() @@ -2307,6 +2389,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) }) .unwrap() @@ -2343,6 +2426,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) }) .unwrap() @@ -2365,6 +2449,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) }) .unwrap() @@ -2387,6 +2472,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) }) .unwrap() @@ -2425,6 +2511,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; @@ -2477,6 +2564,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, + PreviewMode::Disabled, ) })??; diff --git a/crates/uv-python/src/managed.rs b/crates/uv-python/src/managed.rs index edf4087e7..e7287fe72 100644 --- a/crates/uv-python/src/managed.rs +++ b/crates/uv-python/src/managed.rs @@ -2,6 +2,8 @@ use core::fmt; use std::cmp::Reverse; use std::ffi::OsStr; use std::io::{self, Write}; +#[cfg(windows)] +use std::os::windows::fs::MetadataExt; use std::path::{Path, PathBuf}; use std::str::FromStr; @@ -10,8 +12,11 @@ use itertools::Itertools; use same_file::is_same_file; use thiserror::Error; use tracing::{debug, warn}; +use uv_configuration::PreviewMode; +#[cfg(windows)] +use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT; -use uv_fs::{LockedFile, Simplified, symlink_or_copy_file}; +use uv_fs::{LockedFile, Simplified, replace_symlink, symlink_or_copy_file}; use uv_state::{StateBucket, StateStore}; use uv_static::EnvVars; use uv_trampoline_builder::{Launcher, windows_python_launcher}; @@ -25,7 +30,9 @@ use crate::libc::LibcDetectionError; use crate::platform::Error as PlatformError; use crate::platform::{Arch, Libc, Os}; use crate::python_version::PythonVersion; -use crate::{PythonRequest, PythonVariant, macos_dylib, sysconfig}; +use crate::{ + PythonInstallationMinorVersionKey, PythonRequest, PythonVariant, macos_dylib, sysconfig, +}; #[derive(Error, Debug)] pub enum Error { @@ -51,6 +58,8 @@ pub enum Error { }, #[error("Missing expected Python executable at {}", _0.user_display())] MissingExecutable(PathBuf), + #[error("Missing expected target directory for Python minor version link at {}", _0.user_display())] + MissingPythonMinorVersionLinkTargetDirectory(PathBuf), #[error("Failed to create canonical Python executable at {} from {}", to.user_display(), from.user_display())] CanonicalizeExecutable { from: PathBuf, @@ -65,6 +74,13 @@ pub enum Error { #[source] err: io::Error, }, + #[error("Failed to create Python minor version link directory at {} from {}", to.user_display(), from.user_display())] + PythonMinorVersionLinkDirectory { + from: PathBuf, + to: PathBuf, + #[source] + err: io::Error, + }, #[error("Failed to create directory for Python executable link at {}", to.user_display())] ExecutableDirectory { to: PathBuf, @@ -339,7 +355,7 @@ impl ManagedPythonInstallation { /// The path to this managed installation's Python executable. /// - /// If the installation has multiple execututables i.e., `python`, `python3`, etc., this will + /// If the installation has multiple executables i.e., `python`, `python3`, etc., this will /// return the _canonical_ executable name which the other names link to. On Unix, this is /// `python{major}.{minor}{variant}` and on Windows, this is `python{exe}`. /// @@ -383,13 +399,11 @@ impl ManagedPythonInstallation { exe = std::env::consts::EXE_SUFFIX ); - let executable = if cfg!(unix) || *self.implementation() == ImplementationName::GraalPy { - self.python_dir().join("bin").join(name) - } else if cfg!(windows) { - self.python_dir().join(name) - } else { - unimplemented!("Only Windows and Unix systems are supported.") - }; + let executable = executable_path_from_base( + self.python_dir().as_path(), + &name, + &LenientImplementationName::from(*self.implementation()), + ); // Workaround for python-build-standalone v20241016 which is missing the standard // `python.exe` executable in free-threaded distributions on Windows. @@ -442,6 +456,10 @@ impl ManagedPythonInstallation { &self.key } + pub fn minor_version_key(&self) -> &PythonInstallationMinorVersionKey { + PythonInstallationMinorVersionKey::ref_cast(&self.key) + } + pub fn satisfies(&self, request: &PythonRequest) -> bool { match request { PythonRequest::File(path) => self.executable(false) == *path, @@ -503,6 +521,30 @@ impl ManagedPythonInstallation { Ok(()) } + /// Ensure the environment contains the symlink directory (or junction on Windows) + /// pointing to the patch directory for this minor version. + pub fn ensure_minor_version_link(&self, preview: PreviewMode) -> Result<(), Error> { + if let Some(minor_version_link) = PythonMinorVersionLink::from_installation(self, preview) { + minor_version_link.create_directory()?; + } + Ok(()) + } + + /// If the environment contains a symlink directory (or junction on Windows), + /// update it to the latest patch directory for this minor version. + /// + /// Unlike [`ensure_minor_version_link`], will not create a new symlink directory + /// if one doesn't already exist, + pub fn update_minor_version_link(&self, preview: PreviewMode) -> Result<(), Error> { + if let Some(minor_version_link) = PythonMinorVersionLink::from_installation(self, preview) { + if !minor_version_link.exists() { + return Ok(()); + } + minor_version_link.create_directory()?; + } + Ok(()) + } + /// Ensure the environment is marked as externally managed with the /// standard `EXTERNALLY-MANAGED` file. pub fn ensure_externally_managed(&self) -> Result<(), Error> { @@ -567,54 +609,8 @@ impl ManagedPythonInstallation { Ok(()) } - /// Create a link to the managed Python executable. - /// - /// If the file already exists at the target path, an error will be returned. - pub fn create_bin_link(&self, target: &Path) -> Result<(), Error> { - let python = self.executable(false); - - let bin = target.parent().ok_or(Error::NoExecutableDirectory)?; - fs_err::create_dir_all(bin).map_err(|err| Error::ExecutableDirectory { - to: bin.to_path_buf(), - err, - })?; - - if cfg!(unix) { - // Note this will never copy on Unix — we use it here to allow compilation on Windows - match symlink_or_copy_file(&python, target) { - Ok(()) => Ok(()), - Err(err) if err.kind() == io::ErrorKind::NotFound => { - Err(Error::MissingExecutable(python.clone())) - } - Err(err) => Err(Error::LinkExecutable { - from: python, - to: target.to_path_buf(), - err, - }), - } - } else if cfg!(windows) { - // TODO(zanieb): Install GUI launchers as well - let launcher = windows_python_launcher(&python, false)?; - - // OK to use `std::fs` here, `fs_err` does not support `File::create_new` and we attach - // error context anyway - #[allow(clippy::disallowed_types)] - { - std::fs::File::create_new(target) - .and_then(|mut file| file.write_all(launcher.as_ref())) - .map_err(|err| Error::LinkExecutable { - from: python, - to: target.to_path_buf(), - err, - }) - } - } else { - unimplemented!("Only Windows and Unix systems are supported.") - } - } - /// Returns `true` if the path is a link to this installation's binary, e.g., as created by - /// [`ManagedPythonInstallation::create_bin_link`]. + /// [`create_bin_link`]. pub fn is_bin_link(&self, path: &Path) -> bool { if cfg!(unix) { is_same_file(path, self.executable(false)).unwrap_or_default() @@ -625,7 +621,11 @@ impl ManagedPythonInstallation { if !matches!(launcher.kind, uv_trampoline_builder::LauncherKind::Python) { return false; } - launcher.python_path == self.executable(false) + // We canonicalize the target path of the launcher in case it includes a minor version + // junction directory. If canonicalization fails, we check against the launcher path + // directly. + dunce::canonicalize(&launcher.python_path).unwrap_or(launcher.python_path) + == self.executable(false) } else { unreachable!("Only Windows and Unix are supported") } @@ -669,6 +669,229 @@ impl ManagedPythonInstallation { } } +/// A representation of a minor version symlink directory (or junction on Windows) +/// linking to the home directory of a Python installation. +#[derive(Clone, Debug)] +pub struct PythonMinorVersionLink { + /// The symlink directory (or junction on Windows). + pub symlink_directory: PathBuf, + /// The full path to the executable including the symlink directory + /// (or junction on Windows). + pub symlink_executable: PathBuf, + /// The target directory for the symlink. This is the home directory for + /// a Python installation. + pub target_directory: PathBuf, +} + +impl PythonMinorVersionLink { + /// Attempt to derive a path from an executable path that substitutes a minor + /// version symlink directory (or junction on Windows) for the patch version + /// directory. + /// + /// The implementation is expected to be CPython and, on Unix, the base Python is + /// expected to be in `/bin/` on Unix. If either condition isn't true, + /// return [`None`]. + /// + /// # Examples + /// + /// ## Unix + /// For a Python 3.10.8 installation in `/path/to/uv/python/cpython-3.10.8-macos-aarch64-none/bin/python3.10`, + /// the symlink directory would be `/path/to/uv/python/cpython-3.10-macos-aarch64-none` and the executable path including the + /// symlink directory would be `/path/to/uv/python/cpython-3.10-macos-aarch64-none/bin/python3.10`. + /// + /// ## Windows + /// For a Python 3.10.8 installation in `C:\path\to\uv\python\cpython-3.10.8-windows-x86_64-none\python.exe`, + /// the junction would be `C:\path\to\uv\python\cpython-3.10-windows-x86_64-none` and the executable path including the + /// junction would be `C:\path\to\uv\python\cpython-3.10-windows-x86_64-none\python.exe`. + pub fn from_executable( + executable: &Path, + key: &PythonInstallationKey, + preview: PreviewMode, + ) -> Option { + let implementation = key.implementation(); + if !matches!( + implementation, + LenientImplementationName::Known(ImplementationName::CPython) + ) { + // We don't currently support transparent upgrades for PyPy or GraalPy. + return None; + } + let executable_name = executable + .file_name() + .expect("Executable file name should exist"); + let symlink_directory_name = PythonInstallationMinorVersionKey::ref_cast(key).to_string(); + let parent = executable + .parent() + .expect("Executable should have parent directory"); + + // The home directory of the Python installation + let target_directory = if cfg!(unix) { + if parent + .components() + .next_back() + .is_some_and(|c| c.as_os_str() == "bin") + { + parent.parent()?.to_path_buf() + } else { + return None; + } + } else if cfg!(windows) { + parent.to_path_buf() + } else { + unimplemented!("Only Windows and Unix systems are supported.") + }; + let symlink_directory = target_directory.with_file_name(symlink_directory_name); + // If this would create a circular link, return `None`. + if target_directory == symlink_directory { + return None; + } + // The full executable path including the symlink directory (or junction). + let symlink_executable = executable_path_from_base( + symlink_directory.as_path(), + &executable_name.to_string_lossy(), + implementation, + ); + let minor_version_link = Self { + symlink_directory, + symlink_executable, + target_directory, + }; + // If preview mode is disabled, still return a `MinorVersionSymlink` for + // existing symlinks, allowing continued operations without the `--preview` + // flag after initial symlink directory installation. + if preview.is_disabled() && !minor_version_link.exists() { + return None; + } + Some(minor_version_link) + } + + pub fn from_installation( + installation: &ManagedPythonInstallation, + preview: PreviewMode, + ) -> Option { + PythonMinorVersionLink::from_executable( + installation.executable(false).as_path(), + installation.key(), + preview, + ) + } + + pub fn create_directory(&self) -> Result<(), Error> { + match replace_symlink( + self.target_directory.as_path(), + self.symlink_directory.as_path(), + ) { + Ok(()) => { + debug!( + "Created link {} -> {}", + &self.symlink_directory.user_display(), + &self.target_directory.user_display(), + ); + } + Err(err) if err.kind() == io::ErrorKind::NotFound => { + return Err(Error::MissingPythonMinorVersionLinkTargetDirectory( + self.target_directory.clone(), + )); + } + Err(err) if err.kind() == io::ErrorKind::AlreadyExists => {} + Err(err) => { + return Err(Error::PythonMinorVersionLinkDirectory { + from: self.symlink_directory.clone(), + to: self.target_directory.clone(), + err, + }); + } + } + Ok(()) + } + + pub fn exists(&self) -> bool { + #[cfg(unix)] + { + self.symlink_directory + .symlink_metadata() + .map(|metadata| metadata.file_type().is_symlink()) + .unwrap_or(false) + } + #[cfg(windows)] + { + self.symlink_directory + .symlink_metadata() + .is_ok_and(|metadata| { + // Check that this is a reparse point, which indicates this + // is a symlink or junction. + (metadata.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT) != 0 + }) + } + } +} + +/// Derive the full path to an executable from the given base path and executable +/// name. On Unix, this is, e.g., `/bin/python3.10`. On Windows, this is, +/// e.g., `\python.exe`. +fn executable_path_from_base( + base: &Path, + executable_name: &str, + implementation: &LenientImplementationName, +) -> PathBuf { + if cfg!(unix) + || matches!( + implementation, + &LenientImplementationName::Known(ImplementationName::GraalPy) + ) + { + base.join("bin").join(executable_name) + } else if cfg!(windows) { + base.join(executable_name) + } else { + unimplemented!("Only Windows and Unix systems are supported.") + } +} + +/// Create a link to a managed Python executable. +/// +/// If the file already exists at the link path, an error will be returned. +pub fn create_link_to_executable(link: &Path, executable: PathBuf) -> Result<(), Error> { + let link_parent = link.parent().ok_or(Error::NoExecutableDirectory)?; + fs_err::create_dir_all(link_parent).map_err(|err| Error::ExecutableDirectory { + to: link_parent.to_path_buf(), + err, + })?; + + if cfg!(unix) { + // Note this will never copy on Unix — we use it here to allow compilation on Windows + match symlink_or_copy_file(&executable, link) { + Ok(()) => Ok(()), + Err(err) if err.kind() == io::ErrorKind::NotFound => { + Err(Error::MissingExecutable(executable.clone())) + } + Err(err) => Err(Error::LinkExecutable { + from: executable, + to: link.to_path_buf(), + err, + }), + } + } else if cfg!(windows) { + // TODO(zanieb): Install GUI launchers as well + let launcher = windows_python_launcher(&executable, false)?; + + // OK to use `std::fs` here, `fs_err` does not support `File::create_new` and we attach + // error context anyway + #[allow(clippy::disallowed_types)] + { + std::fs::File::create_new(link) + .and_then(|mut file| file.write_all(launcher.as_ref())) + .map_err(|err| Error::LinkExecutable { + from: executable, + to: link.to_path_buf(), + err, + }) + } + } else { + unimplemented!("Only Windows and Unix systems are supported.") + } +} + // TODO(zanieb): Only used in tests now. /// Generate a platform portion of a key from the environment. pub fn platform_key_from_env() -> Result { diff --git a/crates/uv-python/src/python_version.rs b/crates/uv-python/src/python_version.rs index 30dfccecd..39e8e3429 100644 --- a/crates/uv-python/src/python_version.rs +++ b/crates/uv-python/src/python_version.rs @@ -5,7 +5,7 @@ use std::str::FromStr; use uv_pep440::Version; use uv_pep508::{MarkerEnvironment, StringVersion}; -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct PythonVersion(StringVersion); impl From for PythonVersion { diff --git a/crates/uv-tool/Cargo.toml b/crates/uv-tool/Cargo.toml index d01a3209d..210c17c00 100644 --- a/crates/uv-tool/Cargo.toml +++ b/crates/uv-tool/Cargo.toml @@ -17,6 +17,7 @@ workspace = true [dependencies] uv-cache = { workspace = true } +uv-configuration = { workspace = true } uv-dirs = { workspace = true } uv-distribution-types = { workspace = true } uv-fs = { workspace = true } diff --git a/crates/uv-tool/src/lib.rs b/crates/uv-tool/src/lib.rs index f85075ea6..ee80a2854 100644 --- a/crates/uv-tool/src/lib.rs +++ b/crates/uv-tool/src/lib.rs @@ -1,6 +1,7 @@ use core::fmt; use fs_err as fs; +use uv_configuration::PreviewMode; use uv_dirs::user_executable_directory; use uv_pep440::Version; use uv_pep508::{InvalidNameError, PackageName}; @@ -257,6 +258,7 @@ impl InstalledTools { &self, name: &PackageName, interpreter: Interpreter, + preview: PreviewMode, ) -> Result { let environment_path = self.tool_dir(name); @@ -286,6 +288,8 @@ impl InstalledTools { false, false, false, + false, + preview, )?; Ok(venv) diff --git a/crates/uv-trampoline/src/bounce.rs b/crates/uv-trampoline/src/bounce.rs index 1e90f035d..8d658bdab 100644 --- a/crates/uv-trampoline/src/bounce.rs +++ b/crates/uv-trampoline/src/bounce.rs @@ -78,7 +78,34 @@ fn make_child_cmdline() -> CString { // Only execute the trampoline again if it's a script, otherwise, just invoke Python. match kind { - TrampolineKind::Python => {} + TrampolineKind::Python => { + // SAFETY: `std::env::set_var` is safe to call on Windows, and + // this code only ever runs on Windows. + unsafe { + // Setting this env var will cause `getpath.py` to set + // `executable` to the path to this trampoline. This is + // the approach taken by CPython for Python Launchers + // (in `launcher.c`). This allows virtual environments to + // be correctly detected when using trampolines. + std::env::set_var("__PYVENV_LAUNCHER__", &executable_name); + + // If this is not a virtual environment and `PYTHONHOME` has + // not been set, then set `PYTHONHOME` to the parent directory of + // the executable. This ensures that the correct installation + // directories are added to `sys.path` when running with a junction + // trampoline. + let python_home_set = + std::env::var("PYTHONHOME").is_ok_and(|home| !home.is_empty()); + if !is_virtualenv(python_exe.as_path()) && !python_home_set { + std::env::set_var( + "PYTHONHOME", + python_exe + .parent() + .expect("Python executable should have a parent directory"), + ); + } + } + } TrampolineKind::Script => { // Use the full executable name because CMD only passes the name of the executable (but not the path) // when e.g. invoking `black` instead of `/Scripts/black` and Python then fails @@ -118,6 +145,20 @@ fn push_quoted_path(path: &Path, command: &mut Vec) { command.extend(br#"""#); } +/// Checks if the given executable is part of a virtual environment +/// +/// Checks if a `pyvenv.cfg` file exists in grandparent directory of the given executable. +/// PEP 405 specifies a more robust procedure (checking both the parent and grandparent +/// directory and then scanning for a `home` key), but in practice we have found this to +/// be unnecessary. +fn is_virtualenv(executable: &Path) -> bool { + executable + .parent() + .and_then(Path::parent) + .map(|path| path.join("pyvenv.cfg").is_file()) + .unwrap_or(false) +} + /// Reads the executable binary from the back to find: /// /// * The path to the Python executable @@ -240,10 +281,18 @@ fn read_trampoline_metadata(executable_name: &Path) -> (TrampolineKind, PathBuf) parent_dir.join(path) }; - // NOTICE: dunce adds 5kb~ - let path = dunce::canonicalize(path.as_path()).unwrap_or_else(|_| { - error_and_exit("Failed to canonicalize script path"); - }); + let path = if !path.is_absolute() || matches!(kind, TrampolineKind::Script) { + // NOTICE: dunce adds 5kb~ + // TODO(john): In order to avoid resolving junctions and symlinks for relative paths and + // scripts, we can consider reverting https://github.com/astral-sh/uv/pull/5750/files#diff-969979506be03e89476feade2edebb4689a9c261f325988d3c7efc5e51de26d1L273-L277. + dunce::canonicalize(path.as_path()).unwrap_or_else(|_| { + error_and_exit("Failed to canonicalize script path"); + }) + } else { + // For Python trampolines with absolute paths, we skip `dunce::canonicalize` to + // avoid resolving junctions. + path + }; (kind, path) } diff --git a/crates/uv-trampoline/trampolines/uv-trampoline-aarch64-console.exe b/crates/uv-trampoline/trampolines/uv-trampoline-aarch64-console.exe index 3b7f76564e6f12ab5ad204fdba1c3aad9b9d5637..5f2d6115e1d1df4353a9ccaba6553f887ee55d06 100755 GIT binary patch delta 17149 zcmcJ1d3Y3Mw)d&3ER}>MkUfEP7M7&L4p~~3R62kW5imp%VN3!<2wO-9Ga@?OfC8Es zhqe$D^%BHUx;26Z=8|v5F~H1-!`wRrm2n(LItglrpp%#c(roqpPE`dn_ulW`=lkR4 zdD>I;F6TY(+28XPI**CY6XI@X^mEFBRb>8kPzr^KgNRnS_EwE8c%Enl_jrk%fq+Me z!X<7n|2l{o@z~)a8sQCW#%P@|5U6APnL0=uE$mja z@ZaavN5s+TprHSsLZpnVDqCBz+}HwPDj5*w#8~yNz?9rMB-9KLoQ2;?(pjG*dAcO>WcVE)7YBy4dnJo!&eod!2Lh6G;y-*VIJ;UTw>LQNy~~;w)3xxo?%QGH?~(*h zH)tsp=~w|Ic3?JjT9WOy*NBR|QiSK(L9~ba!-#B=C1PaF4m5a)j_nGfCRw0ocZu@Z zNRn%u+bq3cNMtuQUO!0RuJat%Af?~xtHFZXM{L&O`Fcm+DJ5tN1rdr^V zpO9W|1Mw}vS*a>bT(Ofk`*Cq9jWCq&kJ% z7u5TM7qOs&RpBjlx?C)%>Z|hjF(N7c2wCwn%Ze$YJp@ZSA)?6w9cz|E5AVJeoS96} zmkv2rT&k&|ArA_FS4%@;)sTQ;jwkJY5^^u(!z!93i?s!Vm+!XwZ-Wh8!FjzUWVz6_ z@O^jB62W~t0y2g!70;wj-q&ja6+wo(WfRBDemI`mWB0d0EVm;Rf6tOUch|zxhn>LX z6vdhz5Ox);tsF>uR@Fig4nwvF#OQXGA*- z9}UllxvlMEHDa}t1L<91;(^b1qfI>=ZdK1m7DngX()O{k3oy{0EzF!#hbP}s?}{21 zRg7_mKUdt$iWbbL!04LZ`X{5Z1@Wd(|EuU<3u0}&x-m9Oq(t?t*x6!iqS_T(CCZ8Q zE91rr!u{$C@jIhF!UB8jNi{zhNQ7skIx*pZ*m6rfpYVuqN}V{YHq@2s3c&`_?@z7& z+prM9!8^_+vC=o0T-so2*amKhg7RMTWU`F}r*bK&G6h0Ue8uO(%|p2sFni;{$<7T6Ha zVU@oM9gv?^f0~>qysf^Poc3rcHE>_^c5=!0*&*r(51SD@)f9c5`weYOd;dV8h2%9t z02QJAoGiqrK@ z84ys}IDm>s>dkJBQP-ywi_LcR&nbxx=;bNQYlfxlx(@XVQyL0H%4&f}Yzd@e#T4DX zD-bRgYx5Wfk70y?RM`mHnrrOtU`XM>P_iY1&SV74$?zTQ_M5}<;OZkh<6tyw_WZ0# zf~&u2t7}t-OaX3jxbl26)OLl5pYf$36!;PpzeR{@7oZi*F>D&F-cg=t_1)B&!C34w z#0Yi5h$OK-TrC_?9@cS5B3BZ)m;O@yA4Uumqc{%vGj(jiz$Ge}7Si7a~- zaS7zf4m;r{Fr68d6FCctp`9jI-vIWds&mvB;gDK4%A!V&enKp{r9MA8OH93?{&Dmu z@!?zQ_oJ5y*VPBo>cv+FtD)(Wgl+2d^n}jWWA#ENFGMe8~+y%%Z+AyzpICX{~?qvwd=Ccw_$p}<7 z*DwrGHs8x|pt3oP;SltTfKljY7^gTk-^&lfF#r$=MpHVH3OSFO+ObdTNaZm(gqo}Z zO*yP-$C(f$WP7|)<8)}YzeIFSUlmGz)>tp%G_2*QoID)PtmAw+qUB+D0hWB9Z)Nr9 zOF$wKEoD;ojvXo-QQc#gW;utuZ}R*A;>c$-`b|w)~K!t zsUwHr&HbEWdyt&xuZA{$pDt6j`+3FI77VyovAJ&qXH8R^Cd_n9H2hMa5MsRqLcWH7 zQLsZx0KMA?v9a!HL#gr_+LPe}4g;xAqAcA{tuuCQ`9iErMoxif;Ln6#U?o5P4&#mX z90ZvXjG-!keee`_h`kR0*Twb!?A}LkFQun2#)l0Cy@Uk*jJ-K*?PYculI9b zA={lmnRvoL`^HYWYLdvl+u32fma#)8LQ~45H4haEiVl zWwWJ?D)S&1U#PPAFcAAhw7pN2;9rrYQm8~wEBYGdaUhgB+xpEp$j_L=W1*t@Sa!61 z^Bc`JC*TFlFBQmPbTU%z#Ke<;IuU5A^=o=t31AnJ_Nid>H2k)FVE4bW#*QS=;<<(2 zJ=kdhe({R4FbdI3vxt5lG^H(7^lPIEuv(5h=goV5kgZpGqGwDn44 zttHB2O^{8Mg=%N!=I6BMfdl>9Ek2<#c@WxeI6k&G5R2j`zObLe&UGo)nk&V(AG)p; zImGyOX&^WfMwJ?5{}EVz#uSYJ(R>6p=Ym5A(Vh)`xz;k=rGf)F*i{p|$H9u9?TS_T zQ@neF?Ye2V!9S!eCigkCJ&rXUyg)MK{}E)&3$QKlvt=+u0m^%pL)WoOmirH-PP;%)6q$Ii`V1#j)&;46L7|*rbqDl%fbRiG$4C@ECVNFW|IV!i};mja3 zViFVba0HO1m`KfCF;Pqe6TnCdq=*_LgRpqSKyu}yeI|adb!+=tg8KSmJYx|+P6frA zW1~Ikc;|WF!72&h?T2EtcA=U6n zvur$vCGbE1@G`D}R8k4QSmfPluk)q`xSCIku3kXz&cHfvO+J1>buV1yTx_Pzl8yy~ zsYr%6wX@{cQe)lakbss-elIrKx7e4=#hrO|B|%iDNx`~1=_BshX`^r;l#Q5L}M7L!#{&9Naby3_aTpb?g2!GPrx;m}AKQX$T#=Dh=~|z9T=U zID;yc5QsTKIkE50in}O*DqsA@?tV5{X=n=*8rndRbNk6@X@VTOdLo_hj(1K!Lr|}L z(bXqyW^y_tT@ZsqJkklgI|}8A2ln5DaWun;KZ9!8>NTr!(nCUPk_5{Ip3Kr|k>WfF znCJ`3YhWTT<6KZaQNbdO(~D!MvOu5)A9j0P3bfsW#jpK2(oI{pPV4<9+T9EzV=mwz zAIUjaGYp%(l^w(+lLLC_gdSoc2@9m5QBM{7QQd;F&P^n4jD#0I-a<{Au4%`Olrqru z6r^b(>f}BaOE1LGyj^jNP$vmu&+LLxA+EE-AJ5M_XV!J0*5TbL)P;;^-GS?9xvjg% zyJj!Y&1wdQtKr!TEN?=je4awebc_e^kwq4vpj_stj8`4m4+}q1KhGW|4!os`Io6@W zLun7PgrhC+S$ZBhOP!IED&(olavn^4ME9JLo{!->Fep+h7WA`5OKYf0E>$n&R7Fq1 z5S<;Ubuu1aR3587V#{)jMpqXIv&huRfrJmIFZF0Kyj#7b7RF+RVM+l_{p(AQU{l^y z1gunYy~Kef6@dh?p%FMj;^=;PI5p)Cq)PE6`}K6B3LGT)Jk0?PxWM5&cTbq&Kq>UJ z$y&wM^|9h^lSCIsC`RN}PflxAPffdQQT$1PVx6@!us12tUgygfZE2medM}6N*?NNW zY+VawchZop(~^g5o7N`f*?ddLpBqAzm)k{KTdQd6`9`#HzH$ta)GM*u6rU2AQGrf zMH`>n^~&eZ)W`3NSC=Nsj%!;)H&_4I==w{i2dTze(5h~@%NYI87Spb9gom{HA~Z6| zeOLdYxG#z^?Or1dg<`OTqCB$OAgKhEB0dyJ(5nhW=4<9bhR1BYrls%ef%URB@rj5B zbfJTh{KwOfy8EuB>XfN#9fv!9D6_K-By%|K&UB?LAQx}CwnN*uOJq|=L=qtCQ8WBB{i_S+zIxk@~0c zSR+a&weNsmZf;#KYurItVj^5?AT0S02$%jh2p{OjOI%r4_5D9F!IiR9K#7Dr_hByh zWo5#4qOPO&e>O26ltp?{`t3fS5t-VTLShXIVn*M>{kdh<+p{~=#OVo+{FtU9I9VLJ zj>2;veFX=v*o}6-wlJK7K6KcE^O42#eudpHDmZ}g;j4!H#R0~FtLJo(D*^4Sytqk; z%%ixoUbu$WzlRw9qN@##~TA{I- zwRk3xGB4V*^@N$<`C!F|tuxnguoB|i#fkg(e+dC2*|uI3#rEa6$8bnu`zcs0TdM{aj8LW zZ7TW81A((L9&Ie;Jja`#p-{;c*l z@-uh6NZaIh`DVvrU}7wP)9nKw#_j~C~w23LVa+86{6euy#5V-K@<1x z_f|Nkb7iOml_9QYH)B0shj9zouWZ)49p3u92Aoyal;G&g1CxGEMJ3dm?Yjt0LvMJJ z+-!K$0x&6VD;$h2);N#Mk>m7iTL#9g$rJdRBYgGd!zXy7oP#nBIZv^{Z$BF|%PU9V95#o#E1q3iHwK5)vvhQ%0< z9kZ8?^PGc?-+<8oX*R!ep9n75Hzc|~LSK?hQwk!eGZmr3G6bnVaGZv83R0;ldCjU5 zxk(PCauuv9L=f7!^EGs)Cz0!b7}DO-p&7>DOL3J(ItxDu<|M{YYOR$|jtTBFTpixI z#QksR(+y{Bl1muuEc^)XFC(An3cwsGq=q?mBbyrhnB#+L^x}xXs@3M$In3B##y@~cM zhL!q94+37v##K$7us2`I169t+eLM{OF;Vl zBHl0k8A#zyIXZi{vr@|_3`KapgP9vFkMKOiN`7dbyN@7Ww$czGP~lz>CR0QxiVj)6 zjUX6To2*Y>oI%V|qT>SZfZ5v88tBpv#kjQ($$u1V^Wt3qQT5+}3qZIlnWix1Jnk^F zl@;?ILovA+^mP#J32WSO{yXRn>rWf3=M)a#R^ZN{#_zvH2KyXtFTRroc&?ztyaAl~ zKnM`q8waX0=8qNzsE^Kn&e2DW&k^Kq!%SJJEo_04K|au+g$bK|ql4X7Ibq#E$F&a( zf5FxLjgQ*>Y#tmZB4J-XN36RqPF#abqtWh1oCsx8z=4%_mq+s1D=g!Si`*6=vORj@ z9>;z^$h-;`^aTkcF!K>3&dLsf4f=Y&IHAOFA?~h!iW8-O)Qkcgkct!cm~kQ=^*19< zaJh%-R;kTITP2w2{oi`;MHOc#kGa{H+s^|wXtYuEu# z-m`Ft8*b9t+{&3*q~|J>r7z`iFd@aLApn=-4zi__en9rr56G^4 z`=61u_CvOoks%SsZhs)m`(NX_^q+A#Jm>l$TMT5HBY}@RaNHIT?}nW{J9^n>_kV>Q z@qGh3ea?zUVE4WA$<0CID-e)8jxxP)6zzESPDZI4(bK45q4qwW{NJLz4>t!AIn!aV zSY}xUQfFEw6(u5M*3PC(E=Rv1P~^*))EYL+tve29qN@~<@om1G`IUBD7sHQ${7#Vfj@+Eq4Hdm0_+cL=CoXCDY*5m2qY#Snh{fA?I32bwa0}sk5I*!GxPhc^9 zkyRE~X^^vUE;y6OhaPAu-!8OC;fV3FCmC(VE|B+|7Zh#Vjig3j6kexxA#Ox#7U($v zyVk|5@gTAp7H}1?jLq=$Y}*VZ7nLXP2Zg}}b$2B_bw0lKjqmxkBi`x#-Wv>M47&$E zT_Ih~P|!s@k!zBCbeyNg&rWm*^Qp-Hk0Q523g|qI;AMEp1kZ68f#s&uDbM?%?GW0W z_tCLZ{A7Gg)QqE22%&zpl`2oqr6TJybboOhRqg_w%PD`{Uo6C57SNvCDO3`Kgo1Tb z>20lOiHI*OAh1TF`6z8FHVY6m&Oy{`K%EuP*lZl!NP;JlH_sN0jn7Ar^0gZD*Y=_$ ziNaYaVApAU-qNcb>;6y;x;w*xk=;-}-+Y)UOM|Se{|Ac9KG}&l?St&Q(Z?q(he(|ysQYWXv}2{xp8XGk9SWZ10Oxtcb>JB0 zJ`>pZ{e0~oSX@egA_+ASLPC3ODEI-{-*f9?&gFI_m7K9ip`PJzukC1G{cUkhJKElB^ zF3g$^m>w5u&CQ!xiDEF8wRnn?bygRhUA_7Q<|3mDSyQTMMV}%ecMPS@!&l%x!86?+ zN(7fU1~=$%FNjH=Gq4w}M(_{x{X+2PE~ZYUT`T&d+E9=xdhSxcEU-I%2gPp#v0RVm zGss^6uLH6L;wDdzF)@fS+7ct-?Ta%vup(ao@s~fwc_dOlk7R(W*Ox4C7fUhiH{d3U zChx=DPo#&>CfzkZgc`$$VjW<6f=7P94qp?cC!qd*@x~iOnIG6~{}gYUU0Ts${ag}* zc(ZT+7y7v*&iVo3Oe&vC08j4*OJFdJy8}k!MXJU=OGb`x-8_PDx>p;(Wo9kU*7ot; zk9D=?*=~F@+ntstTievTPfXb=<7))|l*~v-aIEiGJ~PQ>AeKEd;d6eB=zO&X=|8x^ z3w*}K9rM`Gj4K#+h|5601mMx#%djynA<$Wv06+J4ScdF?Z{dl2a$+jIY%bY~AE(uQ z@#ynraPW)ue9nXMu2cPqRJDG%I zz?YBJUk6wCi0DUV<;az&(_l}6rwdilBUiM2&!K<`t~8_x3$ofIdqW%fve-#-B~{hRoyYTkI@f3(VhdCze@@FVXZ)-*DachviDNkwm0FOW@tQMRk zc(oFbIEq?_=C5!7(Rz<+7LH~XBmUE455J!DVN|mHAe(E6zNF@+gpO~snZ-# z$Oxf98j2{qeQ;gN&Ovp}g9{M7htzp@I;S`PVj%8Pga-LlBg{mK9EB}VM8S%W?Dq6b zK#V%=$kP|-L(ItJz{p9y)0hYXyn+Ma4Z&Ch1q*8g z##M(=X1d^fp0l+GjCbF@`2EHmc0VqtJA7}3yQ3U1(5u+pmeY1WczbTo=M!CbVekjb z;H=lA3qgaSaMFV3O6x zsW_gu1#xjK((@+z{A>CrJn{#LCocBkrzr;hS{=Vb&zkn$Ss>QR_o@9C z_yziUSN2~Z&a*{dAXaz#0YLt5KwgiHK(Wrz%yF({nAl8=@!g%bc@?WL7PhE15va*YPN zFT6IzwF(QwRg&uiF``}U!6&W~5+alxaaY>OCu#+vaiY{@LqYX8^yVtW#UeiCA&2fZ z`xGsT^#Inu+4446Mhh9dE<=K|c*6pFD^@UBQa1 z_^!%X_$n_FJkMQ*1!C}p8oqzEGGV-dotV$A_!%tfZH;yzU3wQ6XrFUkK-l`s{>9wi zijQs_=w#E17HD%C56;K^B*fvXLH3NY3Ln?`P|8Rekb7RmB$*x4FO4sDSh<+B_dz`W zWd9f8l%Ra``-yNWa}Hej9@XD$+@`R(JCX}Be>i(Q_QTnu{xXzmQGd2Zc@F4oHIyz5 z$e0jtWH&7s<&qED{mIw>6Z9J_uoWf6E}XV_OkWL%t9abc6+CXo#vLwsG4I6acn1^N zn{j>i4|wjv&bVg8wyd+>H_mkurNa3jM1OyMtZM=XE9gi{ICZXqh_-&O6>$wRWJsee z7ui@C-dQXHQm0yQunS^gkviB?I;Uzy-G@*gG|!1X3$7rr^Dh1_I+grep*Z?NMH!H* zvv6YH!e3ZjU#i0&%2>9Kv&{shVO0QS^#Y4mV!s&W`2@k(B1N^QA&cX-ZDK6cH_}rC z?7H2w@aIMN#B-2w$uJM?F@teqwcPNlTy<)NupHm1o`rymV=WdJix*eHh4OLr_TojtSoOh&QzAyS8`ocV7C)Dv);zo} zI!ITxfBqm^Xjcagt|~?+V61>kg5dKS(3i*}p z05Go40qn&Xo#ZQ-E-%lAr*XBZJC@8CJeD&cNwMk0%dJBF$4lN2#J>vamLDaGE}{PA zAI%m9m1gOyVMFKSgHzRiJaW&dWOI{T%|Ga4Be7WfJ~8Ee@#f`q^^X)MN~&{Nj@a@I z|2qWrlV!1E(~xVG;`_s994Jfh)j5CQ-3rk~f=E^ay&7)%X;F<@K4ZkWSgg!fPS-Bu z1_0H5{yvj^2j3&$o1b&G1VR1D^6SF4YSjw+a0#bc{EG`*?Qpn)d0@x;Q3y=J`}bkcU677EZgz4-H+59NcoVIdqL(yHfq@^DCupAqAW)cy-U$h z&E8CO}KVNOyo9qU?X zci#>D`UPT>I^oe_qB2yS_o&4oQ)bAMq4+l!T4qm{Q85G$I$}?wh#L%Jl&B#+oQ^Y> zLy)GxIyx?hq6@q@YD^#sOfj;2g>%@Fi9&Tw4wH&DF3Zh=5%m2aN@2D?tGe zpO52%nJCZuQ2AK?#P>Kpk=TgO(OU7T^&T;*<_g->5i66%&j9g;&04H4=$j(fhiGGt z^0gD|3A-dN=*vxZb>qsh!mrhXDA`^qoI7h zuZ2rGbGumn{HG!EN8ms~w>or{b@8LnRQ}w!2YEOXS_U`bM5e;eJ;-SI-#f@)PKcy5 zAb7aaArCp2@J`swV>Lul?kH!O^?TKrU}LV4MjvOgPnq_nPn#6K*i!lP0V&VXX<@HsQaT@J}Yh zSN!_kIt=Kb+h)U%r;Ltl6IPgTmkAG;@GTP_GvRMd`0pnC%7k4e6rMKl4l-ei39ULr z<$IRdu+W6dO}N>F&zbNg6CN?)Z%p{13D295LWhzx1ja)&Pzn|(#72qnF_DBKJWatb z-26!;L_^e1AAea;)XFVgBl`aF9Zm`~3BLi)Z4>&n{X5#uz2HiYcXXhK{|4+e zV7v*_OgPPi^GvwZgsV*WgbANBVXX;&Wx{hN{Mv*8`;2u)7|=nA*-&J{6()S#gwL6< z)`ahw@OLKs#Dtej*kQt(CJcSiSTMTLny;qvtfq`UpL_i6P`2SWfNXEVJLXW ze@P}Z-oWSo@IJ$QpKHR0`}gxTOqJQN&4hdV4?zF_rigfb`X6NUzbPVpefsZZ^uH-0 z#7O@8Z)Jr3|4BroZgD1HFZpXLHkK8Xu3xdXj6SFQsih*}>#X%ZF0ZOszpiY3^@F8V z<)zCELVV2Em-s$QicW&v%>RDA)6;(vP;4vU_(^{QJi~yr{|Ilh6)7gTNOiSL16;~I43#&`3syA)8XZ@oUCNmaRuP_BdF%r-em95*r^y&oh%)GVwzXK+Io?TT2 zmQ_gnTv%rG3=Ft$)7t8C`{wGhqKZZ3E6Va$m4bR2cK@cbs?EGo)woJ{ZCLcYKn!3pFQ_PAUtLx;>oMM* z8ooUmI)<+Q$D-)z$6kG8zuglpDlM;`T~W2LeErI`Wrh|4?V_sk>azR_T|t<*`@qrC zm1Xvd$MPsjoL5~~R=R>|Avz?^OR3g}8}C$Dy>?zTSYEwpV^PJ-ijBHp)m-OPwQYNf zdS-jFoez8+;@F#6REPdyfu*l+M#Sjvfk0qV%7)fLZ~ zZ{mfg^m&4y#9GnYIBU+=_#4Bh<_SvpeP|nIHZ8y$8{P-s-L&KDudSC3;hke2h6d63;=K>~iJKzMsRS`rAAXk9@ius=b zt^=GIMRY&l9>4|Y{{(O;;2F@DNwriGr$b?8JZdc)(D8aa(G!55p$57O@EopDUIAQ% ze{;VT1bk*8dWK#XOOo*K@zMSTAQKv!f`30Q9F`WO65T_ui7x`4K>yE1@q3E!3E&yL zKQU5%$plk}Hzyz%TOvd{wfTffq+yw04arn~Y zCSVeTKHz1dx5w*<=q6wq-e*oA8qPZeoQQe50jHpS?IdI8wSY76z5&O!1%PK9c;E?_ z0N0`+Eeo6gMB=Tt?*ZI}_ke7o9w>el0#I^I|N)zsp1%@#z)@jS8v)}m{xd)uAT*Y0 z(LM#R7s_1($V%A=xD*?V-H1N)KLj}U6=-^RHBku~hQT^IUaBAR^IHM+VbA*mg`cT` zFOE<1z9zq+yixGRsy8;gL2nkk>3gT+o!)ooh)uwlVO}tfR}7;^AHsXdMAKGEkrw2&y(q@TafdXG0p@$ zkKl>IGva3jw&`_iDjF(?UK)=EH=c=j9#!8JrsQnL+lZf?sAKx3aBqz;5U62%P8}mo z5x%eHiBr_sVxn3jCg*^*kv}_;eR^GGbUMTX7F)Mu^sWAlGdOIp2m1|DDe8Bl zZWqf^)OVvYg>R`{QN~fWpz@JlviQn`_}*R7rg%k&>wP|&Ivch;r&^+CE59N-oby!F z%a6v`gXhOmuqOqNB>VgldCsgRPZ!>Mj17E=FnvRSpqj7o50 zxF2Y`B+*v})aolSvxH2wGbT$BByu^QGQAu}7d~7f$RCsmV()5^T+H$R*HxTPrj{P8 zA^>W5B?NGhn6`}RVGrPC#ueCcP%!^nH4M&*cO9o_<>7QX?K)!W6fcJEBo$wqt74??=2 zAzjdnb%}B_W!Sx}*@b7Q^MPXWT#?d!bj^@Afyi{9)!}6#jbA?bQOcM0U57znzd(n# ziW+f>M%@;eNQ63=R6b=LKfuTtX}yy=Tb$%69}dlm1;UV++?xs39!N}S_D?DH3bA%? zw!X4*2+|5p!75EqbsIS88G2$qI9u$!yxIv)JG}kNe{q0SuS=4AqF|%LWl_{80HK^9 zG{;8Uy+s1``BPfW+=-DfWV=yciYpP{PipUudt4GmsSl1xRrf|2C-j)e#A+OBqRunW z)ox8^$>+}^Ph9&uVgO(xExJy3AwGG?vbcPEil>Cb5j!A zzZmP53^t+*ODaUQ-ndqM*i@Q)Y$&vw#UKdo(t#&<1t$(19#Vg8nx0f8QpnEW$AEKgUZ)I8B@Rp+EG6br0sQ)-=f52rsQoK%mex5eFklLrFWnKSOD_Vwdq1tnta zp;cuWKfG{E}=7jiqC8P?rGeL=u|AY9#x95)AD|f6quKfIc=0iM4F)Tj4M> zz;v0YkQr|gthFrd*r#Ha$0fK+9cYV7qM6yzW@QFx`f*=4oYL646KBRP7&S8<<3?fJ7K|G?X4I(gxbVW& z+a@MP$=6`iAv; zw)``nBAWyA8GBJM9(~x^u%|4Cqqzm;h8POAicnxft6)~2Pf1mV(<#^wy}X1O2GARZ zeJb@#s;6DVD8Ze`#?M>P87axP92f@C&k6&*jJ|#;*_VpCa)I)84;%8hJO#?q2gmp# z`qaY~o51uIF}a!D|DdKk$-d!qdr-GG^@H)T;|8TvUl8r=SnZ%TNqu4BB(YIce>SmH z+##xCEwhCvb%AAWQj^fx4aCgFOJKtw2$zqmFIpxF535Hk*%>qG7`Ju3PagSuP8gEW z$4WM;*4LL?1VM&K9d?Yx)?2d)_Z+Y~Q1`B^9g1R$JQj@HXr>+K*XAMb&nU zA{R+l^1H^W&sfXEv|{yh>qG@AdIICRz?56B!f4UCM5Mg#vG!m$TyGgA_im3SmmhR+ zpDEmDCJ;tbZATQjx|)l<23S*TA{AzU;ThJ6o|(`lh}G29?9jaG959we$!iET37%ni z*06(m|A4wJW85s@7AM$uv4BSD_!(a|_S`_0J!lb%yW>$$Q6paxigVisTJsi5~xc5Ddhjb}9@_rDLH`6zVDXB#1FW7gbZHReXM|nwMD> zf9kA69_u@;r8$3S_hpV2V^?cshiSG>MN5riGRMPj=3pM?6|5f zylJxdhimG_n;sT+tDQHsiz~;d&rF^nka}!#dMdkD(cms+vKTEw(@rc1i4hWnR9kyXAfRI$M&-x0;2D3D~Mw$OqMXa&AfdP~(cpH-~#09NwdY>WevuZp`XXPKJ1L zv^vpd7QAYqt-w(33|bL1s$Wdru0Cm-FC0^k+ZL(Urj8f>p{CuuDzDArk>7H{`UTBM zC~R1#G@kw(ZAZeE!2YKkGJ*Tw=`7Dt-?%wb___MKnYdZGCNN_zEwy`PL}{&}+`9#?o%hp0 z=xV*XWBL@V;;re$v1ZuB8(Zy#Q`PGjH5MiB_w9u}2FmyDvKPXP^1UyjJY>kbOCEY}o|riv_c$3iihJGR>MAZEu{)FxKAKYXBT=Z+wH{IJ7fNLOa7Wv@0=qnSc&} zOERc4#{p;Hpw3>XugTH;Dw#!+7noNfg7UVc~i<{BqNFn2)B88f_`s}-0l zv>u(gMnxNgmX1X901a-yxtNbbuIqSSdEFUYjP;b@G27jxN#srprHH`*4wOCVVlZS8 zgJlM}ZyzvUhNRQcCn%YHqcDp@n(zTL{S~a?64r7V3~v>adM{(_t218MGbBw=d_ie~ z?>dk`?Qkly*yMqT%CQ4Vvvo@;2m>rWw^Vfc3LJJD(QNK3d&B)Yz`&y7f?fxfG_u|j z5NeNO8_gb4KH^=;ocqF_)6j~mQmU;;GWWvKxfDq;_wJ9!%Hydv1W|iom;qfvU3l+- zgo?nOEqI3%cAgfko)^Fg7_?~vR@H^wBVtdrDC5YfxfYfZw>M)##$5r)_dv>wn-3uT zZW&9pb_ESQ0eb|?b*i;8)XM=(uGL{>mSa@QVhxNz9ol)d4;ZQQGEOd^VvYP?j65LOTl+^4m);1QO7)_pH z44RK;^rg`56ETstWZzF=&D}9(y>E)I9c3QZ0o`ZLu$Q@6<4VUo1S`RE(LHZn90eDG zVBTvHJF5R(m?7@Hrk*dHt4^P0P&dq3WU<0Vd82AWFvuuAfYXshM9UoK=ccP4&3Qoh zp*nr;Wbu2$>b-MqiJmxWVbOQLA9j=S)9S9dS;7|eKjz*$aX*54#u5eM(8&}k6Et1x zJ`?AWUsY}M>XN^MF09|{kThUC++E(FzCAB*d^MW-S%@VGi zdk}{}$0lH7(L?P+@>5~ejGRRnCws_9(uDWCEYy9Sa5<@(?PfJ#zpvy*I4#&Ru-*)s z;mKSjz^RgN)5Y*q?0aNPr?sz|3AbbNWy0}B@-B~DM0{X(R4YsH5<$oe)doD4@5x+- z<1P?7fp9yHyd0uHxbK4h283aBzwlAp3faPUYQ(Z{=lCV?I}ZG+5eHttBC2n1JHrT zXS}@U8eH!no~szo-n0!~6X_HSR-XhmC)=#3z$wax*lIT69kJi+&z$*M(9UIco9tXp zUS+qL-Ye$5yytHa4cxHpGZ85*q@r3SvG)Xwfo*s`h~RxGAQ`;Kf|~sA3)LXwO1QVJE_g;*(;^&Q79e7T6NBgNMPHvre1S40zQyTF~1lSxr+ zrNi)ee#sGJH-Qbx706#)SdyHO7g+8*CM(H-ye@+y_!5x+D@tc*lzx-Y`#~rp((z zgxZ;KpaJLs6mVzZ`(_XJ_?@pbSv@(q&zX&Mv~DP$f>)(j+X_kU4M2fnu@U)KOJHe= zuLN4&GxaVnkIxa}n&Yu2C_+l_R;;wBD!U*c#ruY!BS?ETbGZcV0T9G~Z?9-pK0wL~ z&O?UDrTgR_&JtT}d_wLRL$m&#b(i-nl*TG`%;R=AMJTF8+w(sMv_G+WMXKI41dqoox?0Z%nRKjpJcuy+ z>foT*Jb^Ty%iMP49IwSQpai}!o8c_G6ijCGg3ivD(NxBneYEhSQ{0{07B1~rU4 zz)EElplm|02eux-Wd8vL3p{%#G7z%oD!9~*l?RYec45T<$$2arHr|8u4f~HGLGBe0 z*Rd#ikY(sWg8C}o2;jX7;}+u_*dgmaf!j%e<|_uTY;P;=*1VJ17gCXBm?|^|rpVp} z*ul#mq9dU*6ht!l5D4?t@5A{7Tw!<2CKd>d_eR_?MCVCD{WZ8_WD`w+UiJ|NkAvh7 zuEXmEYF2q!X_KI^*WgzMl-?V@I3iQX+s0N<4s8yObjRapc589rH{5Z?A40C}S`=%7 zJ8se4ahi?YFuUXT;FhJ2LoVJESYLhyk@yX(ypMx87K=hxCVMiH+@%GOOZT%2yxcJn zY+|ar@cyBu^@|EEIY<;iIb+l4Zg~QA7LBG_@rTZ(J@E?*<=Q*1^cxly4%HjHWwAhD+&;Ij?)SCOLn-Um1RSTl33AEs`( zCiNX~+^Fk9oz+2y1w1nH^4MVy$CACUXbaV@wv)@YgYGWtq1x@hW6S#Wo-)CFes2-> zo<515@llL1@{dGD}dFqud`3 z0q2*PyX<6i$3?$RfW{{f;fD(ir3gVIVl{spxz0l;7GM9Y;5=Mx74LddoDVn8re4%P z`ej)`FY51@K*m-<$bAml{{`xs|5{e?g;7vzmL)4_ZjLq2(7^%bFzi=%rbk^1B> z;HNNNfcwZO7d+z4?owP2;MUXPJBbh_=>i%c3R?#>dQo2KU7 z?NnAm$b19b4U8N)^O>GaFyjBg#ogr+<%cuSGK|gaOJ>u?w$8@HNBK`B7#tfJ9Nfb= zfwR6x^qKX2q@JJRy-C+^oL3Ym7Q!vK^ibA#?>%4*?%0CbWbh3_hOt z-A(fkc1z<9YVIEszQik(!tTF~x$&|+SOgC$uycGr3Ko)v7$lsTj8ZUh3I<$j#Wvk^ z*TEf~$2`1KTX2yYfG_4cjRA0BzknzIE82(NSmotly7!Dd2)ppW zSZzI_ao(X@MDMq7m))~Js&I#3fBV~`<30#AQ&8!dLcx3E=x{$Qx$&9ef}*L|Kx4^O zWwalC)L=h0E1hbq|7iD?Hyxkb(<~fgX+7TW^xATxj`n~Ej%%|8IZj4=<1da1TLl=z z%o&2U?E{0p0PgG*>clNeEyB{B>-$8HoZGqw%v3I9-~>K0Mhy=%;;zhcnh|n9#JbfF zWItdh*X+=UI1e|IlRN*Y-_kL!D1~ZE1UeSLii46|xHy-NvYQ)#ccCp?=3a$&xt1;j zanRCC_Z$R(I2v43TFpoWnE3G2g$ejLtHs9mPEM>!Qvo|v0K~0F8;72! zyQwpIFm$9#a(YD=mjrR&^A(KY38+<;J-C$LpDlqN8xC3_CJT-4Jpn5YC};%hnq!5I z!lFAVmy>9**bj33%^cvB*1E9Og17elDWUnt(f9sEb8N z?ma#>tlYSk!qt@Q8G!T#A-RdrJuUGA|H@2!hVf1Xo|*Pw6W-}8)r8iQ!e`x`JYf)z zzezgiZ$gOF6n#*BC%o{$Pw}x4jwSxp_TX|brCg+0nlY5m^e|W1^Jlto2g>Tis>9ZY z30P`;2E&oqb{SITWZzVl4o;Sw2z;0cTC3QDw~s{XAAxk+H<9*!@joL?9)a{uM!Fs@ zVkFKrvu+UjPdHC2{{ks)IliW}07yA;qj3si4<`k}2Yp`c-?74TsFxd^K|c6%(q|Gl z6I@E#hdfCjCIiWSK9#29-Hvw-_F0??TTdXLf)mgXnSe71^5!u6WtKBS!)&;NyYO*K zc_Pi`D-O<)Yl?5m@KnUOOLs#zwQ}1uj}YxH-5)92r+XamORXq#E;bxVT5Mt>d|DJz zoow4od!Ic`!lqdO(-Vr%b+gZH3;%Rn{;NDdNR^oWO z(>wgx#rhM@ASZwVQ?=b~WRvJZ%R@6gx1nz`-cO&Op#0Jkv&j=L3HRtB*$5$wcq7VJs$wPSMPOH2$xf@YT+*~NcD-4@902_t#V z!;>-_PyC2)8sJZ$`B!z#FNQv`GX);opE;hw`_>rlXY$QOTX2L`T!juG#hK%gnNx5q!q%pxc+JQu?D;yU5E_x<%~}}ADJ1*uz}baWjg9g#Cgb(+ z=D~O9{`n!-Kvwv48EO|B4~VkTJh0#7Rp2}=bE&SvKrU9lkU_$BC}BI4f_Fhv9s;5W z($yUGVoNj%_G6pTcMcw?;HlG-^E~U|(9N~vc~3O=Gu&y#&JdK2D}3gZFOa zvmeO9RXVg}Fr?_J<-4I;L%M4DdZ87XaVGh;Lif`kb>3T9J46A8Ped(y(PyN}y^8Wk zm5XS?NqCa5BTn;$cnq`+pDNk>d{pE~ZUqO}NA19B{8_0UM5AvZc>9!7cfyt(dt`2p zFAj2G$nC82SWRO#`dbA=#o$!nO+>~`h>YPQSE4`n^Ws1=h;cGUB3Q-e{8wi%11n+5{tXl1=ry|^Zi+j>BWNjGPVaL&r}nv?IUv7q z<0C2r6PnM&#%y~N@BLVzUveHxgMDfCR(-4IIIa*1K!{e(zQyAiP7kGMg->nZhwRjcd>$@fHyjyCwlrI#< zbmePPVpXT)jCpE(#p21o?+LGzZwM=Tn0SkgKdg!@jyWKE+q)|!O4>L1yDAIBy#e*M z${9I0DI8vhPoYopmx=gNkzHA7fcKdspLTPlySEPYhn2Tax`0m%FAU&ozsnCegM2f` zpS7@U^Sxvm+6rG5)uGC(!h`DIBTn)20rj@E=81n}4TXUDbNkTyLZ=@eEANEO@}TMq zYTerB#O48Ycy0cI6D4ce;}6f@LkJWZX=WuidF%a#uClKvl0Xc z(s9FqZ~EMc>f`Io%D;ckYzTh@)PQEIj05R%r*|ooh|@^0gbRT@?d03r1#hXbs8&Of z+kvnbK4}~u-8jtE+Bya8>%6$@W%93M;ZL55lYODk?14PK(cv#Z=)#c`g2#&+;R7Nm z$GoHI2$bpq`2~EU;@-su#8<`gHoEdHeer%UofWMQrx`c4M1HT<+AO{UK;#we$3TcD z2kSb3&qeVe#L077CAVJ-rD$Qzhk~c9NcsbR;q-K$YJPNT(j`Ma1bzyFJ@o+An7R^I zv~Q>nJen@_tKWXqW?GGT`MW#bqS%7JgOgl)#&d^+x2une)5>R%9m=l#9Rz^DM(y{? z)NuVW9j@bk>XD68g+P1P#wn7R^o$x;mmfPj-0kdabxGaCxN8OBVrrpUS6A@RBlsIh zwY6^Z#)hg5mDb9-x|+J%tc!118hNeRR9RQiP*t$jsLydKH&4x|Yl?|2F z`o@i`*VWZ*tl3;&-H6)?`t+GF@^9%Rs=m5qV!^YjSiewcQy15-P~WL{wWl{c8QuO; z<1#_XZVLA=(BWbouGHar9X_GM@9VHlhX-}|Gaa7N;a_z4xel-CaNM@=eAX~jXu4kE z(BWDgw(9Vx4u7x1KkM+U4!_djunv=+4o{G!!(1KSuESCtKA=H`XtQ3?q(fDQULC%x z!(Zv}GaY`R!^=7pbyajOqL zxnDS>3QzS*(cw8AUesX>wtfDk>d>Y`dl;JOUcI70hb=lhq*f>?%6Yvstiyzt!ss$} zI7^4~b?DMzxelv!__z*t>hLEz{J9SQqQk2?90R7|r_ki^3)ywJOo!z<{I(9mWDz~{ zPvsW9yibRR{;B={&VMNF|FQu7HUF98YNcZQ|1E>^|7{tlwNK9xGSt2;>B@X6uCA%C zEUDPIrn-{0QgK~nMMLF6IO7$xT`w)Ed3fo=k5;a3aNxgrdfl?Rn$?x{^{7b{7Cctf zpqFW~u&`?58Vx7iti3O+s;h5cgbN;9LG)c=QDsAM&4vw_@Q$jD#gz_vIb5}|zNWhJ z&YCrqL_Zo)vvhOArp*n-%ZUESwLDibIz{HAZb1!f9WR~{)R?A;N-y^T1r^oHDjL?^ zRk5Mc*%)5i3c94VZLa>h3FHiM-5nM64GVa0A{r~A3?epHYdl~C=#_AzHjE6*F5x<+ z!m(ye9XNq|J8_W`u0pY-vSJg_dhWEmp`xx~^QOfcAE|NBhH%Xqo#CW%*;Tn=6Vr>S z&WJqHQO*B~{9JoqSXT*_oY&q*^{uHq5W8gHUVg8ZqzEsO4(U)^w$m+gV!6 zYN|FiRMstcjA#|tg$W*kn&?5UA5n8-vNCQN!Pf6^d3nRvtNt=-MMYJ^!kW6}RU6k< zSBABVQlnSgRaezeSzM#(!U__^JHg6|wUy4A#~jp1cQuq&R;*#_h}uwE!M(MjTPSU) zzKdDiu({q(+2!(W~|mkm@C zM_@XNHaFU0)aYl`Sudc}gpzoA+quc;X%XzH$O%x#{3Yk`yP4_Ok3KJOUG{>`DBx4C zJ)0fV5ksjR539EAd9iPjJE08iN_*Az-GX{bOb{;=wu{e+qB06wo{U}D1Xuz%k1~aA zfD0%~I0<+s{aBcbOOTtzFMd?35KE zMfna0feG85iT{=f2c(WHqQ$gNlqX^DNBgzOT+S5cPXSL*4gmfJ@Mt#C_h_k0hmK=3S~b63aO3m0A!ZC0eL_h;2OZxwM4H% zwub>5P(Jx6bOUhidZIM+cL5gixQ&SAP=6TE3Xy#bSOvo7)*yJqf?75~tZ4rja4z8U zwOHgUGA3&v8nvf=+z+ltd2WqwAJZ}zEj+14@43Z%bl<6cpX~GROW0rV+NReYd+nvy z+Fr|kef#UDURPJ`xn=H7a0^VLHn}aKEw#t)y*P+kI{2ZR^@LwLRA6 hZrk3rv+cgUoA%ziuV9~jU&+2@`wq3gwP%s=e*kWs>@)xX diff --git a/crates/uv-trampoline/trampolines/uv-trampoline-aarch64-gui.exe b/crates/uv-trampoline/trampolines/uv-trampoline-aarch64-gui.exe index 74080d4db835216ce54afd07aa4b8f840cb699fe..3a5a2e34852ee4b1e457df7af10427ecb9210db8 100755 GIT binary patch delta 17801 zcmb_^33wD$w*RfFPP&q?BxFwrokb++utSy>FqIBVK-Lgt5lsR_2p}N~BXOB-Kn+fm zp{)pt3jrKWw?tgJEJRW|YvEhz{(6}9VNJhg#2VZxmzF3yd|(C!UCv?VOk%R)U^j{MO-T zxIK!V;u$L(TfVxevWT(KBhX;i8reqq58SAfop>AYPhzZT>?UorN*D-KG47Iw@T0g| zIUWDqEkDSQN_la>G(2LaG`75WL+L7Q3k+LfJbteod56D`vu)z;z4g3!CY*^ib=yq+ZXw*YEsE9BVins8`h5%1yN_rwAV=K@b{N(t zm|Qex;Nnd}!8!SGdZhfaVK7%EI}Ho?{3!XV;ck9ulzf+8V%8kS)ePe7c|Qrl84q?z zJ52T3J}dAG=yU_&oUJu`_Ze7s4YTLfa5Z~3FhflN7u5NzA8X0qS|h*gmu9=PiaBuf z$L1t32c#)1-{C~oNVUkJjglS0&}!uOsgQeW^>Ft7Mt=2QllH(uD@Z-sN|W` zZOne}lA=WREdGP@W)SoCfK$Dor3j3|h7a$X$+}GmR?7j#^4(xqF1SejL5x|#3i+^_ z>1c2>c60~0O5EH5PKJS#c5sro)2nhP%thS38H8hAe6n7a{G<3c75VOzm}QCBU=n_i z?l2{XwbTKZ__*+L2Z(PoGB4C4%mW-9Nz9RqO?W{3X^d$TB(EF9(&jMFLE{J#=dI|E zk)QL=;8x0i^iP_XEwFs+6Jak0vaYjpIPpvY$9Ep&Sw1oSf6BxU_Wv)b?CKhmu0W;8^aa{7`UeA1sGQUmE@#@Zo_ou7g19lcI4OL=^THZJMu3PzvuY67`Y-Uoo6HDx1wh9 zQ6uD@sB&H$ab!*O7!F)}A*MR~lN-vOT5EjGZP^KRjg%+E?&I5T$Y*08DjDHmzFc)UI?tw7* z{df!CA;?$a(;)X_5~fB7TuU2-jb@W2y$op(Z&+npLLB$D{AxmSaT05!&erYBA^y>d zQxA2Kd4@huR+s!lGuO!i97bHI3AYGiK)@kG<)ezk{V5e3}sD&SUwZ_Gg>3%jfn+Znf#YTlMMoT60=%i zOLts_90o~^ko)vDSdBJ6cC>&+bnftj8N$jm#ztcZpC2o00%ff=R;Lliv2Pf&B!J2U z_|QqvF*1#Y(r$uO6mSjlVR z!aF&LM{6YM2nl+)D?)xZX}S@MorfKj$KMgpj|-9W?kEZBx*#w|Jb0H9a^&Cd7|a>F z+HvS&w*1w|nD~QGmsU^`4+=>J=%@>EtYp(-uLIHPJ2S(oAt1^r=I{+5lOx;j9L*h+ z8}2m8VWS@B3vbBJk4op0uE~EIbtk|4hWyK@mE2YN-sB^EWUw5VGLid8o|Y0Dm5p_* z_;rIwQeNI@&-5v%pS;R$57_IK9tq|LSwFvj+4JnDKNMc zn1WpMtNivL`J#Db_+BXMWo)7sL_@99$!vTgC!~fZEJqLgfBLU(7}eH~iJ3s<79wD> z!(4C3_oj{;_8OQ#bFyi)G28j@6?u2+Dqf0~ucapN#Y5#0qhmNlwv5g++H<_|@aP~V zm-|YdF=m#n?=j8^=Ct%-aqUc*Biq0ny%6)${V+!GPUHexv8z6~LuE0m8U-?1-;$iU zaJ3znR}ja#1K=G}IjQQ5z$T~qNmVrj1Ei{Z2o9F2f(VA7p9c&_Kf!3pR&@_O#9;s+ zd~iz&{H~bIS~{^$^GIon7{FT09GhIPD94BpglM&Hp=lZf+gr%nr>%vJCxLatl|fYw ziwSWsYF%eb;8f~i4d~>3HkQQ0=cB{mR75849y5$PBs<5fNVms1#GkCH?b(b|hNuDwm7Eyk1?483FVl_9YqLZPRta6CFdu&_y&wa`s zQHV6F-hhiXbJB|k2}d;x^~0OKNIhb9Aiw+Y8avZoAVWmnLi025FTq#l8#t48X3ZP%u+ z`LYDKUU&s>8q5L-`LR25puQ2V{UB3-GSu-Z*V4ctn9~T?3BBE>2+T{Jh#M!ml`VZL2ZJ8_B}AoceEkz0sV-{FiMS01w>u+@T7I!-&r5TSj*LyL;P(+DO! zmtj|aQWV)VEyk7;(xQrYJ9F%YzIeXNagvmnxB!*aB@Z$ud9L13k~a}2Zgt0D50NfU z0S{js=IV`Q-lfp5CXQvO{ZfqDPkni$cN|Mo+qMo%PLc(%bBS=PYdmkWwcmo&Z#dKwF(xQQHau zdl1)70i&nl_gF@b_my@lf-;lq27a@#(_H*wBzs;sT$y6xy&gzPM-uN=3UgdwNY)@8u+`cOM_#c^Q*mo%q7u9wE-<#qS1? zkcTc5;8#^?h&H$3wA-<`9c}#rGuJVeMv?#*uVxhx?X;|%%+73ZU|_q+!<8irLEAOk zXC@n5QOtxF_LA4RDnyyHgqY5QSCxDlAJZue21kNenS!(b1S~(T9}S12`2=jv0*Cga zJp=M`r7g}Og9Dk^RSTKNp^}$Gn?(5wyt|E7Rkb@{ACi|bXEWL!#hNx+AOYw93C@@n zAYGtm(@-1*DDPVZS;sEv+?QYhQczmB6+|o#VlC%}F^5V(;s{45lrS3eseda7qq(LV ztc>|Ph$te02q2`nLTHT^K$u*- zA9LiOeL8*@dX+tG{=U9Q*BCgElm0RK*a%k&-f7;?&L$-iG!aLH4|7@cX?yW@5rfqo&rUVB`e@f-}KognIWvz55|A88G5I_((Z+ zMzpQpxG%*g?rzp0_ zrB{vV32==GJE9y!EahkKfY3kVdIJ7L50H1gop;(>yqlfk!?JTnz_aH%_!Q02GZNicZOi_e`>#k-VN8%F&LGC2zpU z@Rw6sSxX7tQ#i?zHIkKaH-`h~+&!?%k<9x=06ThF81DM&>72}hR8}Sh;FLq9<9j}pocXb=?8S>#=WmQsV@D9z z*a3p<+fPhQ=ET5t6WDS0IQz8I4APauJA8*tJWjzu=SHG1glGcqwmfmdzP;C>8?CV6 z&!D`vZvEQKlz_mRc+PZ=Cez`x3DK@tO!Ne0HxiMTkQJ0nkg!P8w1P-hmdmk54|aQ1 z@UzUu;#WQmbF!^lr}kfraJE9nhzrQ)!zkoxgOwXv4XSCnH~MCs>v5{GGGteg7iSo(1ct=lCh4`~u`>W}n5r{LDf z-j~sH_4I}w)X&`2TtmP(5*=8Mwwp?1mLE4^@YhQIj&<%#6P_FXrsOL;>)igGlJACM zU&R>Iy6)ibz9yeT-1cZdkPUw4#x()qxu6+2q*`!zX39WGWoAGu`W8=jeuyxD{191G zO6MeG3|VYM8Aolah{=&)a)e7T)RBLx?OPyuX$~2;EA1;%9x*ITUuy8Aw=M?V2${}e zOaPc@BV$Da+e}T<+(y>mKK1d`He{vlQ`@GJRVJ6eHjg>Um0xLJl;Yi^mc<&{V9ht9 zedQ4Qw8E!p-pQ$tv<0LTp4>jQ>OR(p4LNRH%W-|;?yl7>Om>(nv6g%i5VC!2WKoay@?~GbP5sEQ5rxQi^xMj zsoGH%Bik~T^Iuryzh#W%2j7tSO!Kfkfvgr$!r?a9EH#e&K)yROiEETsX5MFdP_>+4 zuFqgQFepqZ;M82Btv%2o{?#gfnOPPw5o1(_pdLtwXj$=h@`ILi+bDGPfHafHy2+DJ zcKTAE5=r}23$;OX&`?dm&@!;l^dPq7PJ+iuVvd){vn0Wjz&SJlM?f@NB*w9ptih~| zf600^1+fBoNy?|m!vPm~obBujl58j!p3<2sS$aN`oE-x1AQwf5-13R3t@6pKmrRm3 z-j8oEcl-6n`&k=2IlLvgdq)4IplnN@G27C!SaimRY@M1AvTbUIkZtkY&%9Xytn5-J zZ|P{~Eqxbx3k57k;Y!^C*jY;2P*ZZIupCIV;A?ie%CrQJ@q1}gP6>#>>VqVIx$v?}7Elp{_)cInL8K)LMxlP(>#)&RTWM zczp~4g->Zr;O#LYW3p+gzpZ(qh6Y1?1?!JMmIve@bOrsyOwsi@4vsQ%Dnr~N_0Qu$ zCQ7^Y6x1=-zhH@NJfceMn?(CPAE_UW#~R@(slE*VEq&{1UE>zQr8>e58o~qr1>tM| z4}^OL@DgQxv%3Gv368`S9BL%Y^;^sZzsy8fH3~awfAho~P!{G&9I*SR2$3#*iHxtI zgRuJ+9>^^ej_DSss!JX*E!MU@vLzp87FpL}*zUuxAoGgqw0f1rA>{TU!zLt1Cf5fS ztzKS221ePJ%)A8#E#vAFjE-2elknoUAuO9kpK<3H9IyWZIa<8PNqJ5mD6N{fckeYg z;>-AXF`tqxN~~;n0MoedI)BNWDq>Hxqgj7 zy;G=GqZLN>V=_azmGzEr>=*2LkpU=1emTqO0hc^~C(X9!wPDvvJJ+Zo33n-Hgev6} z*QhANou2JR%L=GvJiH{0bz`hnZO6DIqZ8L3-gZCWtXbi7pRy4}p{_MJS$8{?c0yh8 z{%|_gvz!4~xF6y?5Wy6RV=&GXz`7G)4@&TZhd94tlKfFJg9*H%8}jMC0v4vdiQD3_Q*_k|`iRmXLRlT)3#gHVd5p6xrZI3)6Uli; zyXGR&BW_)NMDiAZqvVFJ!kT%&DQ^WGV+?joRyx|%3?07)r2*0mdZ#`fT(WK&;rIl7 z@gkd?8_K$q;5tkp2>yZN6iz2MiM1rGUwb?&-X@i;g;oV{TqkuNKxaxkbL`^-I$_B* zW$+|Aio)!93n4Q;p;kI6bsX+IP37V070%h{Qx#`Jyn`ER&zphwml4oB3|UMT0$^T^ zj%%}6qZe~L5RHBl=3~7MTXme%17yDewchG^P8!&TowrpRoPR)@2gl<0AM2is#rdo7y=y(a~zK=SBH;QJLc45P;+MuJ*#GKnin8-dRieL{x@CT|Yz3HI|3E z_L7hvT;TK(6AKp5Ok9~xe*TOwM5%F+5xe(Ey>@Z9E@@* zpE9o*Y;)tC15tfHg9|{oBY{mO%4u98v6Te#gcf;FHK@?YYJ-}dKKnCdhvcUN+H(@w zw;8xoS<^2+A%?}r1V?`s2DyGjjd>F|^C4&8JDUc}cP|*l4U!*Pu+!$F#$z)&J1|p} z>hjuPWMEhtl~k_EGfH$`rigVDJEnZB*$XP~SA1*rl6sJz2!nokY|&1ipSXfEjX=8> zej<=m;ZuYM*hMxI&-_Sd{M}Ngi3{tDm{4om>jjyY!2+L?&>S-*8PR4k2&B+g2lxpo zk}7d`{ezz<`jetn;DD5$P^~I-1E1k&MVjfWj++l7vb>hhT#qm{@8@dM)ok$W22+z{^2OD z868(3-8(Uc&WJKAHKR+FyzPW{9Png-8+c|HSgB%LuS*D6-jAPqyU^g?Zotp4LG)^e z?fF-$CD(`q8S(CJ1FMCznan}y6F#5l@abow9ha=oHPX|$K;E}_g%f7d+}er7;F8NB zEWVKE1QD`E@@@w%+bv}IlWrq>@-{NdKS5S70NFc)Y`x^&G?1|K{~Xty|0gb+YwiGK zFJpjW8%`+?47bTeyCGvwhF*Nv>ir%&q6-Hy`V$GVf#SpJL9wYr%sjq1^Fa4akwliGu3 zI#t7AAK@s1XMCG(XZ}SwrXIteN5F#_9ad!V%Mnp`tXiQ~XR|o56IaaM))6EhQ7%_E zMcX(M=zlQg7eY6O*zh3T$&O($^W#`dU1Y7vQRHvWI}P7Gf|4FcDqSyh2q7?+qALMy z+Aa|H|4!RLM~xQLsEfkt)GcPXeO;u+1Y}y5k;H?@R%pOwz+zHEc(S_@T$G(y1PZHj z8}5pK@@!1q8^6$XM~vP5i@O@aSTh?x2xJZllMv7)G%*K7NlG2(%P})Y*ti8O-~0D_ zoi@SHeG1M?vy!o{V^9Lqb)j3Ff4l7<+U)b#(IWgrd~nl>R4IU=g0+>Eotn$?&Cjq! z1s$wx2k=}<{L|h7F6I)4_N;EMjNy<_v2H24spQ|!<5LS9SQBFlP}`I&CLm~<1FzSJ zLMx!US$N1cA=VW}n^!g3=vV+Jrsvt3AKCg13M%PsV7YKgf z>}TKjF7r|k^OjJ=A_ThPU|u`XzV4@j%uck=jbowvIBvpk;NyQpd(AfmnLqY0d1!8g z4c#>XF_Zi;Wljhi<`IHibP>OPFg*`)%?W42=0pdXQvg$<1I=04)5}l|#_ z^JXn$-BPEL|0lUIH;H%MC4ZZ1wS5G^?*Oq>kf$8vOTepuq=C4~Q+-S%e2j9x7V!G~ z%r&e?H$e0~nU;@)srg7MxO(;eMa}{tvhy0uME;~bxcmuoQEt*J-*z>c5=Gg-_E?wr zA6D3!a5Vt+4)8Z_f0p*4)$$Mirq!Y3*Qc^uy*dGE!f81%U(k@JgkeMN)uu2TKElE0XzAe0u3 zC2y@-=|{;tU~$Bl&~I(5^ciX__^6HMorp&o@a>i(S22fDL@y#MTb97OHTJ~1dQcbb z{!!Vp6D3T~P-|l{w=lgU)Y{m=Jn3X4sg|nl;{DN%Y=mqNVHYNDczJImE@afC2bDX_ zU(1J?=`)o0x0QUdxHSmhq$d@8q{cufv=*FXu-w@kD$Bu8=fdsEvHNZ)k(7rk$}lIn zXAy&xDO@8ZDHmWl6VSWqGl|d{mg|k0T(OM|RJ8ypXOT|sA7dO;h zo;O3B;WjAfW$f;;DLG#7wz>A~M8{ni{NYL%E7+a$>oy;U15+b0pQAht`}~H$YMW4R zLJ7u9yP^-TsNRHx1dytSkuKQjT7>FpG#_c$^M%Ol=b`Nc?o+k9SCiSc4GqU|^>_hl z1Pjufuwt_6Q4?Q59I{>uYPve<;~dHrBh4N)6gN7y`BQN$%+-ND@A`oOPmF6Q>7ZQ}Nr>sOkKz1)|J!p-PhNTL9mW?CD=KaDiyo6^b#S6=e2j0{MRc z`50^jg4N8Xj&meH#hNh2vnIQ0Ex6<c0rdTsIqh`~qEnC+^PgScH4XgH&9z{o)l>yb3RqK3FbcE{zm{}4&su;zUHmzL%pI4#G9tF^8Ju25=iAD@3nb4akKw--n#~Z ztf{x^=*+Oc~J3aZB-H znoX<;HgxX*wPdj}@m?3kj&;P|4(;}%PZh9yGd`?Bae@{xE^xky1tRf@8osKADr#X~ zHRh8keg=!WlOi1mm)^w{+E-K;;I=-qcNz7g`scQxlT<5$V^zsCcor@waU32Q(KE^_ zl&&A4FdyR@5qn<7B#{hLoc75M2^X<;9**Z9^dDtJ621ikj_?VUbCIqR97hK3Q%K!y z38OP_=N?;b=N^5RAXG~RvNhZltFl!Sx^+NCgn%QvSkoAX7@gxyzy^q*bS#h^2q{O6 ziN>hu#2GT>S~{U9ajM!Yot&*vd_Xg?xc8_<5)I~ksD@~ zq9~065Yiq!)>P=-fZwho%MVg-q7U4rHP_Mp!ZFYQwK9$wI1LWcL+ww5) zpU6YS0C_lx{-=F7Ud*^HQ?))Epl0enL9%iHl6TSnnB1`}H71TSi&&{~t%0TYhm!b_ z4-9uL?~=zZkBAlsFXHmI^1Zd>#Jq*q6e~u9)ZjA2$Dsu zOcxoB?0eu1j&C%`vkFG=Wribb3TARcvMngEQ7Cj*F^%v<+F67r&M@1)tpGEkNv=w!MLi`m0eX-w;lTDLbR)_MZFUmixo)z;b zZfsyy>4RFb^>pupkF=UC*93W1@m2nb969Hq?6_*wsqtqdxQnCs0%ic4coCIbV$3h{ z{)a*Xb71^wa?{W9A0CR}w##2UloS3ET{x)09EUqI&cVg8Vawz(YhnYsku~F@*|APu zv}U~RhSiId3yV|o3)?}(6WmcGqEx#%oTc0R*}E(PiN`92I)?Vu4t>7()J08nDJ*B< zNP0$Doiv87V!UH1~$`7=ewLX_E?VBg0mNgvtd-VsY5Jq`euRn)vp7@Pr!iOUb(Hr zyzCM8jAuc64JFpi!6oH>+oN7_-7sBFEiA=b!pJXZt4voPih?*IxN-U z4jt~(;afU9s>46%@Gm<2UWYw899(NJ9YSy4iD+@ zeI0(R!?QYMfy0;(0^MP3uwdj^fQ5~Si40@7T49s%Lp-4VghIwboh{JQ53 z-(OiGAYo(i`}6bo*%`}QJbm$B&D?7*|NQ%4IoFnaUwnnOqfIhK>Ho&R%+$Z3Y$C_2 z|C@%kUAyt}g0F?KT(mW#Eq*{-9@@HgI}&1S!F#lkqjh+f4s&(5LWdi5__z*t=!?>rm36){DP$)!$d@uv~|B9q!cO zYXs?X?H#?rhv20C-mJrK|4aX6{oNmjNdIDVsJ%hbbe8{jj+EAC|I26kKRHr$efGb5 zrvH;8)z)YKt7i(1{2&*;g#5ytt^ccxK6l z;w8YNz0E0IxnSkG;#HN|gj#D`SYEoSxS~RBnX#p$Qh#SVxtS#!SF1Q#t@=E(q`ab% z5YE^_DA}yy%AC@NAI5@nOEx}OOc>OrjTNOEiszNCF1|HLZCS9na?|F@oP~PpjE%o4 zDKFjlaPh{<`-;j-idJfb0s~QzgXum>SP?%9Gp_=w`omy%`Gn4q;F|)Wl?$M=1sFVK2)kRV{zqb{Xkfx05ti<4{suRRf1?{ z_6GGY${9V+EH4JjN(Fk(E7p1j8|H1^P+4NFsw~bgT~e~TIA?7UsAt0$Z7wdaqLs=Y zE~+dk-FWM0(JBAssR+21#Wg@T^pbKMdTDu;x-Q!PDf92~lW#sXJYv2d2GB7t zEG^ksSzJD23vEvx|8xXo3|;@1MbR^Wz3RvTyJu`kQAy>@((=V68`o?o)}#<FeG91U7;ccA zJLJ_n^*8?9lj^D*Cq_5|=MKwJrzxGUez$t8&&f)h*zIi5;=L<8B z@XzwRtrDI_L@@I>U@qVez+ilqID;i|`GE7->)dI;dbHOMV@O&Oxu{Uaj^jNa@FQvm z{2Xvo7@}Q3F`Ti#V!j*jVZgbdYZ2gSz%*9J3ZqrXO^<=I?8SuFV?fv|jGd2VY!F~G zZuH^-*W&*Su@wuRH!=1Mdz~+g$Nwz??Oy{DVNr?rbAPU0SeV4vY<7Tu5pXK{pS_db z6S>a;=i+_KD8`lo=E62V$L{2>17@>2ergJIV=X!=Qy4n|$|mC!h@jPg33z`6u!zNT z*8wYO96oxo1I9ySb^*RUPDRA71HO&->EjuTqa6ZHz`R|6$I-rFqPFuoz|(l&GzlC7 zJZ-}RO}GHqg@)vGaN<=^3`p&@fP?UE$Y87wa^4S^jhz){GFC!61YE|F_|XuJk=!U@ z;a!jzjNdU8oC17SLPQDp(G1#15_iW;L^F6_jXbgt&}NteYVdIS9_$Rz4frfz7ho;m zfK7E14!Aih-%zt(*L=^CF362f$BORUx z+{#|JaZA@RmO?uP90_HmP5GfqxbU+fq!+>sZSlo;w0lr+o=2s!{>{pOlGz_Z3`M!K4#BsxLBxtwS zk6R)8y)tf$`+)d{^hWL*Yv0)P275F2&ALPNhYlY)eyHit=|jzj&L46g@*L_q)PD%o lSKR8|`%2c(`u$D&?FV)o*mdB=19b=3k>9^EgL8}@^q)xUS|tDg delta 17034 zcmeHvc~n$aw)eUB7H|P4D8@pq0vw=lLItU);TGk98CrrQW->sK2oYtpBN^T;ha@W9 zd6t*Nw3+~#PFt1sb<$|JydHu{cb;kUy2T`RlJ3_j(3nC@B0-cZYJIz0zd?zO(Z zzP~=#slBtM3+3fD_@(>E%Nt)><) zW6lu3HyINJs?h&kWb8OTE$BDim28|aw2QGcRCKx+%i)5(7;AkIb;><*lH%mVY|CS4 z8HZm5eui7U=*z-b$&9+yr46M(IT01kKqaeEe$P!!{}DBMby;7++Av z@Kd>6N;W@L$>O7xYCbOgr4a-1iLug*y7H>(vcMu37Q=Y__O<^9e;>y$9ixOA%#UO` zRL13=b}+WHTQFOD1f!>Hkwd)^CaD8q_MsAHK0UDF_wE~!OwC810fF;e5|~HS$1S=! zF2=Jp-=Utt_#2TJyW*XLPG(ldhD_$xC|M!P`M<;~&xPF1e;BX47m|{DC*zt%an8c8 z1mT2F5Ih6GX;tM9y@J_O%tiLyv8icKi-GkwF=t^D*R-dK8Jc7+qVGo`tgB(u4rOX+ zmi#_rhtr=i?0q~`QqPZNYHvJ#g6Q!I%yp)Wxw`S)Yh>yh@vPY=NF83R$#*?$UIWkE z-Z1+wFZliO17poYH^P`o>uLi&tnp1R)?iM~vx%cwb2qI==S8PbeEJ6Zb4*fGm@}d< z6~s-#uS1z0Hg&WU!@WS$E%2UVr7dh8m!fgMf$(DP zKb4N~naLu^HHwl-Vl#8bzV|s+%b06YB972g47 z^M(oT`$NFf7)ibNe#yO}!0rzJ;>i!=`=wp~nZjJQ(c+0uL)qaxK~mo#=A}Ahw|z_T zkT_b>l+qi@=7nc0P8Z*mY$TL!A;yz*hPeoNZ-ybAA8U8#FxKo1gFy9meyrr4gF4YD zsa_D$4aMk&LR3VHO)Ob*ld{{+u&xIk%rzt=dDykE>%e7%;QaEu88*EjhVnHpBa-lzjE1_5%2f!Ce8DL|6&Mxi_2{J zY>&X2m7Ex>a&BsL`-QPiA!V{AnO7+AE@_(zh{Nlf2(l4MAJz&xq zEIz;_QX#>EU9|c8j0)}_#As|vY9#uQGa`ps0!K6x<%UqEw(wBsuoljw{3t#_zL7*; zna6I!c?QuNhJG^POoFSO$0*JjP0oOJt208ZX9X}^Mn4P8Pk44z57O3 zHkGGBTl(SgUqYXH*ya{+y_t_|qFvy!raf_<8%dI?o1b!Ge0bDlA;F`fom{L0YGaib zCr;uY2vL4Bv5>C}QO26HID@j-ydZ81*VO~W#Kuct!(|XI+8oMm^91fu<%l^gWe)40 z#_o5SOAK+ql#Cwo8eYccYP=73D1LKX$T5@atV1zbl17I)7rr-p+a9g)r%IkBBXL7u ztM(eD4)Z8OZRaRf#Dt-F-D8#KEXAB#IcG7;(9w4=svAt1brn{NzWF@M?inkoJ#fXv zEUs@`2rKe}4C*tL`pf~o5LVl1U`5?c4tE&LsU@1(lELs~%S6{4s1w8*)7@m(yzG21 zmPAQQ@NeN%GTtlxxmd&kam%PM(OwoUmCVvU#g^QC^N!L3FKe-FlQktH~E=7 z{JzGwQZsd7Y)PW4r;KE6w)%3hYhpM2aE$WK)Xjz;gB&G4?G@fJO8GKva%?l~<1)lb z_LdEmA!q-IlALZ#EL1WHJzxmre@Y+}`2U@GCF#l=(^9wv%AcoAiH?JN^CAij z`iP3rO6c^S*foRx-J%e0)%Gzi(aPDSsF#%M)8n~kl-L>bxcSQ6Gg2oIV=gV1+#=$& z7GCVz1m8~k>t*O_y|R7ARIK8y8IEuhY~qd0k}X5Ij!|P#a=#$idc)W}_b$l>Gn(gq z3FYCi?Aw&ZGZ%5a%8r>PnZf#q&HiXsYe!^w5MvP9XU~JuUrB*u;B!x#|IXKzB0|J& zq-LR>BzWWzavE_6Qk{hYB;JaC634Qre@O_-0yChsah_v(I|XFahY4&ht(W%N$AT4+pa{IH_^1$mV8*NR1f;!==W)Fu>7L;~NB{&`vNG z?F19iE{Eb{0y+TRk<7Z%?QjNm*3}0UwoaBdis7v5Pa$mXLBIb95pw{F?2FG+!y*66 zAcEp-2~xWRqEg@=@|Z>Av=8AhDcB{1y^lfZElF%%pgl=zr?wK*%Oxh3r8T^1y4gTS zhcYxe6WP3$e+d1)P*&73hV_$591oQm&AqIa!=%R}q{a$?WgX{H_d8Zw0w-;*wJ362ba=;dFzz+vr=*fSj!bKyoHbLyMnQ= z%zkmturNXPsKNx#bs&M-;c#ZQitF^9P;)?u)*c}qVSw4=6mm}w!DF{FmPLIL&}gdJ z2?pjma{BC8(#U$VkE=a~T{LZ2{*ZPhQ|60%PD3lM3JKO+!PEywS0oFvsqa7}RvyV} z{SdVqhUwEK)Q#_6NGKQF*@SOMVdrVy;(8IBfI-Ks!>YQmeFW{P7G<0~HP=E?qV^_C zNVrQO`CdqwaMOW=zRhDO$u%edm&V>~?doBUNwAmtdRE!}p%HM3G`L=7ydD{YI#`G{{-Tb9y3+$9Ot5yVr zgysYskH{lrreHraN%?U8gM5@jnYmyx|Kl6VeG9D7hDg>-^6z;!;48)J%B}^eoT~h4 z!F}cf2=NJx0TiNBjK7%E6t3q?giDNdC>aZDef%^?6Xde3( zQG#TzlJCGr5FxQt8}RULu9O0tfq^g&2)E%JOyL@bLv#ugn--!@>-Y29fY|6s5t%E8 z`XQpHxSTyJxub%UAJ+R|@|n~p;#MCJD372Jh(=I2354N{hTV%c8#-c$o{zxgAj47^ z-$VqOg2ZU6)Nw$)D1~Lz7dQA6=f1hPzh`1$yeFj!8;>9}PXrKb=8ZV;#cDB4Xb@rk zY^*L;TV3!F>+cV6>pKVwW(N=+B!tX!x&OQPzC~H%)<415Cjg=Hqq&Lly9r%IP_Km$ zxedSHRXbGMREO&SFqZNhPz|%^Dt-y64waXjg-PUSf6dflL%_KXIz-oa)YIt$hpZSo zGoA33g}90lUk-eDuUqDm`Uar`PtM-E=NcUFW&EyUJo(gC_)ZxgSbZ$m9A~wl0%s{J zBCITiZ$yAupXbffLU$^Y^CTB@{3MmyL+lo>tMI9DL45i<-04A#+M1zU}E6a@(&)Ev(Gk6|yqvXrbN z-qQ;=00*6&%O=)#gF7Q96AZ0|H{kcYk#?2b1h%On-xJGh`Eld2ePwwaq8#VT?jCDb zUk37jMd?p9O213!10a-;64Bjg_dcKRem@*h?8;F0U@@#bPu zRKkYl`5;R(9cPlT;_*;Mlcx?2ip=TVv35M=Jm+i7vFMO#S}n_FRI{aYwtjAH$S zBNg8jsjSPsw&G~6MXKEf1r6u8K5FC9CZ}7NYdBYef`s?+{ghgU=~Yc~mBu&=r$C{C zN=ah!!L-Q4@}R6?1;aQsYo%uc@=)4-sHf&2oB(qvye>8=bzvXy5nIG z8H@UBaK}h0#`p%{BOGBaI}=Y_hu8B}uX2;p#zJAQ!LJO;1Gju}P^OT#m8_ry+N_Rr z$5AY+#bNu7J5K)CU$jk&Wn?( zE`vg8@;#aA>c-8$9^hffda)s^i$jXyC6xadg7oTv#kS#rd)?+lrdh>#;O_k|Nmiw( zV+-t2?=0{>Kkm@7+R;Pa=L-(`9K6kk4(A<9Y`u3bv5B>J4-G6`VjHd(-NoTRVBEf- z+qh$a?;#vF`gq&_uJ;I5elQ|UD`wPYzTa`aJ!OLlun_5xsrB`5O-}E(-Y~-<7H&V}jSM;DEei!Jc-LDrwroj2d2*HY^Wv1& zABb{W9xJ+5{h(VG4E^tdn_jG$lyd;4ZoVe;AGF`9>qec$&JJ_FmEQ0d;R-JXobQJYFE1k2 zn}ML@_P`m+0vOPl@$j}up1ZX5!vMg8Q6mhXb;w_2o{WPV;J`PMI%mj#_|ky#!{r$= zdJd2WgXg+LQOE(&wwF>;VIi95dKur>5F}DyK_Px{$tmC>onwbCf>VG__;ym1OT-Sa zMMi?0amt?70smp&OiAUq<~<6r1ZC0SJT(lQUt#Xz;~|~jdUXOcK7j}?T&Um7sYb+V z-UwE79y+n?`sX?4;bJRk*ApTchjP8hH(X_SG_uA3)J<4-OEQ=;u?nsQZrsF$3F~XW_UsUTvqT@W) zR-CM{zvzw1$B$w>!i4a(rG9@AMfY+v>lZJ;PhmU<_mNz}x%g?$!Y{E+^kVFBgeb8W z(dhpTnEW=U=03pso#*{U5lYlOsr-)VO7=Z@@H3ZHiVVd+;fmf4ieb|Fwo#4go7}xf!Gd2HSs85^(fy8m!5tTcSGac_Dz>#9UK9hRR93+BK)a>Ru43rs^BwHddoKDc74 z)7B3U_D4AK|3Ulk8>`$DOZW9j>IF;%TebH3qus+Z1^1n};O;$Rux;n0U;c1(#0kH` zRJnI1Q}2slhX-KDjn6r9ax<`hhO?qdqjdD~FsWm1605EHtK=@(a%@5GUao_L_2fXF z+nQ-O+6y8mtWD#@2od3pcZ(rl6yOi>W*Ek{AN;uh+-dQw3s)_*h)Q>^?Fw;;nJs(3 zOZj3l4&Wn0)WAR^F3ilQ2_eNpwAl;**}s#I+iTZ|I1ewAp1J6#H??zNZak~a=U9gi zD^?w%ZCNHeN?vXd&V_B-wBRb7%Qa+EaL&>!_Z;|-FdAHxTTI9V#L(HP8xzpcbmS^} zV5D_4DcDGtPkC6CrU4Q(0K~0D8%3U{dstW8W&e?G!Qtj%TLPqg@7J(}EePn~CYRH9 z*fOZFc*p`Vnc4WhcVNUm8I53FQ#jXY%e|XrQsyii+d(cjzAxojc=1uJUqIb?^jlV0 znUnhrUrQT)cvo>w?h&Cj8Q)@eagKQtU%P*czld(bN&yovNp1+2IU3*EhFKve0JWLmZ_AUtpoXKE&pK2(ktX{cxrf-umIA!>~`kN?Q)bl(5@B zqOY{+B=YbcKRktboS{DrMi{L73U!nU^I;Q68OQo7(7zQwJVcUX!lSTq zIPy1i*>Lc<3~U`h3`DWAjC&4jhDF5MONf^*BbdMT70%+z1$VFcR*=w!K8ymoPco3m zcNTtwV2)f^Kl*xcaA-x8N(7O(0wFYrO+{QqhwMZ=86fNAFt8E>cH{1|d&?3xb&N(^ z8njmA0_QEtp%eglWao3IzJ~Lr?GUctx}RrFRv^z{o!@>1eR=`0)7Q|c3%CcG1+l!1 zCk~y+oBskQ%ddsep09+lo+;;^Jk8mpSu9ejH@J^zHXLB`U zxPPvTxJr&c#fghhQYTU!vOY{eQlldnPQ=zLkSgW+T1YxLdQl$mQ6i`sN2a$%BK>3p z(yiY`+IQifBi%6q>D`31UQ+js#QAX6EkeJC^R)agkZu@(GzUm2Zxi4Y?pd+Y;5naG z`vq2b4(cHG=c#n=r<@PH!cvzr?RC#K5R;6IKOIV6z_)~NiuFmHGFcBULe-sU2u?tW z1o5jtCRQXlBQi{bJ2(cNEQw}Wbk9MFa!v7Ll3k^tczXeYwQ}op7Z-w^E?Aakxa{yt zMwBTPOX4Ek;-j5~4F+U7$>eEb%5sSJIMlNgQ9wS}0t*FnqHM?gAkK?PacyR;!FN^` zR*;(45y*_(>MMrC%*N%ohEJ>v=+CkdW8vqTKc1uAg+S+!3+|S2uLd)vNpkg$NQqik z;=3r4Wu}kJ#{K=zGI_7&jPfm zBdlT=I)DskqUUc0VlO3-5rF?Iq zfcYhQkQda=FVV9Veg50;m*a8tAjLV!I%m0(p*TCx$@jdyu>#J}w?c^ftE=G2(-Y^pD&WvfwaoQ4Z|vJV=r584 zOpa>_MDP8SOT(Vt>_j^I!BpI*LrX6EWnHyA|68>@eb5TcIAcAVq5FxDI_<5b9XyA# zC!&@%^cksgub@0q<${{9V1y?4X$-Ut@0F}xIw{g5v%mrJQQNU1Z&?DK2(h;iynRNQ zJ8skVJtDOm9V!Ke)J{4d$FasV^tW&bioq%0uMrqOkH8o{@*(u6er}v+E@PZXk;nsq z7X)Pd#D#EhQl{5ZWQ)EGM zLvqx1g6$4%6Ah-G%?6~4;Ma&4xTk3GCt#!QLnZi3DC{n>C+d$xD(-gK#k2SpH#ve^ zl@;3*TpPSg+DsR?eKK#+Or$wEHaxd$wJnztz07rUfXqxAVN%B3UCH( z&jvVg0vBx@{N(@$yGiH?>BR1xBNYGf# zO|8y!9mB;-F<7RRU!JK2eh&r9mRT+d=T)uklWE|fv#==GRx-^+kCL>NZ_K$Rp4V=Q zM^;ar|KIYdyD6VoPZq?pKZsB&nmqz_N#oq$1=EVm;IPl|Mfmm)HjE*yK*0-cQdr@V;(Lz*+27u0I^l zy|2Ws%HVb=_Ej0Yd|p|<>h|={&IcZRX*}7N?rKB4&<)|zt*=&BW7T;0cje<%sY~w+ zqCc1Hf_WWoi<5G4w=wPNou&$0YsGmA(NBP-Qu%g#H;1yW!^l=@-1R0axsSxgE!Jly zQIkQ@F;6U49)Dyd_l5G#BPnV9y|>=IX`~cQca_4q?E-mr>_DG`OZ1*?SMmMhyL!p= zzx#go1B|x-sp2f2z^C1&v=k@WE})-;#M~FSDXQe zX^TTTZeHpDA+B_+>mZ&G<8e;Mu^_Q+oQq}Bv5l!cT3BNA zoI~c-|1MrYq$=N5#VH%tT4GByK9EV9t)HGFo-o&!%AvIh+>e#_*3O9gUx) zQ=F?fSNNFTyhZgZ5mgy+m%`@ZmGMbZk_WNEgb(vnTdG63v&zH`D34WHqrSnk^c0f< z7SdKhiCXuKa+1IF%k@Y3=_Rw71f$UYR0x29i`u_^8G-r&9ad03U^XZYi&;tFF7vvTWw^ z;AeGBd0lBkW%YW?W2Kc<<*O|X)t0jAbv0Gx4ds^l#`R?tb=B*uH`G@(;_8Zh_FMq@ zojQq%r=ft)d0yc+Ea6&}WgAv1zuQpMp7g`Hq3s1v7jSagmO%d;9WK-1Lpofm!>4rk zf(~1Ccu0r8(cwuQ{#}P(>hPKlqqYX-vjm{bX6O}m9j?(~iw=+K@Gm<2n-0(F@M|63 z&|&PefeBJ|n5o0tby%pw2Q?@&wn4AhqC-W8ZXNz!hwtm~a~)pL;T0Y7I_FtLG!vpk z>;P=E5XP}^+|Q3kVJ?11@n}CNBcs0{!`%pD2k~<%vGN;SjdDyLm=ydoIK&@p27U_^ z{6J{oibX5_W}22-{8Rh0$|ugW`+rM=&+71!4h`g+tvbBi}tHt1zG zgY9S2Ff*+|u#}NAiaAQL}tQL(PT;M*(AhrCOTHflk5sSP!R&&yDBLaLSbDCdz%(2NaZ66_hqq{GfDQ zd0t~+Z7bO&t!+cq%_fjDOm%ma);BDsosY4xJjx(qLzTt@LV#Z5Zq){mVcGdqM^xBX zudV|pa7D~7l>=2M=9ib&Ft(ODt!OB%YuHe;Z2e=^cD621vs!03Q>a{2zOIJo#Z+f_ z8fmYhzsLPj`(9F44wjtPz6;9(El&$~Z>Vah%xi2YFRETyxw_m@QCi223FGhCP+r$a zGu5rrcI}&@V^a*uBhSYrl@1J7H?$iVX6xdJOVXitEhfN&8^9bsbvIPpP=%JhMTMY%CNGuvSCSe-HOWfYpTiv z+Qkw=R?2mi4dsq%O&3@g z%&SIU3tv!PzV>D#o5UAX*Wc>QF7P@L%hp4~mXZFmiH+De>cOd6MrZ{gHClg^$wb7mB?j5~GgBQEeQ z;qM0m{bng2Z#VDS(XcQY9XP#*am05T5LwXq)O+X7rD62M5r~eW&55>9W%~|g?u#gG zL5V-T_1qNnG;>k{GFYs0(H-aTZyHT~0DWki?Mz$TWdJ_K*_0MKCyXU@mMQu?2<&Fd1&0gacz`eYQ z_Lyi)0k{b81Hfj$PXLRIjQIdB0Dg*be~4kM955}Gv3meF0yc7PLqR+}AZ=y7L=AEu zn3${~j7ef_2ng~K#%yP=@h1Td`|!u=c!iJvSYl>u69_wLVQdo0cR>b3*tQh>JtTKf z=uBm78Qaf`li&x@er*bsQ@BM_K`hEXzyiRdX^j1tP2sE38MCui{^#k~=}ORW78O;1 zf5#(FTH&M&#*#rP;%>GP^@zhY&U^y673Hnip-P#BI}6y1^25`iNPr7wfm49TGqnj= z!fYbM!Z`sa0N*~qOq4$W`~dx8Cj zW&uQquhl2m7@LOjHb6U8d=an`aP@7BJ-|}=-vUnNrV8HqAP)WJ-VW{o;!jtwfK*N% z=Ku##k+uYr0!n~S0~P>o1-t-Atem@)5L>v{0DppVL4JT!PC%0JNgZ02L6>M;IVJ%d zw+1T(oTS4Xz)cX{QNVQ8Dzi)Yn2Z%BR0LRl7a$F&0`vi%1$+gvbyh-0P;Rb*ZoCSm zUB_4=##sPufCV*RC+fEXz6KG#23QHgM%6;`F>ZAo0%5?{06PI6hCBc06%h*dL*&%= zwnr(~4X#;{?PFfP9KsbSA+OF%KDz(p{!jLM_m4Y}^Lowe8()9<_14$ZezxsrCw~T% zJ3*pS`fBdfi>*VgtW9hi*Ot&`X-jLH)t1vHwdJ=Z?4Py&;{Kui?0|S++<}_*U%a}Q F`#<)1*hK&U diff --git a/crates/uv-trampoline/trampolines/uv-trampoline-i686-console.exe b/crates/uv-trampoline/trampolines/uv-trampoline-i686-console.exe index 3fd1e0aff3e97aad0254437f156ded1d6bad6324..bdc225e4d20f7f55c993b32e7d3114f8b977d563 100755 GIT binary patch delta 16970 zcmb_^3v?7kx_|ei6DDM!Cy+pd2oq=^C_)C3kdR53KnM{73?$@b3;~k}VVcGOj!1D&OP`1 z|D03luBxx9zWUzvRS)h~o%>1MlkOpJTW!m@@V|51C_aWC$Z=Iq{c5RBUHeqesK^UC zj@zsx@k4KKUdwR_fNc8($L;MKf@h+e<4!!4G+bH9>(k%34=qNNDii_bmVYLWtDe|c zv&6B4;{tK$Scx(OWvTKyKaO9coZ`nRV|0TQQ8$EdR>tba@oSZNx?uwe2krz4P3YOS zO6TQ=6iwy0qS6xYBYO5BJD!#4?8h#t6-uQrS+~ASc~ThbdZmox4W-Hj{fYBd?2xE@xAwTcrB8*SjuP#I{W(yCA~Sw&bk7F z#5a1pB?1}@Y(W=0%gV3&DkUS!^fgNYJ5y>?#{k8$a_>FDl?dMX7MmyR=CL-L82CeJ z2|L9;XgTRfe{Ri_R!$B^H^pFslcsPS>kK6M3_0p(ytlMD2K(j*+5V*V@gML#EHC3a z`#7M(`dvnLxoUBTH`d%{wb^WHcdznPWa4c3g1+UjV~{sbGKg;!0vSQ_ z^zF*RJw(6pXEXOV=C&Mm>V3r$CJz|JH{SO)2&n0qy^B>`XZ?NkAk10zHS-tQ)bk}8 zDP{>*W^ueDI-3aLcv_5MvjsLaz=(%fE)Iw%u;c7wUtxq&6_vs_DZh-mSGSi}E=0{K zT!}&K6#rvDU?%{C{ev|$koP{UXCGunQfuEHKw77_RM#zbXa?D8SM z2c)-3?-PbcPylg60`272^Jn!0ed{8;hKc^(eCGlm-oVYx`O8I4+i?7%w#&_}q z?A^hjxTA~z0t*%>Uk`EetxEaOS^Qw-*`W_djka>{){Rz%B){~AO#|77sO#DTQSPH{fn2n5d|zusd%)m6dMiE1UXhAQ-GJE5DZrZ9d74&tUBkvDBY} z!GGk!@nC#4STB#|SiVrzVb_&GI|u(j#P2vphDy)AWk)pnEyi}IVJG5_f#$Xdi|?4E zoEw%hsI+90PV27%O(IhNq#vXFYS^b0gJ{`> z7A%lWjiTo%{B3CyH@*c_NcKROgHeKfAVE`LvM}O~SppzDzXyax5as(Y!doa!*c8o< zxW8(hFF8-PyT=vjpx6$BQk$!5o>Dl3r}NjyKIf^}sw~J9_mew3L;*f58c2%O1P~Q=|V~ zyg=(ea$8+tmiqHS$6e$Dp+XbL_-A&AGdgT)I;{l}G+-siY*A_q9C4~!rjJM)E~omQ?hN(XTNXV6F*Pj-=Aayf_( zgB>j=MbBN=FzW*0ATM;N(O5?Ai4r%rn+|pKi8};+4PeY|?i>y-6W$jdr%G9Enxu>S zUU}KHSoh9VB|7O2UF;<#EoqeQnX5{9(pp{RMdeJ=e~zfnrcE0lAe-Ad%xxX8bg`O! zfdXDQuWT7LJjSl8u5&ojK+C3n4#h7k_g}d| zjG$-$Hsu4g^>51jF{a4#P#?ngZeCW>#*V+K`t=Z??WiUY+xz*C2J~>nE&& z;N}Y?xt&q?<5nO1naCSba?JukXE^U{{bkYEkLBYL8x{B>6yo&-cuTG+E5?qQkb${^ z@ykF3tYuu5iz<|`UaLDJcPS}W?s*O*4W9d5v$AjOaKjdu;nGFHCP9tOP`(|z=dN>~ zg9eaX)*XE|CQe$npS*#ZeK{Bmj6#s6XP4O-I|m_}piT=Y?~fa-Oeq5sW+AeS0w~j85d&LQZQZuHOX}>XYF!Oa8_ve z7qhVYVwbXaVm{|g7&MbyEpW1lv+Hg4$OXap=R$lK3%?M@;g1nLzC5L2yxtX866z+; z0@kInOY9!uUfTl{;c_cztbS_86YoSgm%Fu2jfjxD{!1F z)Vd3)2-n#MJ_iEQOTq0@0jlaYC|Mce{VKt`O3z&FSI;~=kS!Jv&vo_#{_a3IgGFW( zmOEfY&#=qOY>+0k7OGzk=8e~y9*0V*3&TxAsL9SR^H5_sHCBMXi4cT1NG{DrA{^Uq z-2p;KLw{ZGRV=ppQJ35s;T%~fKc<`JoGtF0Db&F$j}&)C$X$~h>2g=4!yMQp_v%(9 zhhRo)Fx06RcebJ585;&R5OBYM*EtRibyNvKOmYsv7#$i!&x0Tm#)_V$cp*Oh$;IhF zqGlf>B|Y4kiG8o$ z{}njpkWARK#NK09LAzuciKiY~6Tj7Mh397&u&AUc7+;AhdkX&HMTys$tX5;Z-fz(Q@r%w^t~e8Ia`-x5ThUMbEU?b7?UOD8apT`E8K#=MQ_~kNpPE>fL*z&jwpeV7?u7b_pbzW7OaYNz`T};Ljjx)~nagkp%XKDpZRte?>oJ8afBP zfoEaqA8cwL{VI&HexRdo2iA zN#SB>6DCDpD`RrKUy-OMgg9*O5Bmm$L4{e##t3>9AipV=+SFMy?TSA@7; zM4P)gf`3+gTnN410AzQB*4q1=xc*1H`kesxd?I>gl8FJ@drDvx5q7--yIAJG3$1W( z?1(*4UGO<9B{t%b4ghzs?HI@16QVQG5j~qFN-LRvU?0z7bBKohQD%U&u?xxY6au7G zdI2DehiP(XbG_O(bhGl>%%QF=S~b>6qiJA)-kwO>V1l3*OAfW_3$UYEGWB;9Wz|cC z&IFp5eQ!^+3qbm@&q9jQEZHFR8M9d4R7?buo`R|JrV^83Me(;#N=8fJRN{s(70fM` z4<-hFd)Y+*g&12*1F&kIl>Bc+arSwT$G1fjEprg(3?K**ZUc=-+DYQ%uKtcn_};c_ z?)DzU>Eg^zxr;|+)unTs1c#idSSOic-u(|AN{lF!B)Q#nUfhh#lyjRqGuruj0WBYR z35l&{NSXLd8`YVgomKCHy$d`6BG$m)N)KW?iL&MiAc!1!f`e8AkLgYD732~sy&;_N zYQXES0fkqixvk}}=%K8OxMQSzt-t8<)S!ZhMA$Sx!hJ1jW2F=!KC|Dme`BjeIwtO% z&)2b5O>w-d_1*>}@-gIiy{iqNqS4!sfWhQO4Bpjoj!aFOz@i#3=kC9OWSjD&2)X0h zo(G$)oX}$tc41OZoO#-7_&>?nR?Y=7BqHGz{PKSQya`8>v&)Or3y4L_HWdTiogNLn zP1`k$(eH#pC2WNB)G>bJ!#dcG7)6 ziiaZ?^EgyLqz?>xrY$f9#EpY-O$g}s6qCm2$_NviT1$9r+c32Bp}ak#fvzS`v?}I zK9VzW_aU>i2<09W%Dk29*~duF-lnBz=lrOcCQfy+Z_p#3?^<`kNYZkO9YsRa#JUkV zIQ8n2XamkeNisgB;8wn>V_ngQ#1@ucYLxRWziFGgIfErCc1(Un_W!lrohyD+^s|2jw^*u`?Ju8;2Y?bZClPcJ%e^P-n)m@f_$I$Mz#kf55N&FmbY2d~4fS zzcO*smffKPes2)krck0A^y1KQAcp#2`A1#w3|^A*@xXQUQEvi7j0eHvD`o1Ea& zZKgvO@ctW21&dM$isZp!7FePjCQztSQ>M9^GA#m)r?@n2nxGkfX;ZOO4TcD?q56$b zR1H|tfG2D!l$LI)mzIFMQ`|i|ZCJcEYyk0TW1%z(7RR))UP^Liz<)__5dvxvEjaeT zReY~r`B?L0$^D&_6PLz!HWu**i~80JoW|@C?i)JiB#ag?8l%*2;omYlS0!pbAOgMy zt`HL#?t~L)j3+z;XCV4L>kbg@wD3ZVQO=pmT5SpQt!vQY^3`6=ml+H$|W(Z}p z-Ds{t{-sJ|fPBHoK0qq-5*;Vitq4xayd%7;1iDIjIELhkO+|7)V29Ktvu~Ima1+#x z8r(x1QvnqsnQ&Qj#r6wKW9Ph90n{#22|7E`)5;o<(>;zU2rZh)X1|5zFu=ATQM7izHX$ zVwd(1yiMJufz1}Y1%lV6FHtR^2!QHovP+sX9$26FEc-YjH1cUn- zTvQx^Bq0?J(dWuKKo@Os8}*0jXkf}qlPAUOgcgQ3p!{|6LKkJ2!-(^!Drv_i-`vi7r4?)}1Mrs2ZB^p1D)3z6f!2SW-do-{PA{(Kv zkLAXie?O^?%-aM%~2l7GI8`!YACCec#G_D#V#=ykX zKsgqE8Vq*kQ{LDe7=VSV+1V1+^B3AC8TVo-PPGCAcvNetqXG8YD|SD#3Q2@O687Yf zs(l`bQ+B4MxhB|vVQP^&!H?19IKet%psLkKJXb?Zz^fWqq6y(WL%PW)AWCJIZR&Ok z7Q7MiMP1BH!C44e( z$X*vsF%WU0E&;2}7rdo38;5C_ zMZ?w8yEr(_+dhdtid^{2)9iD0dVKGyyFjE=aOdNLpbEkdkSW7_l5h999_2HyHt7!v zu^j6OT;fmAn-#mX08I0I<4??H%YftQx8BpLY;s#MN8kdhio3cbuhjc2G4XI@Vla6p!>ajo2W z9YSr9|0*;#Zp69Ao0zli8s|pxX&URIp7E!f>f{bR2x-Jche1BgiE;xpclXn)t=yjH zfs~pOHxf)n6a|@0QQwRMtG7ciPo&4WE~zgf;6w7$TcTHcM!;LIso4D!q4EPN;Dv?r zwQ3XrLsD6;fxpPgjm*q)1-3x1&VW8+(hQB;&A5KYyuchALMTj*&!!fW$v|q;G4kpc z;<|Of2c)AgBoSigA`StLP6>&?FaeV#s((DG9elF@lz_%W2;~8cLm;fha@t9gJLu7( zGCuI=?zA=7=_|C;OdzeA;&0jQE6C(S`-j=bRqTEc6j6uJCNnB_r#*s-D;hQ25w?aF zpgVGkk`hab)HlA@&W@TTG`|N+7=rQU3f{Z*|AKPYpM$|vzCTV_6kZe)_iChi`qRwS zK@#1-j*Qt#1h~}dpg(K4C1|P!Fx6`4z9=C~LFi&N`j(+fhgb)AnN*d|n40mpMdIH! zVzVAiB;O@NsToX+0)_^kM|xR#d){al@dO>o1i-Earrha}jM&f11b11DglqEd)oR`? z5*@%na7wzsW+gzxl2MF(Ru%VubbFQ%j4y-gXqI(gUk*@g`4uh#U|rE$jb54ZfMq>F zSCCAspORYQhC2L(rA-z$Ove!f7Q;$r^$ZMpnct4#*o?;PJmjnq{f08apb8-iVbCN5 zcdfX2(CCJ%@DlYd=w_MSMAz3}wrf$43Ad2T;aM7~FM0-oDVXlpsHq>oh9a1p6LxF9 z$OeQOdnld=W&bt)-A}H|rY2)U@N(YA1aA>(7w!w}(AD8!8IU#L60RPb$IO%vQk{J> zhg^U!@<|s>P4oYgSM0W_$M@0H{k)6G?P*ivB1qi}_J^iEs}2fH{Txj_{GX?OlV*PM z-_Gp#`^>Iere1o>)Vr`RB4fy_5Cw@LHjuT(7IH#%o}LJ}gYgqcG~3mkP-$cpT3*y^ zv?MuK-wd}s3eN%!p-I3lBMdDhSpA(59*(97#wXJpomOO)R*PysSVA*pC|cgZIIuNY zqj`;F4fEEDz}f>9(0EPQ(E+{>OM0e+>(=no7JgRaX|u1yz9?C1v%8}45x|on{JTZy zNIR0%9qKqj`x6M`00dF3QY>Iov{AGvo1F%|8X9!q?}IMml?J_uL1g#4WVherI)Cu{c36W3=;+=>Cs~@y?RZ%YZBO>FLRKgQi7xV!|m_D(pU-RjXD8e={ z9v@kCtM|~nZO*awAb_`rmL0FPi7hP<5Lk*HbpaaWgF@grm~AUr);t$xCnH|K`65q4 zSjevJAGHtdR6PaFx=0bn6J4s}4iJrim$W zTV3%k?lJDi)^4ubx2y~6a?Rm&2?2b+vX|C9gJ2OBR(0Q8nVs1aTK63S(AGs1eREw> zju$D4)b#GGdJWk5^;k3Pnf9zfbf6sqQ^1@Q`(X78T;93l0|tb(V#{2Ft9_7O4bt9U z_0UP(8-;x(Up+0ZyBDSjs!S~NhVuGqG(2pd`NhJaIN)tu+rWT`(1|~4uVPCUY`Xaq z{|j&w>Hx@xeM-gdx3G*$?KcGWj&cK+)bS^72JV>4h^-~4W6=hD4IzI*y9PEAuyId= zYG9z%o3F>E>#drlv01_2x3$-(GCsw%s^ucKFyehCu`?V;8Vk?_-;$?HHquTqRqGVb2)hX z>Vfa@NnD!v{55gC5v_7>rReF0m)tpWZ?p4={~7|60=jbsUMA6V0adwcqUdpbjW0Y>%D+9B^|C~^41U~m(n4xnkluFfG2wM2_8r|}Gj7Eo-@VXB5g3^}NC4m2OG zTbDI)qB9bAYJuh91;UD~!&Hdf1yV4CC8WiO{OI}zLa6S-m6m1$jc{uQ^%bg~g2>#? z+Hs|edkZi*MV$tX+YeUu(mwG5QyG_{y(SpK^KnA{WuY zw@`3i^v@!5&?|@=#@+}9muavDHQG|#E zWT)wne@iIp$Nv5)6gA=VB`p$t6f_ezIPh1C>NfO42M*Ra;Ir-$^;swf66Gf?nE32OBXg`l$am0H!1WawPlN zC42v6e%Pr}^@-24a|L}z9JVao6p{s3>D4Y==^;{~qyB7|fv+$Dw1!xXua(3W9xv?y zz5F%r?}t~HJ-K4{7j%l*1uu)0C};6s6w8NE59cn_--g<1u$^UUEQEU>CUyfVne7Qh2UMHdoKp83le@}OC~f`+=0>H z8U>hf?O*GoJIIYeeB*Fq6&H$Fx_Ss^ZKAwNjOKF9G3s_hHg%(H68 zJ3uphzS$c(er*pOzewDn5hyp4TEC;Oph`=EC+H*RTgClNtepAN&6Aglj$XsD4Sa&} zlmB5&-XA1+hba*^=+HqfkFpU(>Tlpd`eKYYwSfQ!{ zAHbC|=FU?;?9><)N~wK}iWuX~;;-2wsxo^;Un|V8w{V-_qpw9G8l%-H*duH{B0q2r zbCazi(@aK)UeN@Nfw}fY-a!FT=m@ljf+KvNzYSYumwcPGVQLI`9uCLCV4=vU_bsAG zIL!WsK!xZM4$VG8_|Zo;qiWuc9T9~?-&ne0*5Ruh!f6#<2ZdxXb2nT#wXMGWW?OKV zCYLRLKr?KK1ZGPRVMHbm53rA_*!>VG_y!zH5ilLV_8|R-@KE<-C;s7xy!y2CV+0mf zSb)uYK&~M!a~nE{7X|Fn3^DJ7+!HCz_)yIO3f_GB34=9EggTaikNJplU@+nq9{x1+ zSx6t19>I*^$uR##WIDWS1q9{4c7wd(gbys7dY0@=6bO!GvpKpM=4l^%2i!ZVOT-tL-vtqg@&h^ln!~}fjm4n$0NLjFb0N_<;|tO5$JQT5!%GoRCLG(ugh(> znDzZ7>Tpo2%sZ7`BxN#^B!~Pmh@qXp5eAV!K&%qgud!jcxCH+NcyjyDkrlgNBj&%; zAJ_nyUsC1u{>u7=sY6!bva8H1f)aG33a9VxSB@=A)V;D-`F>$y{&nO7pgkMf!C0R# zg{4YR_&7K?T%7HC1EYPBGA{6Nap1qC1!nPDh2V@4%Bdc*3LTfT+Ck zIz;L&a)@;QwJE|f%89e=o^R2xNPPpHWr6AJWt5j+5VYxWdxhVuM5<5V$YDO&0Webt>^xMp<>W!0bLh<{ zXQ*)ys1Ez!irvQu>GjHM#dl0AB4H$n{MVd<`Wlvp4ELxX!e{#;>B@<9@1YSk_2_F# z?4qO*|N80w95_%Ug}g#bM(wtRi$vYvCuHaC0 z>Wfnjl$lL){~O4`a#E6)vgO)^(#cnV0=|t#s%=|*xmTy}`zed^+`U=4dtOpLyLa@6 zA-L1RK|u8CON-Qff21%N3HWFY^FO_$46nRhnOd2oOR*~zmFb30K^pobWh>@Xwp6C@ z=aly=vkEId#Y&-DgA$cuhq6+2-!&q@C3PY$nrWj}NS6tQ(V)2pe6R@%WORyqOPQ{`A#jOKs;GM;myLsjJRpVS6(2JWyG~xFa1=oYf3C?GJ`_C1j zMjJHjBUpbKAEYNru!By@ifg}$Sce729~9Tm!VG+8;2^F&M*tFEr8Yne@I#2Bs{ASp z(Q5}mH~Kk&0q7Y6uAjuU_&@=ILb{GS&N}fFelMh7gw3bFsI9~Vw)?jqC(WKsh6{izU^!FwORfu^tJ;g~3rGdPC9L>SaLNFD&60rctnWpxB< z2m*qU%dp$vfNK18D#I7Yrv3r3g=XsyL5Af6CQW618lBW=m?&I-%p~&l5zm?Ce@1z2 z@ola?S&Ry$lEXvxLxU_j$FZ%t$0Lc>7~bZ$!K{ig>RYFR!9E@Me|`!y!?x>-^*SYS zsl*H0a+eN`$Vmc062Y6HD3Bn|iu%Nfm3RyNlo3XTyKe&ajza&aPUX9apvpw$SGAI2 zth1rz_fBPLT{_Px|5?TZ)N%1dU=D_LBayJ+6bf>{fxp}1iFyn^|2XDR3FiuQDYmxMA2B^zZ9N)<|-!#b8*hnHWXJd5%+ z$`O>mpaf95P$EE_feOE3e#H5;R^=((LrU8UlhpU~R3hJGl&xz~R&w0JqP(KlA1}M> zg(L4ARD_1)g~OLyxk7|-eg9tegnpU&^DS*}qwRp3P8j-h;o!mZ~x6N&}JhEk2P3S|?@7L?af z+E7lQoI|;Q(uER*3xh2G;uyh$-)}uU!vK{3Z%C{(2P@-syDD-ba_&E*F0+f<) zeFdItQJxOh|92Y#o&G=X=zq5%2|N3rcEl-*A07@wXVy0~)fOySQBz;bo#kdW)-G|> z<}a_WEyWz6+MI@^^OxFd?|0-9#X@bi#)kWAo0_ziS!> zd1I4pkMsJo9I!=h%b;ka6z@@V5Q*9slL^PTne zwk3{b^OiiM?TsccT+-y2)!5k32**P(Sl(H$?LG|E`uElliL?y^k`~Rasc8fa2;XoE zY6Z1Rpk`W2kz+}t!@2VAxhv`#!rLrz)PzaR4T`{^;@XE+5>gsvXlQPIeZ&12PVf1R zwIGH)g5DR_hI&Lnscwoxj&Omqw($`~bi~g^zMV*_(Ql@su|Bi_wxqQ?8yi8oyycDH zUqj;~+CqT;w`83WS)^n)4xczH5)i~PTf_1dj@m{dr8Zk=>btR@#)pMQ^#&GQ%>@vf1)qmVK5_EI(O{*|%p; z%C=ch#yK?5`6z5drEY104&a*i$(t$ z;kZ0JmZvnNtVwaDY)tW`Y)&~l`RB=TQ_`o%Q~okVopNK!khC#r=CtW)?ldudT&A4$ z+pO2KI)mq$^w&|ReEl#UTlha(A z(q2k?J#B6JhV&QHUrj%j{$cw0^dHhAruLtjICad_nN#OX{c!4M*yGiyg&8L@lc%Lk zTQP0Rv^S^evgTxUXC+xCT7Hr3&Hj(TXS|y|8n}bv;iAZGf2LyW^`nHknv^4 zvdnifUzpZ4Eh%eX)`6^#vcApw!P0Gs%Qk_4b8@!i{4VEg&bgef9C3Q)^hMKmO@EIT ztw#10kEWDIQ#w5946%&1WLVy}dB*C=X|}YQv}I{)AQwNUO-PT+PRLFKno@Rh_Jr(|?9}WG@ZXx9mtBxum~G20 i#!QOClQQUfwmxS~PIHbs$CI-$=X?&wZ9DBT@&6aAp4Za= delta 16613 zcmb_@4OmoV-uF4f5l0z0qYjFRINB*uDQ6Uf;d4|JGtyCzuMnlca6%!RK`nE}&X63& z*|iS!w7cz-yR~oaZXa&?_!Sk1fnOE6m0zuCS{)oKzfwSR-rxVsVD9eozSny_*9+Ip zxxerK{r|rI_l23H_XA#A~JFF*OxCt~k_+Awe3%|KMM+sfqI8CxK#6S>khbSuc0zK}i*H5I6BpHbW5w%mez z?;5T2cj=Q(=O$}@%9kJH?sXXo7)IQy^Z&3A{lp6~G>dtEm_sntW_$a4 z^Mqt?m5}fr;Brfxz_wT}^4S;7En+Y0%Zm!|O)WmIo}gCiCFT0DF`d`H)|9K{^3IwbEGtsFQLzAjYHu*}PB%a;6HTV?a7V=y;Kj zkg_=d>LaXTDFYygmOhb=`f|bo(y{AuV|YA!T;37>u%=rh8~V@Dys43w^dCOq39a<0 z#auTY=&8Tv7%W{@4&Dp|q_+AN>7p}|k1(H{A)QeE19kFW`lq-)&sC|%o|0B+f#J3w z5v^D)Pb?R74E`9Hjw~U@h>f9OWZJU?Aq-oIlk$Xk?`nO0fcKn3qgU&#F_=$EI>9@e zqpf;pYrR$-awh3g>$m-+EFoe2nLr@WA60;j_Y7kh7vtpwZ`LuczQqyi%@N{lgy}%S zu>kldFkc78^0e^v!+{iEwh`zyNe&^+>j2ubz*yOOsY|+yY)oTzOalP(W=ErB46M=x zR%w+V09H{EDs+HVFI~pCG^s1FiVGwuN7ObK8VzWS_T>mVZ?<86!0{0i$oq&B_iDXs zg=De)^b7#tnFqMLo)7I(=Pks}ICB7drLBcw60N$m(ZQ`brg>>s-L$K_?$}lLox5_* z@?{Awo$qn2EifgJODl@)-PxWN$?tgIKLL2qT7Zj{f)^Og6F5}F2ONiioWtrgwjgpG z+!ofafjsTC1|3Ys%7LSB1USg^W4NR}49dMUL2LYiq{v1QJt?0n$WkM%6OW$Pz{i&%E2 z95-|fyH}nwG>bhT|9Ys4ZQsfpI@m##0GHq1&3*-lX31xxoNS|9G;Ahx=PSdW2p6o( z2TKKc*zg^$f_$6QDSZviIF^xGm7xGaIx1a~_OKlVipx)%hW?W}*Y~*5v@+a$q(M8q zO**i?htZ-O)p|bMeJ0a=pvAr4-Joxn&Du5oMQCJLX64|y>{lUvUZaev+4ec&Q>qXB&#{+mIXhS_1ja$COCX+9!-GF@u5vkH3! zf;>NBglo^`KtQDl^n4)dO=xcMUPhY5mA2V6g-~Xv)ZK*STMtQygER@gwNOc5E^som z#Z0NkezllToxf1gL*_7HV~L=3-{72j@ShG$HGX`1Z94mrzfI0FfIeqf?x8{Jk*3<`RyPqV0^GSvcQDA3K9#(d(^@<(M`TASP!IE)OFN_#CCR$0w1cgQJrx}i~C+7$7YF%9|$id5q!M=k0*kuKk zc9H{xWjN`Cgew&{`bUapZ_#OUAK;rm!gd22#ALT|Xgp*_2r7Dn5;VPoskY z6o~eZMJ})1$}t_YxFvN~rXyT~F3(xlo%#Oc1qC&>aXY zu;nY8R50}Lyl5sMUfqMZnqzIsL*HQ^Dme1$Q4=&@eJj5WpnIU z&2vA=3uD)5@_&#&kNwRAqlM@TRTVUMP=CdCjY8~D2u(TtNuHe(ti200 zQm>K@%y$)IOItaA{|~L8##W7QP{JnonU|I0H;Cz|+d&Y~t5$oXlF_Jqds?12cFlAT zV6iDbPbFdMy7n`18F}W=_@64Ks~8jBIG1bB)-h7{poV#aVV=w_&$4M62irD35LFJ% zBfmR(wYJ_pMk$39+#$}d zdIp?`FbM1SkTq0>UV>#93kO{bf9<%P0TtpE{duymVk#s3r}}A-AJQeHjT3-CWqqy*RUITTe$t%+*HBH4JFAGxG}qbRDUFxdgz!&>p41`HQ(aF`v+Oe?iIm#8}|?lbq&rSu)>;(S16#|?EUK|00;p+ zDkvcXX(Byi*FqN=Z1!;Y($V>Ws9;}55)|ZvTzeQO=N~BE&2-G*3NRtj(jN)2)GE4* zAT4~}US%k9zKW}=8Iq1GSAGP)L#hK&HCWgG0q;_k+4>6!IN5o(3A6EmJvD-BFpt#Z z%V(7(Ut@|AA%Xn?^qwfKL z5nFDlbNZHRoIk$*68PV)S&V%el_YG3bdj{0lPa|xKmy}lznAu+ZWyMBl)+yh%ifuc z_i4_%9Q3$;yyhSd0(uGFf#j+Jmx7Hen=!3ohgS?`46CVugGtr@2`N`{`>MYSRomIh zm#HD28j5Ma$7osq^K}5gZ*b$U_;Z+Z>{8LCNpU{J?|^F!%8uoC^cTA)Inu=L5=TQQTdqpI%eB6jMR{dJ2PZJdNT2)1PRzp@oDkRyQw1$Az@acoSEMS)bi9C% z4uT)m?FOb9;{Du(;P5w4l=fgB!@!P+9t1g7J*>r`c7^4<^WKm{+x;Xl3}1f$GU@W= z-N)2_zY~lX;AeMst-x1p-B6BPszKm4yoPR&6%I@v4R2FJ^VikzJ7!>CDRyp=vwErc z6j$P`#1@E74F!Ogq4t5ITD}vhV;h`t!Ft=|cOVjYohAX%PkmOBz=X*T!B zd}2llsj9BoAY-Ksl(RFrihd$EeJ_}lesk^Y!a!65I;rY60F;7*^{|m1c|+nikx8)g zAzw8RuDCqD%a02+3uA!Ad1JWQDFAwI+;zJ5*Ad}%lJvMEQXv2?L~+>$&(i{_xC zg)kYsIkwZLxTsx@Nm=0fJqRrBHK_WFR-wt&Rfeb&4WNz~r8>!Mw@vBpM1`*(wPH2B z+UXqC!Qr6sZ?6rF|1B~$Wj{4}5gYXC5gDPScLXQcLj5l7c2R$u@~YY`n?r=iYt!Z{ zX@D84{4Lmt`U2))AB2yBeJG%cKL51uhpCTxGM5g3yAT=p08CWamX9rHrB7)6a7mVeyDwW{JwfMxyj zIj|MtTM&5TQIO|8EcQjy)R zuBX5R{01lfrs8m0Hv$KkW}%6|?NMf9-Hz;=D7gK4-$r2*Ri7wOO#i^1Ln0lmOd;r+ zgN@yA#|GI)74JGnHqJT@+__C{@p;q@!cju}MqCmD6|}_2h;mKZSeMC$4Hf1pH!oq@ zB6kMoh;#?%!?BSKQ`MHq>rtT!O$WDxe7+N^(^#koP%#MVhKm<95wkGpFaQO-FX5vM z2YBmy-H03^vf$b0uuILt1vJ0~p>}U`h43=I(uoiaq~nzgVjLS=5pSAtrB%FvKxhc1Y(nLh#(BY4HhafU!&ML1II~? zVu~t9VPQsG0(YtN`mvo19Fq|z2OLDae8w^9E9uzy>vf4P7~&G}llfzSmnD>ggiFjv z*j8UHYq_;j+s+NfdBlH-?WA0YzIrP#U5+UD;qk}BKoG7C_Lu`o)@M-2x3F?KhAqKaq^I&rVqXIkV3E`YZ+$o^+se~a8GuqC zzs+L>wBjv}e`Xuuz5OPHv0VHC<=0Sd07H9YlGfi~+zhj7x^$6R$7den#U`}E66y)P zu)mh$Qm1%~;l&att6fJ|S()9hqK9(oZghhifv$PVvJt8r5f2S>-H(%Yh)3Q)Ctr}| zywj4sMQI?FPFW6Npty!o@^|zk7CF(i8$O49b@8rbETBArVZeBK-&jN}N~CEzwp%LF zOBQJwhp2(lMLd!R@(`b_6-vO$L^#QH35Tl~TF9nYNkP&9DDC=J{Q8J6m7p;gr32^Y zQE!jXfZeDg0EmC&t|wPxhkvFWUKoo%|G>_`*VGA+sza61<8+|rJgI-puXwuDMzS;RR8`IP`Os9 z1VdJV%WA+227Kd?ZmVyE{qg}sOd#qth!0)OI3xR1IA8x)?8S1!Ip($+=j+1}EAYi+ z)(1$HyWs8~>#9~&`7%=FB|;p(;T$*xAS6haa~0E3?5(Q~^8ghl1aK0hOR6%8IEDsz zP?f~@iuM~=gY7sv4CFo0=q$CmTYC}j;-7C7TYALqF#h=#Qus$*#;Wx;*z-UDJhK<78^l4JLr2(#4!<)iesDU>NdG`ga zO8rURvxtg8(SfA=3`v2KB9@1y9BhFuJ%hTtCLUm*aAT zXycuwg{6cL=LrJZj5g)5FNjP*J*rZQ94~jKhOakcj7vp#R*1Dx$WrN$CnNwxv-N6g z8nV`C{6TT>Y%MlV1okzl(xAME>GX6(WBkz=pe4I8@~ z9U$gLOyYHUgcy8r%Ynb78(^9AwdCZ}j`d0>lu0^y%lrlp4eP6a9`zKdro||V`Ix#W z+lgz~B!0U^3>f)ma#817bxOQ7YE|TW>=-7IfAtFgOgd`C08XL_$V_w15d&+T2fE@= znC*z-x5Ga-@}3dYsr!E3qeljS==zixvA$jz_Br8{Wc0EUg~$WgxiSrme-^4ZZ2(_? z5?@&HD&m()Sm- z2^z?w`Vx^b*b)i}0379GDjtQ`KT2wqdSUliG?|oF4$xWOyx-FT=7@9Bc~TDp)doOj z9<1Nbd!_>#6tdv|0rt6v@`7lLM>U|JrRm--1dx%I_Nb6ZO)4ZPEkPeB$4V8h!%{oj zDt$WsI`7Fsg>QZU9n0mLbD}4eqs(ugf6Lw5^0d`g^fPuwRdCo4tSb81eWL|g{nHbC zMK`HQp7G#97fHHr0q4BfH4}_31~`7hB-o!y(%Y4JHC6_EN_}j~5O_l3UL62Y(PnM| z$ncES=M7@_Fy3<=u94JgwKN~1O*#GEjfV)wID~@9EJu0I=P&?`+_1)&!3}%edp~sF zyo;BI=iKcIsM^pCmsaDnhU6aj{{SSnwAI}Y?x%Sm`d^@?PxPI`CIybCsu|A`5YYiC9#90AR%XxMX;iv-zey+5-;Ge9|A;$jA>&y%bf2=0tEsPf_s(6`qK?7%nyf_A4&ihl(sWX~3rOjbhE)4+CM>WJ1!6>XMzUb)44dRv?i=nR_XMNv(AoBw zhe=(5rZh%*2xrrLw7oR9^V%emLpV&t>^eAybf}1bz25F-=0nD`d`Om2RK~}?wqKs z>ib^hJR&vzjljN@Py)y7acnzAG}6%=HH^5#rDfW!Iu>hGfRMAezsdDlgw zr@=c{3*Ac-yHLcPfF2(s|3a&$)jQXK5eAHKq^Xz(6$p9|U>M%e6vBMrL~&y`g-p2K zG>(p^3{HPu-S2Bktqc$phnF6KGyjkMTxYy5tE+{Up@aWBr)eEnE`CSQRY2Q(fB=V! zIC81PHhR?kxqJIHOw#~&28aml2G}AEjv4`3(*DRAu^+OGs3CjQXIq+8v*dD^FuTg= zHDGkm`Gpq0g(d(i8T_L|V5ykMRfZ8{<`#eS?keI*O<}0nrri7rEn8SG$RM`IfJM5B z$d&`EAe~CVrXY1Us)Pi*L}bb1Qn`>sNO|M~Oi_duVtUd=$_&(~yiNo_W}`4$&A`tk zr3kk7sGYm~aMStvPMj?K!`(d}^1u2BSu7s!PGeEpfs&vK$dq&@k{ zL#Jq^BzT9)@jf{d7VA4KIS`N{kuC%|l+CpFjcTk+dFWrK{+y z^Q_>^w0Y$UIQpM}zJ^I%yJ`M^pZX+D=l>41>5i!{VDTV62?c~c`r3eB;`vYj%+69w z3=ePvQEQ2HTa}F}6ny;(jfzeeDOcs$P~~G(8Jq~T!;2+iaitEeG)*9CHOkfTuf?t=ajBZ13uL1n3i*Fx*c-@yl zm`}i0ms;VwcPLtd6pHaxgv#9;g)+=yQU}pKZ0ew^!9joeWzgS|r9n?&5cs|jGgk#W zxm|s|+LgDd*EPPLVjbqbPW zfG65L`ARzM-!C^FRsyF#DIp5l?5Ccfv3EY_ou*|wrNN|15k_4qmT4I!3%V6o@_hXW zY#16efk*{jBsS3?i>frLa&?g_go%=sPPoBPY7{$(Rz4%2IKrFC-mr(2T5j>aKi#6% zt(J)qX**OY<5d!(?^wx9^ z*{E!eknijBK%+ad>EOYD6!6Pf=)PHWjtN)Z!gN@IMq~03I9+NR4w6hulL+A! z`H~{wR0%^<$E9(0?qMK89n#CsJmwvsJb<%Qh%|a-IKfYHbsqqs_h4C}J^1?hNB{}p z|85iBewUC5JC;F~jHInURB)jSpq@}Z0;cigqj?ElS4Eu$XwdZyU=z@HmYpI{C87I| zK1xH;ss;nqF9o|7p}VSP+m#J8@|tsmH{?iv020|_i+3F*(fk^|5XtegRQP37ZXJo5_}qF zgxDtF-$Xu!&Vxe&Bc@z|@2ox?=RI`shCPv(8}MqhO5l`L?eK_i@f%u+Ww|L*f`E|* zNFfwi_TMnl*Y9`m@Ns|q5fq8q&;Iw7`)H(}V7Ex+{d3HDdM z+9H3E7pM8JEzd6;=lT$1+w6(OyY8lqTHrH~Z(2x&cob+00MCFs;e%)ZZzN+z-+gq8 z0@qMz3`-m9)B}`6R4i07XgNO~(srU}BnmU;`H%VfQ6u^!;A2znqCV=4JI-R^HXB|5 zT$JBj7&B=TgxGw9-s{mD1gW(Psya}Aq@86PTJw?V%8GXg)S<<@1{1kf%hwl9NO_5v zj5tjk$8=-|&#jNaUFxwZtI!=DAp%?%{{fKu(UrUe9N=S9ir|L^(N_tGgJ zLECH(_j2_YAKm)fd|oqjb~#w_yDeJ3Ys)bIQ(h{fWUtg;M!vAqGGQ`zF3l~IsJxw-=e!m4i#79Y1SaG79bkm zfE@Mu2dMDQ{j^D6A``nr9#fi{a~F;=h4_~i zyhF`bJ_NfI7AOaA(6%orpMvpeb5<~q5hv*2KYLtK98dyijwOHh3#fPX-AKOjxco+G zHva;&5UG43V2^S|zEv9S>VTdoP)5_VA>KBCE1J#KE8QPMekRyzlV+4 zI=*~(*kB8^25#Az;YeT-rcj>$*wDMq-*06!ldLEQ2h#+8GlC|Q<#mq<`AhNNcU8t( z=L*NN##CIkj5fekg|~$B0YjshqTUNy_fk7GOr`;K>8TzYozay!mtND(nzFxuppJ5mQDFvwzX*E(4(i=!Skv>8C8tE!hI6|(`NO4HXNLffWq*4)) z69+PzklsLQK{|zW8EGIu6p#{-vXF|A=*J%VuEzISq^3~$f3^|e?*G$H{%0G3bHMz| zPUPivsq7f}``VGNEM`Vk^~#FbWh=_7Dwt=O8MPH-yZVdnMKpftOptcGD8rymIrIf`!*m`Ij>@Q4S|RC&uVDow5qD=N15-`{H)rF@xa-6wXmQf*t9n6 zA!n6i+4MDzioEKAW#tt!D$8n_)-XBNd6(;6E|+MaB7BCUwko(*Et9O)J8NqzRyeYj z)q>#FwQI2EJgWacqIf}HtF3z33P(jPp|ynSg45nX7KT|w_3~k7lxs2N?!eXj-5<^6SICXIp=3g?KGxuk9W}eNwk{QTk zOwdl4S7%B$%{6T_HJSeEGX2eT(DZlHNz)nAccvdrG3I!)*<5X|Gym4S$GqPxPyWs1 zKTiJh;K9$&!xNhoaQ~gu9q#H>ClVg%i$-hn>W;vBII`wGk zxzxwgHl@9mHY;OZ#-fbJGaMO>8P8?R&n(Vdo$1PaF7vmUyE0oc|B-nvld(c&WdS1A zqy+{tO*T`JX_aZ6X|Q>ud7L@X{Hl51l$eC^2_*@Q39lqDiM5HB6GtUYOiD_6AnEVP zr;~?T%F`GKYy;+4YWjz{+Z-|3IC;wCZzlgV`QVh75_Tj+B{og{aO&QvBa&j1Rwk`Z zI-PWP^7F|Z$rqB-E#;O*mqoU`W!Yw7Q^HeXQ|?JAOsPnzP4T1*O^r^COHD~lN}G{Z znr2UXFnx3SpVCjIpG&`)J~SgeV@^gvMtR2gOjG95nV!sVfnGqcBuF-c=^oQ`kgm+s zV0ztj)byoEV;*3(naifsPH9ePOE4rBCT~jqUGkyi?=0iXQ`{-PP1%vsoU$k7(-eK` zu+-SpHL06G&ks}EQx5~3N-6{S-6mU}ofN}O)=xG}j-G6tT$osyxHi$9xGAwIQ9E_X z)R(5p|M{#bp*g)JeQ)}{^h4>L>7S(^OaC(cG+v5 U8HX}DGvYJ1o_^NIK4;SYZ&;P|E&u=k diff --git a/crates/uv-trampoline/trampolines/uv-trampoline-i686-gui.exe b/crates/uv-trampoline/trampolines/uv-trampoline-i686-gui.exe index 4221696a17659b1fee988b030c0170383c4b7fab..d6753380da3e2a8ec4ff96e47ebf0d3d4acd4261 100755 GIT binary patch delta 17501 zcmch8dw3K@)_3=$6DDM!2TUMhfB+4I6>)~#$rVUqzz72gxfvi}62lk?CNlvMnTb7- z6vHUmxUcK7tFF4MzOJGmH!mav69Gj;7X#k#-s7MGDkK3i-|zHHkX`qAzQ4XN&r|8H zs#B*>TlGwxyN}{+;5+^AY?|j;nm=kIVV35A{w6HH2_nfijLC zIXc0?anS%=v!3H#9~gnB>O^^H+;z$-UZ1pdDO!vul_&zrRe!N)oz_sb%(jf<;zyuk z6$)XjQeNaI^Xru3{A6XKZnz@qM(|E$l5R48pE6H3YADgb9YrC8t=pS)Zr)Lt%yIAh z_E*e~$bI1rA?~J2{;2fkY_8bKS^QBJye;Vf*rLLcVt1Vs>0T#A1N3ru<23=ek^o>4 z0Dbu$!;S0%bz}m^wY1riJh%8+AL|;|KIL8hr__@yC+?Cs>ttbZi5gm-$Z_%k!+)90 zr!)Im&yuCLF7BWy@7A*q*pakU`yh5!Emtap>AJ0D%4T7bnfqqpdp;DwH&i0 zJ+WqUHYfYT8zZp5ho*BJ>++5B7&6rsyn~C^c;@@re(>-;zK`XkTw)&uIAmn!mMraX z4>PxATPzl}w_kZEG$vO*t#3JK8}81L4B|@#AV!cpIg;QL%m>*irgsYxP!417v;BUX zB&d2Y0NcuZK={EC>Hg*zp9`?J2t6ukq*GRRw#Ads9xP?r)gc za?q~#6iGnsGm0<0>#i42(=%%iE5F1Bd+NZJlk6+zEwrfTOEgC03g>e<-WHxgY_L5n zMzEU%mh5Aia8*FKa!9z?N1lQZWl5NcZ&V%(yHz)uS5Ak`EqEU@v*Y}aK7kzt5LO=) z5#D{5p1qfPrpe@1C>g1!~SOvsz30orj#Q7C-yaqOLvd_g9vd-Aar$A7e*mv36`>{Lk3Jf7^v4 z{-~+2H1Z^lwiQ^E=)lGoWYRvO^mGmSVXxuo7|SP;*;Iy5}89IW@{O zyrg)ePU~L+o`fX4p`WPyanv6i0V5JkLeGx0d~6%H&>xjUGxhjwV^~*9n?0HJSi_dG z^#Tj?{snmD8iDgZgctT5>s5#O{eEYbU?y5IlLLebO(oNs+9A&Bu&9YN7evs2nQS+~Rv9_lt%M}CTioyfAp|_E%?XP- zm_X$;-nN$5fgRQR#t@BNdBQLDY_2Oz2XODF&`27O{z!J#!TMmPEa;FA#CqQf^i36a zVzI5ngVYmE?X9^S(D1%t;GKbnI(r2>=C@x1%-{>>-GFgys5cR75F@OE%HEwKMv!3b zHparmPn?Y{e%HN8ibKuqk|aDB>}PW?Z&W@TUCftmPaHFd=UbJGu`{y|{^a*}v>X#% zH^8*81wtn;bg1E&MeYj|x3tH;-7z5Tkn=TwF}FE0Irt;EHkk7z%Ieq~b+3G{JR7@I z_u2&|eB2*&#X2T=-r5JKT+*TVCSI$AOqeP3kfF<;@Q&oU z;P(UX#tKPrUKDJx%&R^JDqU^1D6)>?&co_GN{W<{{|sy(T1TOU^{Ej)zu#MqskRHg zLHptwvbpU#&ya-8*&I8kE(d}D*buzAwPtrz3OZH)SITbb?lE;B%c4GzPQvCrf0_1y zTrEt>d+MfNmH886L*It>5WRDFSxKBU<%;IlL4LlYV1xB~7V+w#@BDuFGXwuA>t&;u zRj84d>p6A27ZaBVZin7{&?97dVg}3pK}~rzavEn_SR=0+tmABR=?Pyti=JVeZ3aE{ zoNYQi2XnSR(9^)##?$kTp`2}$+|?tu8-1@#c^me@B)9bwKF_T>`x&=E>dMnkZGzn9 z2_(2(Vfg1%@A!q-8_;ph06}Fq_f7h9qHz$*!(&*O@AE+XG!Q*4yQr+3G;wMQaQUMW zzy-`@teuG}bTCD$+e3FNrflvh4lMPbdNNpuq% zAh@hI{A5I=)V!aZftqp7@Ar*I1f*x@*a<5KA)2br@G0+3zD_56u6#dvyyGcoNvS*2 zJx^dg7rO5n3MGnW5p1^LU5*~N-N2w(8;eJ8%p2o6W(#w**~5HB$i`&n`mvlX%(;FX zXS-oz%{b@!Sk4yi`r00D70P_mU0>TKnGeqNO(8J5fqLx$;1A>pT-R&?(Ff=Kg+t6K z?wmK)QxF~kPt9PTF`wQR3Z?NSJJ-i>wkUDup}mp(aHo8*zx|6bt~N2}eWfif%JCX7 zPzUFpXApNDXg?JdF;5>cFEYH{$}NDUHSb);z}w)-L-6d$~q` z5es@Rb~a-d@Y!d>p2f3ER=vd~7C&@Px{f7EGE={rIO zs`+#RVqx~4$A)++sdWqm}(lbZs z0A_*)L!EkYR~!26!-BvD0v_aZ+b5%;hAIJw8|@=7Mu!H`bqAP)v7&1^UTB}*aBw=X zXq^<(l_Mb}g5ithy0SH$lq>ZdLJ@Eyy7@8>utpI>64;OXv@vh_CSPAC>EX>{+4t)5 zFR@cLDHdy%*jwxZco#<_@zg_WqSiqItQ^CDMa6~ws5n&FTUe@Na3J=RBtcyaMgv&$C~B_pNrOUKTg}4fTKkUDh=nMnc_Bf88y=OCXw74d^>A}MPD0;O33o2&1_dO4@& z6N@CH`VS2A&V(1R&j36o25+-~6&rxNs7 z_2oVrF;kGt$9z^ieAQOn(i+(T;1anf3ubmt1+=Te{%T-?e4%DH8hW#A6Xo7%VhaUl zv}UX`tb$Pf0YuWArx19gCTPaz~F&Vj-K!Jb8et@FmgDG^cLU%{h5 zc+h}-je z4R2M79o+ww-w)d`Hq9k{h%N~9PM6e;H@PR&Hc0LXvkP<*a_@j+1D7^QdOYA+-}{!F zu-LK&YS83O(kZ-n zwn$q7b(ciTL&p}4WKg~Xb#@jknQPSGiAdCDMdROaYSyb4Pmu%;hALEwEkB_jG7X)B z-oQ3+4%Ff}uL|n$mUD&d0x7*%t?mK2nng*+{{kro7;m8s$mN<35Vr%t@s;(z|GgH3 ztfFW!uyA&=L_Mu-?OAn&##PZZ6Z59_i1JuGH#!U<&fXuymQkp9^7+(WQGOa*D))ql z_lan8t_$HG7w;DWua^PY8=|%LKOx@tBVN6BfV(~xU9-u=fb6}+u!;z~o`YR1_1=J1 zxHopl8lzf1gQdhmT+#v1j%|B~<8BV{nfQpFEfS@a%s;S?a@kzs;b4?mVC}GlWOxby zvPpUxAdH7;vT3+p7#O-m`RDABj;&gCST>EOf%$rC3~56w1ieVIsf$0ycC3?P2ipqM z>ZAgDG~s36TVt#Oh+g|?KvC9776^SxF3U*!69TSNmeYmOB6Uq$hzufZPAR!y|bejGDEAVhc{ctp}p5-0ZzwpGCQ zwq10#_aRIdXLrdxJR++eo$VNQ$ew_C;wa|bf5+P~AqA2ow>!QTw;=6f-|kEex4)QA z)BFB`tkF71nfOQ>)tQ%_R2ReE`5pihtKe^?JFuLXfG2<;a^wj*vkg4Z$HG^TOQ>)M zXu_)jua5!>uSRoQ%R$k#3WLO*W95s3MTe^j6+|S$#`z)6i(#!5Qi%A-e%JoiO%myt zxNAON!!~J(<6f0^X`4ueNm?MSA z9T)dX_hxfKU%IdxNI7wKx7+ZSxQuMh0XD=So8Xx0heAcSLV9W#-+Gr0_AwIi!Wy(1oqeI8cO9AxY^&sgf8nq?Cum|jq*i*o zAZ^0KHWPSksu$9S4SS@`7Z2t}!nj8J^m~g)V|1lNiAAj@I=1n?;Z)U@U#KpiHgg+h z5#+8Ix&3QzE3^((Bd{2Nr4*CWp12(aQUwca6^XRk^e>Do17td&^lCY(nhaNIGF*q$ zSS87^W?FKjbgv81k}cZ%2og2U-hJW&o9HDy1aJ3I?j~Sykz2fP4getmIo@o%h-0}A zVIk^5xma#75=)CvZbm^+<}TB-kC6MkR?B_P{ZWaXmh52Pphy0?r}?yzq~$m}jBH~p z>qX>X*Q=Y+2AW5bWPC)yt$abpdcxlpTZo}xOB{=o)D5uZ$nqeDgIlAsWV^GMx33_T zz;dUub3nO<1d$06>#gNL8X=Dda>oGnqZ;-zy70Gcldv|;EWIwhP1|;c^|q^n+r6X5 zwaZ`daxXFij|XO7qb|c*eG_3}P42uNpm*n80xJZoSccj4;a#5BHUIGQGxlrU=p`Cv z>)Fi)`8J&2Lv$o0zIkY_Lj*<9dnDdh;c39rhNsbOkk~aiQLvl!tkK}P#|YFxKl$@B zn1cj|fQ@W+0V}kyn~Mg*WKtzS5~JLCIqrExn31R?rGh^OBDOA3GF%}M>|Dc+tD9lU zF)Q#2>c=Orwda672J}VKlDzdW<4Wb6NSD5(!Ut$cH1o^H)KI%qWm*It$2&A_nyMLpX=9O8i4758 zL-nmtR1H|tfTwOOkd|+(la_(K@y6|WQADTj1Vq2D`2g`w3opb7<&?S9(T>;*p&42^9Pln-!*JOWxI96q zW(eG$6hH))Apf#N+W`5rk-djZ=2<#Us@o8pl)A^bn*_S2xGRF>iX|g=?z2Mb;@CIL z3%IFjs|I%`$5cQCSSFkk9m59sX0TK4YysRcSa67}#azhX?N^iFN!cl9ieL}z93=R{ z!CwJP9yW-S1PPl8*j&xG7UG5SJd|=TDfWbpzZV!_oAwUHi$z^`mqD8mSFr6l?o&gqD5zpa%AD)Ox9H ziN-b}Uz^2|s9|`Ix{zKvr=EaQ@`V1|xL34sOSORnt_>stEb9MI*VW|RPYwS;UPEyF zqM*w-1D65Eg1{x=P347ov5v^*e(kJ!4Pq%Ia44`gYUkApIIkj#fPG5^Hmq(*vq=k$ zO(Vnzn3yUk$HGti{;s@B*hZ&s2qvy%CyP}Vd0=cXx#>tX8z7*gQcE2Tv5qd^^GFkt z2!Eb{HQA&}k4xf|U5SZ~sTNR}P^eDzVssf!u(k-OY9;c{l@JqftqPWCYH-bvZt@9; zQrS6+x`ToRcZhsO7cpC~7r@>nzyegVbMj=a>n1J7E8ni?*a@hcN&Nt_S<1TIw;~b7 zPWo5rS*IG>?;sRI5huQOEoPf9xJ&e^02&d>ri01uO;QRT*o7kYx^_l0`zg5;%qE~q ziy@+5m70(7?H+u|D5f{wTd=W0e&E(>&jlZutZqnX2etC42^=`>KTtEMSWS>#z#D=y_d} z2+c)QE^t}EWsotoh08s6 ziY?C|CyvX_{pRy(;}MuE2PIT0u$EHXJ)f;zMKbaORH2M@ASE@D6uQ<%jos9UGp{QV zG$2dYuwL%E1fi~we-auRT5;}i$7D8N*xU?yz&Z*;Vjy-7+9ANvDIo?FMgv)l`us8N;F|`ZXf(z^C=Xy90%0wd z(@vUPr%Q{iVBi8dQ!JShD z_zd^!TFt#xq60V>PDvNotZ0Z>9Ex$ks^a>OF3+Ox$l~ z=#?oCSlZ`z_{qe2DXArHsKI|&+Bk7T7LFjW7zt!nPr#s;daW3a#b}$IgPb*@-#|tf zTp?nytD1!1sued68sBgeUaWo_39Dy~rQ2)Aek}@$#UMn)Y7pwaT$RDNG z$hN5SW9T*=8^)h8e+*>>Nnnuyvk>nhaosuuf@;L)6~nzAG=kBni=8rLj(HF%o_=XK^ z5Y0egxaDxMc zZY9jq1~3O)fG6~bgHRLx|L}^v7PU@8J;=S3+@6LS7eQ*>71SryZN$w@+M3l5hm)BH z(F3S|BFsnrE9T+9VRl@FdiYhSi-DTh81O2@LD~=t*ji-?I3X)fPXyfls2(RYs$E^B zkpM5E*LX>Cu3Cd_YvEa-A%q0mWrU%H1gqu4;o%64Kk664(UpzN(rQuN2TN$C3`NUp z7>8|*!#J`v3BkQhKw#~H3TS&x)X@QcfJ%Crg7xg+(-M4E;%Tw2!n!C~Yq2`Q@eM!) zsaCuC3VbJPN3yy@{T8n+AB!zvB7&$*(lEf9v{AGuiVo_c!e605cqit->iexdODGroA+d350?~=9iyWFRz@K62 zlTq4chY_{DDfoh_S4Cig{kKoD`T^_=(Do&4EHSYK%!8%qQ;h(UI|bhp8Y`jADGp#K z#-?+g&=KnptiZ1ACDE~{XWt;L?S=nLPd}@u%FH>o!HV3nbHpaIbmsC-H+ zBS(kVs}k3qP14s8639WNx)6vcFH8-{SBS1gz4B7VwIh%b1`j;}#XpTs=(@@&pJiO< zXo4ll@S{oGP(uBIBZyH=K|6%gH4BX`_%ve%o~)feitqT6@!6K#A1mHB32mPBpUM8w ztB1bEkK+=>r!I>38PO{DSBS1bc$qm@?q6p=t4owdiTe|TKh8C~}u7gw!gc!14 zXCG=lSks&~ZJIq4cWS;B!3lzjjF5(6cYzcPVF_t5B0swRfe@;@aiyi%KqK6mL4A&@ z$00JKSv#(Dac_Z5HmRu);j(h`L0230JC%a3HwT)BysqXv5RkKN2c_Pt{gOY7We@ei8t}h znZUee5PRS21fZv^A4}6Zb}s05_O;!c?J2t)Y(nsbTm7=j&Wmk$HHP2&2T$20>Ql~T zEpiYaJPQT;8E-C`gMLBWFzK@2UrS+IZPBW1Zoo3D?eH8>P7t?^h%>ig89VR>*MOZJ zA-aCN1g5oTr|x)%R%-XTH@!m^6!sftDD}AL+6tZ9D2&_~GpzN1^T0mmrBR~NnLWXA zUdtmwLTTLZgHNcK9Q*s*=-A(%@FTA};Ty>?LXz6CXNae+KH<~lg?7ph#G;PuKq!h3 zF_P>w9rCXVMZH+xe}tm3_$8xg;{0})thnViYlOa$)`ov&j+v!Cb_TT-E^;!cdl zu2Fy)+5VM2yo1~r#5WQ*R*`{-r8jVIWl*;x|K+_87$89fz#;BEHMnu;9?%v$VUt9G zhJC`bMsZ7rZ;W+D`JO>Yf~gKTUbqZWf6;-jwQ?XOxH(2{h6q-d0VOuYpzp>h%vFtV zgwPf6C7&SSNlG27nTR0k_i7%hx7Y$7cs^3yvB!&L^;Xq{5N=N~k? z4zt(DuS*YuM$Q&3Vsp0OjRKr%Pz04D!}nS+k##Yc$O`PT7V?8d>@#2d^-yh3&}N=g z_rC@*gXf#q1IMo&f#Vm6yG@g+D@m;<=_{y`V*gS4hWKi6e;G5U{(R-+<)EY2byx;I z!T8yGmnQFbki5f`h#Pe1AeTqkh(h%-cn~eh1`i|EVmi{3t45Ybbawjy35|3w5Zp8M z{82WHgcYa%8hIJMJi7z5{QOm%}A}f+rSCZKv?yysrX)wh2+z z6R(27w_nh3r>GxLzGxt&_7N&#ggcGDXboAC+An%G!3?_#whJEmS|p?)T-^ufkHv@N z`Az{h*(x&4WQ6DyP0$#aYfoq`1w?@(&|V6TENaJgER~)0Y|)0P5!mx!I2Hs8L`GW) z3}Q(L|2(tOt;@*2?6j}Tbk zJ1JG!3wDignA^}ndy&u1&JuHu%6*~YtPj)-kl@av-v=ZFnNY)0@G&284jYWPg@->4 zd=@gmr8U49L6Fhif?9+jOv%tL^e3+8jnfOO;Y%>u+o=a5oU~0_g3GO9qMgVa!Fca~>`~)Du^er@?U2P80bYLQm zBj>y_!H+55a}-F`YmvfW$K^g@b(Gk$4l#jmB+1cY>bo2Px8YnPw27N;Lyz2fNp8E! z4ec*h=ORolbtkhkr2JWcffkT(VDq$$0m3#CV5mWix(&R6OOEzl2RJ%Hsn>%ltKW@9 z2eYXI`iW(bF409#S#)3p?PLt;l-Q~Mir1F&b^}D5pw#;*u!nPV5Y0D(X6xAUJ^v&{ z$fOEZ`XE)zs04QGP1>z{qpGfq zdY(p&z$is3nm*!FmYP0)D^|(&g@k-7RBVvhMR=l+R%<&@d)uOLBe#A z2rY1B`Ii?l+7l|{>J+z2PCP6sqc%67a5c_O%AFzjEz$#fmD5Fk7%}H6@ZKPJ;)}}E zMRB@_7nR~glO6dWyu+GUzUMRKKvg=*&}Lsl?70CIx;VvlL?Q+`j1H*p1OLSGZ=g_w zZXP#=q>r~Fi?H`wG%QlLptICBSloFOkX5M6p6~1Sgi7$;QqK)1}tIsiu3!mQm32Krl$b@H4|Z$36lz2gO1 z!8PT3-XWszCM9Ug-Y*a)ij)5r(Db7#`Lux@IlrH`En?rfE2J1tr~$SjX)jrf-{*YZ z1yW?nU}HvoiS>rkl_2ZgOG7N`wilF_i(|+9{+AgKBcFq$om*%*Q?vbCv8cNy|8`iI z@3!fEj%_1+_k^TB!ri_-vMiquj{_!ug6HVHN_Dv@HvV}K3D-thc)HHeZv1vX3mWk8 zI&z`gUoG#{=~Dl$+`D+fm=U;V#NkYI>q`pNvCq)a1eYS=8nyrXcjdLkqm_>qkJFvH zL+M+bWM~6#VNh(_fJRAQV&V@dcP>dQc>W}i2X1nT)qP-mX^Gl(krsVcodzwW#brb8 zV&SX|n#;zq7_J80gWtg8IKo}+y1!VpD!(kb$?*_5W5nCE_r2;*eW+nA@F|hFmDRDe z^i?GWujs~i*XngNQ&9FJu|Xte^66Om`Iz;WK#nCFpEWgFamj^LlgkTLk^B9kYd$m` z--CVSDduz8iA9Ul3s`??S?cvUqAP(6!WWcZU!Z3^eqBHjh6i6`^;y*WpQ5GFrDVe2 z1999BSTiJBK(wj%Mv>K+|EAKv^!jVN@Sg3v{$<|W#{P9i+%}{N5tG<5hgW7*PIhcS zuP3w)P522+{v|*1wDXzQ`b&AB(E{C*5JbxOkwAap|qZOUQw7{tK$&ra}-e&JyNc}Ab(XK_6~8i8z(6~Mi7jd&bC;nFX{ z;-#mxnb=vF&KZBxHs?w3gLSfx$oy+L!L6iQxF1pt$Zq)-zfl02;a-gI(&-lrT6Bn+ zY@=X24C-U>r}%Pt2>l%4oVpS<1ZMuwTCCPTq!Rz_%4?Ow63*|#9Q1YCNYE=Eh}D$Y zqwz^CLoA3ZsoBK70p{5gy^kojEW0*oKo-LS>HXl4;TV#xv%RxTcYi4H8pGSX7T8NM zLe>2XX7kBqOOOR`35DI@ z7EcAyDD+2lD&I}Ra;7PNyj@a^wHCDe&8{r3O~R)nf2zHC^wOo4vf|u%#Y+ojE}Azx zKX)ND6y?vKmp}iOT;=Q9qQR@~ZmM3{H0`#U+qb8zNC;EzsDI9p(2&jLpe#bU9c4Aj z29(E8UO?G{(uMK~ii+|xioP+M8;vp*Wd_PED2*upi*f+vW0dbu&Y<+647O!+<4|ry z$v~NlvIM2ZmL116`>Hnm{T#OqZQWP34M$s>Vs04eIEtRrD19jU`#3HJWg1F4N(2rjUvb#7mJdBl7mu?vJ%CK@-WKNC_7LNpd3Z{1m$}a&V_kU66o{10=(3qxKJKN z*@kim)kVe?~mVqfAH1Mwy4Q1Z5=({oRM>W|Y4M>pSu6L^&C(|M!K0+5ewb z`rjAo_R_8BUSyflfo#Y&| z8>*Mts`FOVRhLkwR=WZ}QyZYQ1(9hjxocL~f^~fOo3~lkgt==7 zC3jP`ZFc>gcVfnZ6)UR=f!4CJvA(YQmRstps;>ZPP4n%xRd(BKORzO}Wz&j=`jvN9 zue22}Ygn;td4N!$ASz~K_y8qbg-(gJ$LJQ*V^mn~8jh>hDNF3594mDI&$HLnS(e#q z=PkQaTPjgeu&mLR+t5(o08d0PpvGROF(U}I>9^JZ%e3_ak|v#5Rn-9g1_=$4kzc)R zRj{SdwyeQsUp42pl{NLjWf$72f~@C;hX74c^_{DTD2-7xbY@*${cRXe?|BW?;Egqe z-WOH}dPLzCd!22?th;U1MfD{ss;Xz#E(7(PQrsUo zb^lj>&I&D5#@Vl%mKzELv~`yH6)SDk4a7rjQ~=W)ETrMiWwsUdE2&*M(-aN;!H}!? zsMW7pCIXpdD{Oi74TURK-dH?19!|QLbYrqN`E1IoX(!V7 zbbb2u>9f-3rEf|9Yx=(QkJEonH)f2^xG^I=V|GSyMomUTh9hHJMtg=oV^HRpOer%n zGtZGZFS97KJac*GgPD(KKArhx=H)ERXadjhSYfI+tuZ-FttJmhJ30NA>5=hC@pAmX z;??-e@gou^CYlqo5}k=+(&SV*?ayg1rgfwpPpeLMrnhBr*&MeHIC#?@Q>W<`bA$PQ zb7R7~gpCQmCL||jCoWB_Nt6>E8x#ML_+sMvqzy^WB)yRIPSOWSUnl*L6p}nRIVO2x z^6ccf$sZ(tiZxzHE=W0=8aE?x#>yF6XS_T^mo_)8H*H+{wDfy3+!_Cq@m$8ajO@&v zSzly*OACO`)8uKUY||sA$4q|HEb|-YbLNrLE2n#=@1K5n`d8CWPfv}X<%q9{zb*cW z_=EALge3{DChtn=NO>>ii=prb(v%F}-N&Hhp6{XX4Fa=9|m~ z=Bd-;r#q)_nC_i^e)`Dxvbgx%_=5PVcz68s@h9Uyj}J{SBv=xbB{d|yp4^cfnNpFq zCGGLFqiMeM+6-65Uo&3IXv^r#_#i{gxHr?2*_PRvc_j1aOnuh)tSMOuSu?V3CIVnK zUQlvdZ%l}ro}Fk(tV*m+Tm!lJC2?xf(Too>x-&k>IF<1=aGcIKlkrPNPsXJTE>p+$P&FwnZQ8U+X`vMn0xf-DEf3pTLZP)JyhIXA zD!olJq8DbI`S24*=F|Dc2O|uRirP{rFU7*Byc|Vg^oD@)P-qL1|8L)<$c*3m)_<-4 z`gg69dmj6ov(Mi9?7h!Er_PsD&JC*d&X}#4|Mesj`a8>vW+T{v3{(EXZx^uVU+BKK z4->{P8>CU}&=IR03=<2$)-?>Xr?&^b32PbV>1twPvQs%2)!?n~*DS2Uu~D___E=sHs?K z+e~e9+Y5?|JS)}W-^HUD276!eiabX88<(WoSR(nj`yJa$7{;|P(*ONzz_~8Q(J1;u zVKzZmpXcf8DHPH?HA3=xz{@Fe0^4f1%;#O!x4L>*Z(($R-_Yvi8VGBpUX*W~9MyT_ zTYmlCv3^54|6+&wb6bD$ple@T`P|1IZ}-Fr`a|M{hBLOoo*5kfPMJ`aCQcJL@t9a9 zM0(2v&RZjB#78*MS74FTfLckZ=TYwZWQMi3h%>m|ETNI=e;bQ!IBpxbdBu7&gB5+0 z$S{7Dcu>4r-tHNsZ#7#i7CA8>jR=q98xGk9cyg6F_R6zvOKZZ%RF`|o1RbqQJn1bQ z9|$5{m)}CG9DCZZw?0#U9Q}AtF_8RNj1t7tKvONA1A38S0SB_^6co#f6%;2cDBAjE z5`MPd@)2Sl2QD|6lRd1@K+kJSV$d%e3o0~Rw+#}n z%13Sm0%ChZt9aQykdM%x$q`S>zeAmruTFQ^XDdu%zZ93NLCE$Xugq9hp{qiOWbhNk z9LN>ov{(TOTBajc5W;|Sf>I3X)k2!9<7^HPaL)kV zz86EQjP#UXWt={MwbIhUFo;@N+Q{J2Y}p=KRTr)5p1W4nb@!_5Q@pu?BhtG_Z3#>W z6wr*Wj;=g+tLV4A?;i&`s12aSQo#d?7783H5(BnlAkHym7)y{iHf}5HS7F=iGY2;U z`T$&wsfW0_So>f!8ILiZ!ebn^R68UXsomAXEe*q0`h$9ms;ppJyINEYe2_>YhKog< zt4hsCbrHi=XDyO9V%R-@><$D69^i#1tl9|6XZAS-{vg|G?VaORysGig=9$wqcs8Nn?yAbaD z+T=Xk>OACZ)HLR^R+ay8G%_r+^xn1$kVHI09uJ zh+YBp?7E*3r*P%%R#gc!)|bj^Lc3doeUN~?BzRXt^FXv9$Ium%#cu15bBNSMvt)!D;@^7$oT17omq^hh=dh8 zqP)Ghs7a;BM>%*Jmi+$K`=#$=$7#u;DBYB{x#;{hwkW@2puRQC=w-%8Z^r5zL1ji} z#v&eVxMUkOD-gY&XoHOyFCJ)UwWo_+*6_LF8cq!N{gJS$;uzm=kpXEmSY!F^K)`9@ z?BP%bQT|9!K|?tsXFr17tAVng=F&2j;y`p6#sz@}1fu65m+AYNVI20y0F)q}dM}Kq zIgew+SSw>?X3w(Jn#NJej8f(#$CXNc0c&p5e_|( z*1ORQ`5k8H7~cH`AdDREszi^b2uRO2t=CWUj;is4+r?F9lloN;7Tyhg64`m`pn?cF7k3P4JV#9?B^%# zx5y8N=Hf7qZJG;W$=K%65C)lFM`~9w-ls*MMUE#>*E4{71^`juK&*c>a)ljcjyW)e zo43r&90*sTkI(TbxJRP^C=&a+2bO;$8lWnDd(g!12;+A&5-AM6W&5G03me&vY>xF0 zLZ0-k+Sv`!2YZHKU@QDg57rRJ=`i_NaK}8&+ z5ONYE;EF}QNVM_{gpkYa$XffBems_sAYt95(K3v@$|S8EUdL|Pc42s5mhF_T#$`J) zvF!GSKs%K}&b3&ETD;;ZRy)At1WNKj#S}EfLo3+g zF*1>tycuk9PLQXefA4gC0m5&@E~b6o4qf{V$V$2=5=4<-?Fs~DS&HQK3K|-CS2Pmn zuB^eef@3Z6qu*g23Odq?5#v-}eJgDlF;}(oC#g^TI90?Cl0JU4>V==A+3{;sMc+%G z#s7X>YdWzPnks1Pp#A1pRWeCK2^6_!5NS6H6z8aKq~?*srEyw8T?aK)BwtIHinQ~& zM1V@~X+ILWt}E1m{^Mw+TG1ybq6$QfChJG)&LI6|R9gPDVC_Aq5qsoJP~JBOOWMZy z`+jH!H@2y~1CrOnm%J*Ux=AueU`N12k5cW4PD7*o?OAF3=#`V)z{MiJoJM=ecl~D) zGV;Em@IR4tKcY`~({!#QFOm`S1~kqX1nZ=4eU3G%Y;60?K(r3(Lwa}Q3h8m}uov&u zv5}Jg-m#jXkKXH{556Vk3ilOkfxrf$o#+6arKx@kMeI|zQnvTm^?s6>d5r;LfwQk} zKxN=;P?biKP>AQm&meNAu#VTD@r!2%mU6ObqO?b7nR@dJAW0UbyWi=EC}HhhGKTVy zD=-YB;e@N0NCK zgZE?WgFWldbgqeGY{NyP!0+g3iDCykU7y@;`z{`SV(tl#QSiqBmk9Fs%|NKXe*ig< zJ5VH8Muayv9p-W$&L!%$v(XXL)e+O9`n6e^nZjEQIS%$9$qd@VSn=*g zba&n4FgkRZT5Pk=Q&u>q*V>C$GIs*R{;j(qq{QX_`G6!_j$IvyHez1?2fRbkWos`ca~NQ|kdF_{sTNX$al~$K5i8I821Aqy$?W&w z_jqwBSLJ#_&EQ~i;v8lv<~$Cy{*ae5c@z7)0)3a|RYBu3wxTN6>i#OmHl4m4W1B+X zaK@HJUzm)E^zF|$Y!6Z$*?)Nd zCCI;3H5cpD%Bffm=^}9jCswNufC!9p?LJzIvS3&uQU?Exz3iFHc%I=ri@}c@r|OR2 z6rd5|9Egq@NGZg)dJl$`t?-GVjA1mPTa@bm#4eW$daJ(+Ra@EWP1I0C4Rff&$7osm z^9>-tuXEzp^>dhgbd}4YO1D4C?}TFw&W`4H_H}hVWHY+D%515=KcI{2@t;-8#s}f< zSKtUh&F^dlj6EvUfu|1reO7d^k3n4(m4iSJ*<;X6g$mw1l?Lp3h<9fr1If|0NHrPv zIM(*CC@+mTzzGa8;%ENR;|p=lCIaq(se+al;7@49Yhn%dbfSPyBsPBZK8T2wA=xjO z4GFiQDDK5NVjzx)5CkPwIjO~=c8TGF{r-?Y+qH@WhHp5GJ?ZckKEO0w+6BQ2@cEs- zCHN|>>nf1Th!Xg9TL6Z=!a?X`;cTjC{AG3g&K#^O-O4>~udQ-D&6U}!u>|51Icy}R ziSYUc(F$-Ngw7s{EF3`It$0Xq!r*4PM>%M4m;nIk#Hng=5_bSpV4NsxP#-0KeGzux zZ&2X&G=66U&FTkjBV0XU_KEsSL~Hovc8F(pnO*DJd)w8{0gsP>2S~$N)sOq@0U&aM z{*u2MAO8~&PX&6mtIK3_SgLlHfiGqDZT!whxT;+!ben8rT-^`y4X*$bS~-DWjp1eX z-?-YpJP(5Fg<33F7YZfWC$@q9HT1?-6u_kuwiE=!+GNaAbm zt}D1KX_7Y${~gSV^>QggG zvoZzpRz!N>Is`rQ0xLT^5M2l$Mg0aqQb@1{Hqs)kOZn%(RM`2DFD2i1Mp`vdtLi69 zn5M{bP(hKwFxyKgmBWnFNyppg*wc^z zkfCK2G8XxVV?^0*l0yvQ?Q@)(ue1=9MLtfA<#VM|hO}X7GzT3mgu>v>v7Hv#LG4mp z`YgxGV6banl%l_A78+cc#i9(%%Y#drLkfEr=69B=BWm2oj(w(_C8S zfSFS<(t(75j)0;thj=#$7Dyc(8WhEO+dpRfYDICT!Lt7OJjBX|z*ZRoRW`wl9UF_U z2I-FWEGi&9x*B4moM8LkG4c-?ZrDfMQI__2uW@3-^^l6}a`>Kx5b*2l_|3rCcG)nT zU-k$a2;FWuAM+l_yM=<&ukkhu>#6#5v8?+C)*RZ>vGN4MZcnhW3+~te>xenK&y$Ti zn*izDuC#dF$^zjiA$;SS7Y7wIPs_NZI^$@E&VmJ%709=)VAxV;7H1pi4339WBO9iu zEuF`$U=2u`=HG# z?h!7c0WJu&d-hZcoA4D+hj<{Ks%981IiqFr$1u7WH;l&3fxCa*ExZgiLD#VnKxjJWDN@US8-r}KaF9^kAbdm^2JwanmFcfje4K&fq=Ybo5`~Ew*F3mOoi|SI zV&IqzJJat7V&XX`#jnJZV{a@=aljClL7wy<1HD|K0xX=TKhCy!>siC?rRolD5Y8k1 zD{Lp_Lj2X3LFo!ax{r-L={m)@wkT|%FG&Mbq<4c-Gi#;=BANi#aTjDTLdD?uP!4eZqi zP=KXkJG}Lwq-?9tQe^;2f&4bN8Q8jRbNsX0iS8Y@ zuo){{-=n+*kA!;D|NJa|59+H#Q zLK#Gv7$-We;BXa36Io<4DM&g18E<^WuZ^IcikLfkMM8253EC|*Vl_$+AmSgsd({f8 z@XxfuW&uCXU}cbN0s^K=SGoKY9jJNtLL%@MWpmQ^O~62FIJO8|DR%(-4j*i5Y9yM| z_YL}@iDVPqsfq|r6pst7z#D7@5N&__sStd@zSPK1z^VI1|AfT!`vK_fR#x*KsbRbaEt(Hn=#5;n-K~sXFzQ2O{ru(I7LN#{v%U^<&tA%Rpj~Yl>9b_R2-vp#P>KjqN zA|MeLh^~XA)4hy+V6O`28~%c|7;f4}-BIIwLpWju-Z)c3fK<5y?(Runt)j{ok}59~ z68LrJAt^v1O}ttl>rP;8zGWB(q|hONlO|qKlu^V~I zvCY}mgK!uBVwSk4}mGTf_} z9~rmsdt@9CG-7>Yv+y>3HYn242-mFfP2nxnz?nWBcJ#3n8j1hi=_ z@{^wvn}T{&;S@PuPLqmn(4&t-A$NXAv{A@a_)sXMfkgQlrPYkAITk-C4$4UvuMe{r0pKE#dFaqj& zkaug40V0th6`+W|MvnQ6Xi9tZsvM2T1H`%72*E!GRc!3XH=My2R=kGf_( z{4o_z!0R6&wuwEkdn}rC@+*hwtgk=hZiR3p*y%i}fq`lVB9o6a9OB)RfDH<{@c)4O z^p6UIWK2Xgu%M~w(k_IMmZo+qn1~w`Oi-E!7%0bT1+Qab2iqooGWG`V&P9cHW&nT{ z(yghn4^^Pd@0fYp+0*)r*<1QERz_8D*buBL{n>f56e%(W`Kh>nSs|)MQ4CItxEb_Z?z+zt{5K+jcZw1Qmj2U{$;OdIu-8bMG ziEU=Xo{wlzcE6|jBcd@5p%5~|3Euq~3_ueX(-b$Tai4SFht6B~@Y2v}_c{WKHgv+J zRoTsJKWfw0kea4@O`)+6z_QvQ$u)r?Uy3Fk#ZX;1ZP&b zeHrdWA{pM@1~9DG9LBpSs(6)mKm+H8_V?W?0h@q!zIX+CVCpZrVG`+v!BnS~RUd&U zSx|+_4ON9u&A_yg1|`3tj`3xJM_^hPDuSprD+Gd4i~c_JbI`T}eb06U z1}B9d(Lnr@!`ne*#5AE>$pi2$hr4}e`K@3KxdFceu2_EJoxNKue@=b}=s8QFC+}_t zW`-`_wTTorUJEouvd->H4!&UnD!loup__LVW2=$bPEZDSF@nQ2j!qPMP`Gd5%CD%^YlD%D$%q* z(j-#4vq7Yr*0}%>mtOj+-3X%KffElutD<-Jd`m~ev(Yd-*w&P%>JsH}u zj_!2!E;<+&HchB;wVmfT;02Iwg_D9pH2^xO1YiqVId;3y9SCn?Zdu zkpzy}A}l-3rKO`eY8cl%hnndyN3xis8uA&5N~lc@K?m_esTqJMk%x()wfa`l&94B2 z-x2S+3ARYo3(C7`dYJQOcq5c1Jd}F~VCQtU3f>WIM8%np^sI>mWRz#R8oHMTcA$tm z0S!J{eq)=v%`-g;J)+RVW>hE-E)e!$Knxzxl)!x9L~*l)LMB{~k=unq7@Yn*kuPiJ znHdl$0na=FU;kgiIllI6Qf3QHLkItN&eA-vT>Q?UtAMtdKmiUHNo1ACHo2AcIeYpv zW~)HEC~Og04X8yL95n*6r2UcAVm)LTQA75q*S73Y%#y>V!{~~j*Fn(1=Z&p?0}TLC zit>*Pp`}owK#n2I^sWBbmKu^tRY|DXBH#K7En8U+*x>4jgNXP_$(DnvV4cFj4MFZU zD~trb#AMO!P^6Hyki6gn3{i>}5_-}_at>+~StkY{(=6mG8Th%R6e0F*1=#0@o6a|M z;$-0;>g@iI|IL16F?piX$fC3pCBp&!*>?a2Do|hFYvkD%3XW=Tkj5c1u3RPusfeja z{SwW3<0~Pe(!KJQptphEM7)mB?XsH=0Kwbk$B+%(F0aJ}49WYlDBwhd)yltxQqDx3 zgIPZPPGuP?VYyPo&+$ZPIcHP*$2nJUt*Zqbd|je7{@A(v4d7Zih%qW+G3$qlUgJ#uV4$K zKp;i(Un3Dyv1;kHQQ>=vT1;kaF#uCe(4Yl@S>z@H3q+UD8LPu0-xEbooA3gSy{r!) z8h`)>3>{z~00{^%H3aaK4?aXgz&_aLMQsLd$Aa2cyfKB}F;XA!7D&yL?r~hhD3N}P zt7n*0NGS={=ZIrj1#e*8$4~EmNN*`C(X#3D?m5t2~ zEdW`{c``hY;ffxI&sRZHaI_MRJ2?X(g zJQGv|Z!Q8G)ac}Fl);crLXR(Q4N;UI>lRv3nt!5K@~%-}RxdGxV(sejSQ3x`Vy74)`lBzO^6Xb#DZ5UWErK|dVn*q&L$4f$ zDnntYH2|=K!HM4p)-7}7`QL(ToW@`W8!-_1$z}-jV~`3jLNF~PBo?`I2kp}<%DLdG z{3AT&f4jVShX9~I(x5Yq;GJ2K^ed3`&4_gdW#+29XD7VUWmw0ZDe2}hG$+Cb-aal& z*Fuxi)_F{s^%VRo*RN;HQf5T_1~aB9Gt$1AK(i_OV4gmCUHJ!$BV5n0rK%kfJj-b6t4^s+?1D#^W zzXs1<$ApI4{}Mcl{uY!^4h7E;JG<<&A%=!~5V|pjj@R((uHl%6uvi)%Vs{tGnXrEf z^+BOn*-Ys4CDu^T<`6-G$DWy-CtJ;Sii1d%B8*z)ny+T$Ts!V#^EDZ2m=}APPoBPY7{$(l|Ln)IKq>`-n5358*cO7Ki#I*ZJak7ch+I` z{p7*e4_I>86#rQaM(Tscr=3JuRFQ;37~djc3~K%s5)g~5-5!)ja^G-VQylrAME1f3 z@5s8h(egtYB^cWEY*2<(m!rl`DbiBLec1I&rf+m26ZFN5uE` zdZ4-kIdt$~zzXZ{}MHXES1z^z@S-##t)F8jU=Z@TYzC01%<~V7W99#_{%<0w=sB0YAUC z32(nk#DpEoB1=ZQ?RBV`5(m`uX?Z^=jVB*v3-G!s`puw{Nr1&B0iqoPZdT(%Dq3+e z=b?anujVEF9@Oy{gMBZCQD0j&sT^;g)1($i41SH(FvqXZ8cb*@Sm_8WWnk!a5>wo$ zoW%sNAbi6Opkt+gW?CTnGAaqxv49ET@^k2maLUGhu+NJ?uF{1Vb|`>0S(g^_c1UnP%qCKb@>Q!3=5 z5Tt7uahr(yYr5{+`tRI+AKijvJ@Z%Wk#=ln8U+{VBrh#A)~|cmd;J{% zdi(qb&QQ3ae+eIpEK!{&S->_^i>F;3VMWFAl|9moqH!?=cY$|@z)kN+&le@AqTi8r z7maa@1m5k|lsUWaC5jnfn~BOLR7gaD-Vb`{cyK~If(Goqm)-_B9-wXPBA{gaO15UdRiv@2i_9(*O1Hl+P z015Z7GXoYi9#=FRviFhKV|VF~(?dym{2{jaU?T$!$2(ZYrq&;yBp-c?Rxo7F?m;9C zE6Cs*N0w6h}|s&Vv$?klA=nqqm=)t`uFGSe}Lh^dBMXqtgqZQtAtk#kt?AZzT2wy zJGRF7pAIu_++4ZMHYcC$SA)R<$=;|n(*DPE+P;4Qb}%4ncoUJ(Sjq#85C04_@c$jS zN7@!$)}d0FUzh$-e(&%hF02bj9W@Yi{uh*5kH4RS?c1+Q@pDH=#<^P6YO7Q}cVc7$ zh(uwStr$bvJXgmiO2_7AOj`wgT7rKr!lU3K`9l!8q*y+3lNNnN{sfzl2xNwsYZ1-} z{@cwJ`BQk+XkG*5DSi%Ht32W0PV7_VAn#8X?*_>ylT4eE%noPHs%M!0zdk!#3xwEm@JZA@dLt^^ z_Dc)qk7ui;-^`CuU0|gDnm=Z80vf#0HS59ZbFQhkU@D4>WD6ZON}Gn0xrh_LmiMDf zZ?eHEJiXNP!#6f52q1)|TBnA2bb;1#>}#RlIm4yXX0@|69A zjXmeDtiJU!zg4NUr>J~CN}4Bz#rE<(Ja`Z34fYA_ld_)}67dcc5dMkdCB$Z>`A<~5 zTfwP@o*8du=roNj##}%?E78o%O*BhkRrj%R+s0N64I5;Dj=?PeGZcy3p%Q84!Xfuu zco2K%Av4Os&NP5uk04f(v}U1DRE7U|tkKrnm)I6AuGCi6*VopkYaf1SMsK#ZuCji< zZDH*aZPomRHI)@wTdnqq+QoG>m9|Rl(v?e|sIISFQfpsYvl3?_rUfWGg0J+)g%*`& znRI>OM7BnH!1{=^(OS6enl(9GN_grG$FzDgQ--t{$$|7d(kn>sBJD#uhV&KEk4Vf? zGZTTNMM^=+MVf_FiR3|QNBRQk8d3mBZ8I~2kwzmKkn)gbBP~E$hP1|JevtV+GOr`; zMEV%%8>CA}VZdL5G#u%EB>LMUy`ah%5DT96{+*TD>Yo>S|2LIL{UFj-2hvjvGpiuC zV9LBle@Z!^jg@Rmg;|RhnwflOZ)b-2u_5?>sU>&SML)+dX=@p#>#n-ds5^Y zGtwrccab`fP9j}Ex`h zr8YPimYq*^ghh5mMLj6NFf&wnj$jpv`IYnQ7-kUx71-w2+w64@FR7}{W)=r)DnbNj zBq|qHF0LbdG1WO0^~|oRseOX^PRUQHuLMahDEV2H!IsrwkJ@W&3n#6#RTkD3FRZA{ zsh(fYw1vgZv{%-zq?zg$E4uRz>zMmFDSk!F@HDOf^tad7S1z&TF02PrYwK5H-Wgnh zbZFUd$1G|mZdqy zLfe$u`htZ^o~)@1N*5E`r`S=y&{mmKtH?qzvmhjF#Sp35<&#%rL;P|pm)h!UR|cz? zNyNWLLGSrbLXucjC_AYZuv(R+vU1U#Mn+IsYM1^3%v@B3ka&0rB*p&O%1)8*)v z>X%HMmWc{2Od&pFbPwx(qZ_E_lcSP%r({jsF!9TYLh9<&=ThHI{V+8lZDSg1&>QTA zk8vY)nY!t^ zX59weUmUu>>W=9Ct~;aqTKApq2VI;#QLoq6>X+&Npx>)Mq?eL@pY+G1KPSDLv@_|0 zr2Z)nrOZevO?f)yK+2klpHB2oD9e`Bl=VW^Ow%0G3X{Y1g6R*Y-KJL4KTPLMj2TKX7Z|ZRH7H=xS#+hk<+?Sx zLHgnPG5Qq!Yx;u|;*!TEmnAnPzmm+P)Tdlc8Id|ZH8u6&)W4^lO&emUFf!QUFJX)- z-9PkQ`iLZL(uAacCjFFjWWr0yJCmbRHcb3*;=YN)QsYyXrmjdmn|g2Bi)jbaE~aG~ zDhy2ygJgKiu-(9>ho{G<-?oJ<)5u1^ak)Dxi%rTZ5t;R<(H)sAS^UKWh znc6Ho70+^JZOq!9wL7aV>$|M)v%*ayOnIh4(|pqs)6=FV)AJztXC`JI!&CsVL%L4g z2)&?xPA}=V>-X!w(fjmlQbJN*((! zHKrSjjb+9K##-Z6;|}8o#$lPd%*mN^GpjP6%3Pk=n8`p}Y?n%#9*X0VG)YlOu}S8n zl9cL{)hW)D^(h-t)D!1TdsFQ;JX#IWahn=VXH9?furKH${}19-mRkS- diff --git a/crates/uv-trampoline/trampolines/uv-trampoline-x86_64-console.exe b/crates/uv-trampoline/trampolines/uv-trampoline-x86_64-console.exe index 5b8fa6acc8d7bd0313340178eae82fbd8c5b47a3..b93c242e7794a98ccab1b07fd2defea233b990c2 100755 GIT binary patch delta 20553 zcmcJ1d3;k<+W$?OmQW}+6~ev*NTp?MWwEgZnl5l7w@_qN5L#$~%F@~Zs0iq2!EwAXE(Nu;Z1?wl&b=*lKJ$Cu-|vr?kEhSs z&vTyhoM$^X`VJa>M~$0(o!4jNE#mZl^;|!$ov{PQEqUg-vLUH5#}!gb9mn~i0e7QJ zu=!s-$DKyL>USL1^O?RKdu>G~5f%B*a9paPxfjP}Jj-#Fa`l=Xvd>^1mG%f)EJ#a` zOm})w&W6E5%L)tJ1sr#@7b<*6y^%`gLxurKPa)UpUk275TB`MChyg(^25Gb0!8pM1 zoIDDDx5{&j1Cm~7H4NDd{5Q0$Xi3R@Z3`T>!Woe&_q<`8VK8nVEw?fC@>W%FoFG-F zoOMa{g8!&(j-V!dg1Yzd&pE?6efo?kg8zG)D19VIAG-XF@#XzhYp~TIm()b#X3d@B zoa3B}(SJl^u2k!i&fOns-$wb!fdwk6+}WSi2rh3_t#`LtMYU$26fvfJE=cEFtyiPtCw&a`gy1TRkmm511?h@% zL=Kn)AQ#2eYn~wovFkr6)NrYfqA=I<2;J7I8<1eOEVV?$m zOO+5ZRnX}Pjv9dslR;2ViP8z-+IgYxY2n(}^1|q0UW}b;Pfj@*u~H`0#qT;H z2FIC2DYIUb?r#*NSRu69o*|+!QIMKM>6A;V%9YM2=jPGUK9b)}ZXSrA6eW{g2xZ&Nk!V4x zOF1b>W+8|w%-l%~qF#{B2+|QuVF@!fq+CsIMHOFh;HJDFW|(1v{9H^Q?_$>>5RU%& zMCq!SbV_g>DVvUEJpYke<9|!7*)7z%aEDq6xG+L6%iIuUaCaawIqLbqJXKZsc7ik> zh}|2WiV{0yIl0X=)SkLe9|CCnHL#*8sw$sB&R_LK57vh^~*Db;3Tt9(ctdw z+-Cru*p&-p-Ev(8nQ8>whKkQUo%wd}P~!pmgHu!1-I7n3r`^?k4h9&$6r>dvof+ly zc4@}X9+p?N%QIekWY2r;e8$-sxl*-DIxR@2gpixg{)$qI=^J7U9*Y2CCJRCE3ns{z zQ-e(e4MNgs(NV>(du$fR)l8>*C(!T@5751J?xVjrRHYKzl#4nHY5Ni~n=w97w(%F` zFFRz(i!J>OhvcU$Jvu**Hk#iis7G9UHu$JXDZahuuq8jr_`65tzMZDa*ZGP4>Q(iC znaz7e-%0pQ)cKnR?i+||KC@b0-l<==Ucg%nK5m&1YCl$pngD8Fhrm!aPnF;7WZm;w zr#OS*g+1R`PZ^AN70PdRu^W!c=eo=?E-RFWcim+8Voz1q$p+&qO>)QXJ3D=I4QIBu zQ80X^@#Gcdvqt&j?vER~$&-5I8P4vh>@m~eCHD6h+U?xh>pmb--bMslCPe0}z0LvO z@(xtITRL5>#I&!9j6p4D&}#=!ZTt3hksj11kxF;j+qAEDZpgUFn(SlS`%t*WM(c&! zzRP2Mr%;`z(k-^fDsL;z#C^9{55qqBLa!c!la}HHs_Sfm|Adj~UZWj~b3YwTIQKh{ zyy7HZF`_|LYvgBpi$=#n`Rm>Z#=Z@5Y@dAr&u8w<{D7~ji zOw~FHD&r92M>O=o1sD8{swY8t=Qi=>k#ICtN;#X-5Wa~{0%Huihv6Sk6hnpfJf|Nt zV`ApAYF)XQ<1kO0YF&=p=*6zO`59Gwe6~GR z0&9vAI98Mj!9P+{%GsQhs+^RQs#O4bqoc5vrx)rE&|uXXg-Wn<9&6wOE6`j~wQ?qL z-^}m>=)v#JGLZU7nfE?}d)gCKIHKiUpI=Si0cjM2Fj+@9E9v#Yj?ZGih)|mFJMlagGo4B*L_F z@_S>1P~4SiIFG&U&7z}edAki!n8gsjaa(n$8ZU&b-5?T3D6Kt8TtnJ+HtE}%{cTYE zAPEy`%6eb2jpLLFg50~`W5%b8d&wn(vhU7nq z_VA1=#JOemG$W_TyX=n}2VRp0Ck!#({865j(BH^klYI#*jT3&9uP1Cby0hg!-hH?6 znM?Apn6C1dciYAWqnDL(T#*oLpTIU7G*t`14NSg3^p6sP(f!a)EbSg5gakvqz`q&C zqskKrMh`EQe@f~Tof?e(Lx~(SpiigAN{FZ97K1h^XO)B5(BYrR?FM$2rwr(yR*Xh} zW0ac@M$gf*e8moMmT$FvD92Yw$STnwF=@eIbQHP>juW1a!Dy~Tese$?#RSxj0G+zbFr2XjEj(93tGPiP&U+oF)fe*yhXHU@bPh9 zqLz=3uBAR|^gaMJ?sfe2bP;N!pGAiLVe%K2$~}?>7xVN^^QIAh!$`;8Hd z4AM}ZK_uNOLbpEo4Yp$Vio|z@Aaxg{$IJ`-^DG>-ef}?yIXKnR3wNO$uZW8E+O0v+$NS ze0)sge9I<&V~GwVQo|rz;J2>?0ddw($nOmtoRfw{RqJ2#F%M)Tj7RG|3=N38O0Ck< z2Dn*iAu0N=@G0n=HS-N-dL3--t@(2LAY1(BS7~<>o3-*E$H+?uz2Eici>jL4_EvV= zZ`K|o?FfdEzEkF0ktYxCY)lBt#e;_!AGj!Q9c&x+0Wo)Q4D4>q(FpU_c^EYu@2dcC zZk*oNxp7=S;T_U)GmzxE!95K*@~y#xx^|-uz9;OsAsw`A0_Q#wHp&@8CK>)FZyqwj zdquTQeHio8tzQ7!Dknwt-!gfMF|0r5o~K&hKCOlmXC!~%jp-@+Z1 zze?`neFvRXkQ_4`&{U4QzQn%Ld5AnsLAn)57n5L5i5QRVrF=yMLO~2Ju`d!F7o3M_ zxFCsk0Zt%1qt*6OQCecp%av}6(k(JL_CfDquhMt9lEN$y^&#AY<6wnY)q2B=0L}3 zRB9IXqZj@2F}DdignFea_n}kx$}Ls(WMi3Jj;$4mWqJSYD9@;r&z*;funYW+v-t`* zy2v%n_P_?Nz3uWJGXaaU6FBZ2x(bnEt#4#5ptrf}6o2Db_rwMJXJhxXJ-caY(}H~{ zHH~!-@HY+RD_*0n*j3DI>g5Sx?wgp~%(ffu46@v5Eq|Ln00+Xq7EEjy`vhU4X2sZmk> zsi8Rw`i~^CO-KvatI!2*;NF1!1akhyXm=ZbV~oedw@dj!40_+P=QB=+pL$hUiU|T$ ze8q0;o~9?J!SvA0Wc>|?ci%yR!`WSkq@n|w__@D&?T zAmb3WFzK9eiG{1 z{xprqjKc4f4HxKYykf7SnaI6yN!jVJ>{`q_3*?xSkDr>3b>qc!EF4c)=a?Yvw4b0s z{@T>YTE>_tkoxz=szee{5bn2cAO|ij;+j2x$d?Hnd5-DO*eZqj8;c4DxOOUR2u5{d`_114c zOih?6%4)O;!9GIJYZm-BjGjlyW%w2P0nsdY?9foQYG0GV^B`G|`LG^wJ8*TJupn%l zGKP)rK_lIX!JYPV48>Ly@AtD6~o z#cOAZMCwibNIBT}L>iw+GsVLhik}F1IGxED5f$muGBI0vA}?E7Fco@(sK)GWEq;*= z*+j_G1zbd8sgxl?$oo*uxB${07~1agiw{pD0fwf!j6f;dq~+~Z9|4?2 z=Z5`}6H^E;m*3k4t3VF@$jOs3dwQ7!3F^aiKG_IM0;AGn+ym%l5eJZ=jo@FvPIfA> z1sNc8_>AWH8#mc6qhjM0cC9it?zCTGgWMDYgx9-)bOz2lpU%6K$e#j@VRs@*mTmWv zu+7^b+8^40s9(m0CH4n>*NvV$#^0m@Yy!dC}3VrVrsK@UP#UDA2w z2I9@R!Fyf7x#nEQzubc`0Th>NorEQUN;EmPX5~3pcZe718~qbXqI5%Njb~7Te@kTZ zA5FazBb+|NC7p5M+3DIvkr&P!lApeJf_KBQ7I`O<;|r1Z9<)$i2JT`gx*R|$LKnes z8zk1l{ZpwTWF4sWNdTZW5Cdz4@BPmcULMOPYyeO?-I>s}v8r1qd z2|%?5))j&yC?Mro=!s+;$B>(^^?X?G`B@Fd%dez%_V(1a)R}Fa-e3spKT|W~C6S(x zs=_hAw3$7Dg7PGF2wA@x!-#tUfY^oDGQ1c~|K>9#B2dC7y=Aum3L*W?({Ce(UQW2r z;$_Y!oX~e6HsQ_x)3vsddGmLP@GDN^nlOJJPZSYnZc-iT^-AnYl#VDH-vb^&Sh6US z?%hH4j)?Oh%>7RMHD1k;@XlN8g?%m^(_H##$Rl@; zQ&JGx_w-V&S*U?a@9C9mP;(tWm#;H`xp!~*=fDYT5^czJ)mo$@USOQ`ciAly2T z7ho1m#6ozJBq3Mr#2M;Ke67@8$f7;G#oFlZTs!}0JVKS#FiH-PjiyW)Kvag+fx%9H zz75LiAX^0WLvRa(zZdLVYix2n3s;Lauw$R+)<>d5_F?t@NDJy}K;J+KEH;z;?@(V0D)`R*6 znwRKwX9;if8e3r%2|=C=;vqc<1H=r2(=E#LccTDA{o8m$235mUtXVx6x;7vJB^O>hpRDMQ-bk2p)j5MU)m833DM;wQofVwI| zBEXD1Qca}V1q0-)7|Jx@l|~MZ?&jUfFt{irA>R24BOVY7!$e|w)|*3I!Usr%(^PB< zOhu#cy0v*E@w;|l2Z$2|#~HX6r}%hS`A~w%q^uA#hO)t=cV4$vVp{EW%WFjg8>(8< zP>VI_07u8;dkj7h#4`G0*F}V9@z@nhSe!?Xh?^9D28KBC4Mg>~Hkbb+qjr+Y8U#zo z`(dmQic{&<&Nr z@27YdZc*ka+@hDU1Ld>+n3CiPyAhCl*IRb%z%gmS-egkcu9`G3Yx-`Ru^9mBo z6cKEYuarSx;>pfE?2gcIIgaxc&$FQv01Z+A6n&A?PMHe%sD4R+ZC8!|F1NdMa5vCS|!B$$COvWQZ5PP6+8@9s2V1~n0<=!t2 z5%Hm_v~7_T`t6n~z*i#-+2+P{xEJFv8A(8!d?I65FG4O~Nn=qxayf7lD{kCHNCoVQ z+~dCPULiR0Mg{_wboZK-Nylcn<~&HzVNp63xlbGvU@j@0D6u)Il()fy6tQ4soMb)f zU5+nAK8q=nM%L{c_89FXf{FG;km%4CBpJ?0wV;=`gKFJ`^$9U=8@h^(qT?9FhnJ(4 z#fL>=zLxl~OH#tqfGfXl68`2oj`HgUQ3s$^$=187bcUu2PsU`#0~&GRtUyYr)|W_k zgXDy<;2(fz2r@ovMIWi@Z#Jb3sFM>=`M?lpWg;#c{+sh(gwCq< zab_Ce*{E7c>rGuvZD1Q8;Bfvp7M%Qzl#|#yeajLnO6PJb&W`iMMONiHYTRy7`qHJ= zXmN{vD-s3%<*VhLoek9n_k+$i4WjfpW>R5cd>*MrWTT%a#_79ahhcF_UXNWFuG!A>df#T)|PU;2gv~mV=Gp%3f98g(*RN5uisLe1Rf5 zuJRRcP`*VK-YZDZfAj?nq}ap6j^s-t?8ZEP+)m{fC$b1Rv>_tv?7E4B-0;6vXWLH0+V#F%fB$6q@-$n6vEcoFn+Xw5ALBs-NGJ2V^g7mG!O4$8sJk{V2JbgqZnI}w-1Jp(zi(PI0PQ;TA zldf9nOPHPIEBZiL5xS%y6+1&0@n{(1beY%+kUofAz~H&8^hA5g$&|CIbwE0NCHe*i zZxDzKuHcoYyJ)vM6Jzm=sM(erK3qZe1du>fK=XTB>@_|qgE%9DcqAhqe>lp(k=?}X zQWOLW6{;|p3O4-l`Lsetso(pGy%Gf+l5REwQ5!h+KwIjvpVW(LoviJ4BxIoUF`_>e z$RKD~5#fn%v>iHqLVWVS56sgB-h^4f28#N?8Vp2V2-*buB0PSkF>0QpZl7WPyQ!PX zS7Z@cYoS==D^soB-GtF$*k4e130@2lgG1^8gwT+mQXnV%Y78HU$B@k6wu6=ECS7o< zpR^)xywa~Tk=GrQa#A`H8A=}vHYtm+DrD+FSj)9guCN@`PjR#ks>adMMCg`s)~onX zFA~&}Bl~aBBMiZKT*W zZuyZ68=9HK0?mq8G`oe-a0WFN7NlR5)o(*+=K$ybAB-ky7?sefGYKQO30IY!?D1H~ zXnQi+($PjXFkuADyuZN*IFL|SK~}aV60ht9E}z1WQ^wNihT_%_f&vGxGyBBHpa`Wu z<3~(Onrj(e^+BL=bYpfCmSL_dCcL6N0b5cCcA)PHNOLa|*joT5p~pu|BRH!0iZ8$n zOhGEkadIg?+)+i!L42!(RowBUR(5FxzTz&ZB79r6nbce;t}h`t98)UKb)yp?UysZf z)Z8(ZsHT!?ytv<*3frOujsH&lNjcCH^#_U*f5;;BCti{DX>LXf!@xl7{n0)ilJAU6~=75 zVp8Oj?z(6vO@U8b-!=5*V2g1k0b2h&R29jh*PzG9WT&zga(fgP|IVWYMbbF97SNtv zJY5_^9}{2kJM%hV3ZTX7xfaEpT69MQo(_71Gv^NI$4n~hz32kRJA4n)$qdfY4*Bq`Y*Mqz7*s1UmELddlH`# z`0M(3BRtBylhxOa&1rkWSzQ<7uN#})whAkb=D2u{51hhIQqBtg>t?qV{05?D|>PRV;-wY~`H=VV`-z@w-_f#h;`tehj&i;f!d zC|*Y`^C*1uL}d6}bbRgNv%Wx(C!CAz@ax9oFG5uOy1CQ=+-<{nFftj6;}4;V1%;Gx z$4S4X-4U!_dZP-QCnRyr5@`?xngBC4YOnnyn8u=9U1#Cn-eez*Y};y~`jU+vRU9a} z+P)ut0~3&QNM;(uB*b8xGXZn8M&t;BU_`LTA$YS8Cu28|EH5B=UD<(m3x68K?5`N! zV#WOnHg5Pf)r8`{g-%B2>yu>V(@#os{2WGS^{D zK>vJn1}oXOw~Lf}s9Vb%j3n0@@!Z*P+~9fOjx#|b6(pzo-vO~l!9SLB52uI9Yv+02 zaSRZcn4sBdj*5JUpbUQRaaZ*}&91gbbERu8`Y`Nky!F!eArzJOLiQPVVUrs%9A;`y z4B-#d#g_Lc>0;}TU?OVV)|7@w7tkP%XmB4|lvS|Kc33j_E%;JMi#QS$-+lvFn}5ao z1zeEbWZqzxdidbyuTdnSpTT6?8BCYie=6pq1EVL^ws^3h4iz?Z2$y5Da!k|FO?RVP z5-l5rWf%Al+UJ3w9P~PvW)ZR^n=78B3@#S)&Sl#Svlom zxl| z=Kpaw>WS82G*PUolwJW#TOS(&EglNV-|PowN8*&d9jGq?zHAJ31*kX)lS}y6PWrgT zRMd@1oCOJ;Gk>FCMU0@zZ$H>8mJSs$X~pxV{N;lK7HWF%lJa+SA{j`{WN`T3Cvwb! zosEw}FArgd5vzHCrbSR;?Cd|d2oK@>spaSE)2Wl$Ldd3s! zpt-K#0^pyu7_D^Pv@3rtZ${Mir;h-!CsfY>Lxzr0p}jn3;=JP~kNY^?>Y&`^t&p0Ullk^&qay5QLvYmQ|;(oCKFBl=5a-coK z>liA+^adnPo0VXE;qUV5S$&N5L-LEWdZg`pl~{aqM{)>53B2YPf(al;lx?hS34O`^ ztKdHtg)kmyjQE^O3M>8pF8@4hpk)g6VQfPqa$c2(%pPg%@Ty!mJ9)_AKjZD5y%>h# zsz9g%B^d;gvbdE$Ti2yG3XyiQZ^f)#uIs*Wm>6x(QeK$ND{RkPx7n(h+TJs08v!6l456|tKgSLPFJy^=ia@w5XuGye9q)I*j zSW@{JJ%#yrGM;K5;@pRwb4t@cG@-&c_K0$ZdW5XQv~dIVaqqk=zda|l{W6N;v7f#~ z_uq44j@4)z*B2Ad*Plano5K>O+}%064gmD5v&qQ?JL0p=qxXq zKRw3fIDy|l$w%j>n@&KP$)>_J&2{SwUo>8V1l=m*kvVMriIWgq64@RiwE^Hj3 znvOQP!@{SGew+Nqg=zh7pTju)am2aXpj;+0Ku%b%L(t-`a7swNxv*>R^CVU2{k~eU zLT)*6)}hseTNA{3JNcePlR8d^LL$lI)22XuH+jpVzHOI&N<*(GF8R=+)&jxwxmd@9$|e_Jq&~TA2Cr1+4%vlq>{S z+f&0`F@w^P+$wy6AB=tw7NX!dPrt#Ljx0Wt;RC;*i&_=w{iY$Ms-E8+=R^ZvK_-kK z^-4s6#40C6C(u~`XaZ=*kD_3m`s2Ewh&w*AD|iW0H*tHfS0#G0pVZ)Y0vD8_xb~6#?3=#;8ib@T@hCv};OC0l=>sJ^ zKe_E0a_%DugT}JMr6_py^ECYxpo{R8V?BRwu7=1l!RMo@@8S;*I}zy4i+DDEtE8iPv( zulKHIYQBp~KCl{s8bsQCH%j!)Ng{aeJc*OwkP8S;r5oLr-Nq-QpFuN`W1<|hI4S-# z)JUWXze2M%}P~bUw#S|UR*WoH1KBdFo>u{G259+X1hi7%T z;we@A1JYh3%KMNGBAworax|M$ZT z`Lr=7zN=oDsKaDA{6w04z5c(TFk*&#kiPgG5>(p*Q?jRAZ*V@p;>Wh1$bvV~yW6eJ z*$hwX<@$evmGT{ow!Hvn_F0ar-*Tt!HPmf>4rSCg|9cB{m8i3|syl?bD(a{GduCv~ zw@xqK)L|?=gyUaV9S+vv7#)f_oT+re`Ex%_Aa)Wi4%^F~M^Eg)*^f3|-s57o=*I=sJiJJ#ps=@n%1wEu4&5v|Yt4`=khc|>e| z?!P~y|IH&BPy0v6S10*@JR`LKPe;Vbv-~}<dIH0OC3ywzMH35_ z7G#tJ$bz1Pi?gt> z43uK{d$lSQg`$E|eM?i^1!ZnescYGS5}g=R+=cpqaNVLX&D5f$rGzg-h(u>Wli zNBMDOMIc#u6y+Z%(pv5^<$9L5i!)cbi>8*$C@w6@T2uh+xt^0fMP;jKrLv_3?&6YV zEk}z+`KgM|BRFmf&c{<$1_Eanm*LP$%2u&;QU4!_|3I{SsG?`*Y0>CF$DCJEyv$ux zma~$!C)ZbW29KfX|FS4rT9KFaY_)s*nzx|XJ+7o|O7XIVONulu#I!TYirqz7C5%A` zxm-}RU|~^a$x0{J$vDydKv6*<;lgp-jT8I1S$D0>J>Xt4(G8NjJ>^qNvP;U@!ODI7 zY4XV@`^h_=?AwX0#G`Puk&C{^kZ))g!?nAoO>#_WbiS$1STCE`4~dV7;$kw5 zrG|X;qyBfv_pcu>zqEd3M~j)W7~;9M$>!2F`7u>&#NZ9529D+0C%4OsEp3}`u8KKL z)rR=CT)d$l7n|HR?@ni;+JmVvDh@7M&DadC4evMR*Ji*TKpy3}h_z?2# zktY~LqOk-E?gIOup5PIr!N?PALZUvgaME*;Qc*txuooPA>JRAUcA`L&5?pQ779;qz zo+p^nS*xE3xCV(P-3B^kKH+Leh-eD z`3Eo{Wmds8UxEQaz(yq60l|rIEGd5-aBd$BiN$~v$RP?5+-`&Kfj$Il`T`N;3EoC} z1$lxS6A%wTo?s0UQIue&7mf=RG6CN~dLQ}k0E^*EQQiyqXQVph34V^GAWyIeo+Iss z;Il}yOM*M~`dYw$=y`%s2(|x$u>_|hQGYLG{-`$)G$G7(6P*aoMHtSqO@q%NPd{CG z1u%j%7l<8%wZGuP108j`J_XU}U%hNGs8Y;QNq4#5e?RAyNO1Bf)q`lwSl$-y{ampWx9^9Jd8|f_qZ&vVlCo*V8%f zAo8!LdpT}W7A8l5;HyY<90VujaNPG)511){A}?~>WWdi{KmvKk{oraOA|t_>NQ5-O z^?E(QVG}qm8T|=9iIk1}Q-EI~ab7G&nPgF0n4lYpc0=$zBqBS(l3cCc2ly8xTA1Lq zV)(3M=#dVV_K5Y*z*c z06QZ%RnHTYkcgxNP32mi-~^-{s3-U#5>YYV&2eua?M6ME6Yi|L5S)twJQS`0sS0_5 zeLNc01bs-fV}cJZ2dOa;!T&;{T@rM!Ks&|~OnD440C|FGNOdplDR^e5X-|je8lnxG z~`U(I+`cy-aMrLS^-7XGZfQU6Bc8{97QF3Ya2yKK7>cO~yi-IcLRp15t; zDELAUoxp*sG*?OV delta 19847 zcmch9d3+RAwtrQp6G9+V2WW^a(m*2#TM`sV%a$eyRAUFiB0-5E5yBcs4BZgMr8{)D zP;^=u3ul}WH3EY=`bHE60)v{cB!C+(!#ZNXt(pb})EJ^rzwfzKf#}S8pZEFw@$%v3 z+&XtV_uO;OIrrQfyax^5eTEI*ZuMigE#&l{%UnOMgP{}0E&BZ*%7;E9bKHC?Y2-L> zG~jlQn@zCw=Q78gMBajWy?)=fOYi2VI4%i@+TU|rI^SB0<2F2vBo|fol)ZfHNIMN@ zLRy4myj6>G7LF?%RzAPTRm5>sy@Bu|-Hucuzs(O!UXNUxp9~B&tW2xT2m^s!3{sWc z$uN+AN*;;d8|4DSz~n!)X@+bD{tYWHE-9U-O@YHyI0MqA9e*?A@rJ7-2Dta|Gb>N@T}%?C zfvOhi6H)p=46Zhbqiyb3QM#f?@^zzVXeg8?MUR^^5mSzQWuK+ho{f&3e0}*m^zID$ zt<|7S47L|T#`dD^vM5ZtoL1}bHS@y8THpf2Mjd}os4Fk3N1f7P@u!nw-(%uWU(26G zr+AEMMRe!NcUsx$ehXxm>t zT?`y?|9{PD)!du2a&^LVx;kx_1^)t7RRs{nCK;YB_lSMKP;f(D9s4_jKT7^Fc9Ee+ zlsvb6^6-aDqAhLZNHI7yRtz+`%$TV1)X%CaN(V&9N#KmTi_qa8JgD7Ezm$nT%SYPZ z>v=dIqw;4(X_bliGG}hV0ehZ(mi=D)%vpK&l897@zWLU4j(&@r*pOwNZ9)6`ENdb7 z>9j7QRV&L07Fu!wUkd&R${fLrq?}-?BPe2JoK|GYOpahhqPPc>p`Sit-#RKua(b33 z>5kBHw#ex@flGqFC#Z7o=t?M>1NCO13tYu>G-exs7(O)YBl29Wj^oTXxj)Ei! z1liYTSyR`6BEk#-CO-!mQw~}uV)aF-MTtWr5hdwFZbnYfXrMzC(va-yu_u^b3gaPs23AFR;nZW|LzR&cK90ide&@iIo%k zHWMaEtoz28ou-N*7lhlUx@=Q}S>s}J0-jo|)XhoW6YM&UP15afhUS^XAQt|rIs;=j zn4HoV{#w_;ND8qdRkWeg6$72fCpj3Qt&Bn+G3Y!dmUZMZZs#M0v|6lf#heG`7TELc z57-Ora}QuQ5r_Ntp#AFZA=aHYQmruw9Btm<%!JqjH6$ko(rV?@xY35e_sh%Tat)Ei zJKl-&8sy6OZpKL{mYLlg1ft1SuG02O_$DFoWVa=;~XiU2?BS)%pNXJC!D9t{J zcH{hJRc(sl-IyWGB{&ny;yaj8a6srFzuGBN{y6@W+|qeUzwcFbZ!9{eMbd;b{04Bo zYlCVA0WD0ZlP`7Z&u@?ugr42fZa~=4l|@wdJA^D~z-!8`>pLb0g;54~nY^|0EO}{{ zzT?kaQ&ng1VbeXKj^o9sd$3?F(4)$ixezM;hErP6x(^te>@}o+?R)5Vxbljs?%3NU zp68G6INbFpZx}F7-q_8`r^tJ{%`u#sCwJ>!#ZTI?rTaAAU~G|_5?||@jhPC&nnnI| ztOEAp6=l>ldDm?Z^Q-0IJ#zWF9ljp3c@MEEm00wF2(357*N>Cxlx`T(88RA}gzi@! z{YF)Z$*Bd{&&MjSc~QI~c3mvn%?G5TmDf11YDM?D3o+iKwd+jYbK|{7yz4}MFVTJg z#T?JA7*KbiqxWdzI+JviDxM$jJ?dTeU@qTU+psr^YS&%p>^mJw%QgDhj}on>`a1Ss+420w^vV3dI^@YuF)d;4jVLa2^`tbDY~Hn4;_Mr;1tDTp^ib z3e#cx&>>9tP{^wl5}?3HqdFzFlSoTR*U~=DPOH5yJMD;SK1)4CVI+5N6zpYM$owZD zvCnc@f#9z|aiF$hHtm(!;hCr-?8-C}BlE`?1z!&o+t+uJj(X46Hk}qKUzE4s-qmx* zy&RV%?7Az#o?b6J{yZkcRyZ7k=2D$%F2ESlerORa?omVddsML3kUy1UYse8MG|<-1 zg(SmJDn~D{-S5gG?KK!Q#TA8y8~zYUIoX!ED{ z(b$*8%7fKFkZh4;i6Lm_db^SH^+l!VsZ@!?K$Hw(!-QC^Ei*h5Ol*lKBr0zJOWQ_; z{od0KoppIX8FaudLkw$DEim8JnQ|A)?+h0g%1>Ip@;m_V6$=;(Ufh*d8;Mu$Z(-7? zZ9w0_^aF59T+nqNg`F3%-I&55r3~&^M5&YLTM$cAxHO#xzM?$v6)me6oEYmr;%Y|> z=}1mUopKxcW!8k$P|y(@js;?tNB276NVZpaHgp3CRJ<6R603X-jVnrz!7WjRr^yn* z3Dqe0t;#zKZOKdpO!j_d8@R7)JVN?@7t4dhdq86N?Jv=#@1jUv-biOqe!Xw1A?-`~ zT;GQctACUW`VBR_dPxrT>u)IdQGT;ug`wr5Jf#0-L(F*jSpPc=lh4ZMViIMO)iN5L zqz9vumT{b~Im#Uk;=n3wzLa{7cLJt5B=9A^9V$3O@kujiZ!25JLjpgQ#}4cvH(LA0 zH$knTwMz&_M@nSVfbQKpLkECcVla9GsXW_Zcb8yvs6-w&pu4967$K0$RyckxKG+&b z(fL#pvXZKVsLVHZIuNM&DEo@f!k6kD`+$b5MUfa9I}KmZkyoaGtR`o01SwcYa+C1* zT9nsEr{a?rgXY@+MgHoZAHmQEAmt8hlpa43s~be=6Q(XFz`<5hnpD+a>rSeV{%I*X z$O)*fy9vLI;a%yh=AQ_&1jZ=xq*J;^+sX(jbxPmYN7pTt)9$#f;7K$l@ADPreV$V8 z!nk5EzJky}^J)NP-e=fYCa_m9IjtE&Li|!-g@ovtR7Z`@22kTi;kUcH*bvNIG?Lak$N23&VI(9xTzcVDyW8Y+fF9_N}j;-3V{9SKJ7SR$8N z`^Xss$M8etM+fF5wjQ{CeTM4yi~MEM*wjmKDpd1$a_q4i%#dDkGawnNdANpjw}VbF z6B$zUiAD0Xsjr;sysvn9g87d zQ2ufm%W;Pjn;Etfd*`Y{YKA>fN)%ZhG<0$l8fnUdIrOu0r21P9SqAO*A4tf8(1h5f z(n%cAm`-~r)*-3k%_y_4pVimCequj4VQ?=#R~|NaaH5sa-p8yE(c84_0M4~P94|jK zcq;#vTsQbG4~*Q8bI>tZDfn>;hrw>}T{pVtqg{W_^?+)g2Ow1VjlpM`@| z?WOa)eGjdPYW_3oKmnzw=0uhg0yxuQg|n;~t<}lEwpL%tD!H7Ze^PRpCSg{ku(BV$01*%{+^O#AAEBUMzj+mHJ51NzKTW?g) zk$#H2r)lS#r>4q_6-MqJOyvOiDAEUxw=fv}cpaf!D3(X8h3HBt{2V)n|6as*g;N?O zN^@fu_!gRo1w?D{ITC@qSVwS@+#@O9GirfvOdNKB!{EM5hlWCB67)0_z+@*FA}xq@ zO2c4%Ff{ioU!P9Ug|uydAk`d9a_tGx=vyvEapNWd>9bS^h$2&}Ci3 z94*my)c%gDdQgB;p&;OxE|hL!ixnkNan?FL`--R?Q|cgX2p;9G6XcTa&ah{r;NW6(so z5}_G1P<4@}n5*nQPngG;fB>rL#-v*u#n)8eoWPla?p~n;}btosgj53%oZgZ+l{))NW!t} zemXzg1q{JF7|S-?EMfa{qKSmtE|_A%dwx+>A~XCU+(DcorjF`ixc_^3>8K2OrtSWo zOW@Y9kW{S;8{&$x6nRj+-eD8=Yu4l_HCydnfSlN12(*XBOqc40+|~g@%P^<m-x%mNLgvw0osj@krl0MX zvkr+UcKpL(*t`kigw9?8P^MDbU^_8rat1SE9U^~>CV02j<2tRP4}h>%Ru|l(7*sIV zL}O1RHipcFTFv|Qnl10jW9_#k-KJ60g;AtOO2bFrB_?7R1TGQ7s10AeA(v-N^}L8> zCeJjz^~n2ygFmKBr`jQN0C5poTL4f(b1MjsLeYO<)jp7fs>cA7uGG3MtD>~-c4&3} z2GGhX&)kCW*GH*G;uVYaV%*(Y#da81IgC;*?#1>;$b3aBjIds=fhhRk;mxS^t6*Q{ z#YHjD&B;5hxqxEGZ*3%=`7FOyzVdc4#`88wWt#dA>@{q*FCCT5OzU=WhR_;i_P0p) z;*cD%=Oj0Zp(@rF!mEn$Q(DJ;THnE)R)xl}eQw`JT|w!7+YSy zs}9@eP9gK>9H}`>mPJ@2;&+bi(28DkxH+JF!L+2`nofFG+GI@yjF^aY%F)sFsnaA7 zFwR#!$`ja9PLQvaU53;2Py*b~QyDlL(Gb{f-D*7QNW;M`bea1yi>Ji^n87kWNS$76@|&zoJbA~Yapu^vj!H_jjgj-Zl= ziD_)Oq6EQ~$`&u^5;oTP54)z*cph5gn*-YSaGZrx{7g>@qVJl)WohzTpF^MK?{jO#)kj$UKm&Y_-0oES#~y=Gm9PVCY@bEJz-NpU3qveHgsM8i0x z^GbOGk?Xgfq@jXU)?);DXj^A!0-s}RT+zPXn1Gu5Bi};HQC@@{lk^5~JCZMWMBi36 zJmg3l&MAc*(n;otoQDHPVU%U2?$J6WQm+5 z*m13pJ}l^$MVQdDT|J>im&{I&6y8hhQO(a|FpNQ>IW_@zUxI%jYaf*hyW*Zy{9%}OYYLP3--}eQdRSel_GywZKUQTE9)~%kv_=J zGp{pCq38RF6?uLr%TuYxqTkgt6{@-vRp) zu=ZfrF=Ay4=jFIn!*3Q#%?OSQz86_DxG}bW3cfSgmYD7H7-H`h6evTJ(5bFNwXNQE zH^8(B%!&8t60Q#XpCnw{VI=A6K(=xgt~psp(=q6RIBn#|Aq;yfXyl#fN(q0as>+kS zXkuI0q;S<@G?MUY8{VEMO4A?{p1w|Y{?_*nj^N6c!Z&`DnT1!cf-ZF#0Z z$Vlm?%DG|+Ji80Zvk{Pm-4K`|5M9S<-)SZ{YvNeY4WdWJwx1{p-Fpem$11miBJgD_;yI&ktMTfo(E2N zFlB649k)vdVahtG<^YB!TGwLQ*-|4JvA(Lg6j*k27i-*nnE=S0`PKIm_s1(-R zi{DP$Az@87RG`Ib#BewPwJUd zjo75fY+@E3oAWY0$HcgB`lzy+sE$-+Ed^{pP5r3Noyn|Rarf(mju7nj&dQP$~ukOVUjc| ziD)6UtFkU3u#s7VjCHSB7vt00VsNi&U3Di|!G;Q)aXkpNLT0%?_KRdWfL?i8fRuii35Nw5BtWAbB*-MCz%lYIMa6Hljl(l@vtq#2`7y zxcCh;!LpOGUqs<9^{}vUf9z32<)K8=&V*k8P+o_(LmX2;Xq$ZsOe0~iqD)pGNF@2n zY;XtTWx{prmDiw~$SARTO!U2R^3iEMW*vpZ8->8RIHG!|l_P<| zwkK^OR;o07d3WY{wJ04(FOF4-vbAqeea$mJNKOD*>E{R+_*n37jFa!4-qW)R-`SyQ zXYIb0yH~~K2wOfC*31JAXIZ=N+9U1{!}n4P^@z(X?BadR7Qvqdgs*v+;2%vTQdkQC zDmQ2qpbfNbd_k$gp%D9!KF44eU&1kFOnV{l6(D)Cv~swTZW8>T0z=cP21Z2e!JV+c zUO8^qFn4>+?LQK!5F0d!$Asaj?9{X|AZ@vNzjYDeL5Y%b8C}sEgqe^oeD}dO3p}5o zLuPO7j!=_k@Z+2@f!s zgiJ$c8?9w15Z<<{@MsW(_$VBp9z)Y`zSYWb;bgyI_cqr)IuhvXZP1SEcJH;?M+X26 zcR}xIm*W@TxpsBuV(h+i*84je;=I=^VgjI512#poO}o zTt5N`iLi>JH7(=rK^u=Yw68hZ)xp;sBly3d3tdCnnVV++4VYbe6Y?sNe@jo|e4slQ zZrDHX_!uFu9yQnllQYObaZ|@pg71BR|6YN#>W_`O{n()PurB0>RNmaV#-e}Eb-@z%p7IGZ|5izejD-1>>vdvPND;cuRcAz6nyfZ5&UM-HX*YcDyDTk zHN~cE`Wyk>a1-_(4MJQXuZGAZ3aBIEGY#6-#I?%8w}{Y?SQDG@Y~iNss<~uOV>M`x zP$~IkE)RS%cw;}qFN5D%4;=5x7JZ&(gX?|r(A-mLP&OD0&&K`Y(!=RuR25VFqOb0- z9dCc^vB%uiUR!(jvv34jX^T8Tfj{^Vpp&CZ+*X>kquUl-gM^vPkmn5n`1M)ZQ)uGq zGZJwXsm~l{y^zccZ4Gg$<{YQv9^?2Zd?VOihSBIPSS2O~ojT0nyN*R&fv?~cJ~+z= z1^P2S;69S{IfE;*oI;P8MyGjy#_5XviTs8t#uHvR4)HZRk!mZWEF4M4u@AlPVF_MjqH2U zsReWeR$`{LOnGWIZH`;IXpvG1420Ih*hO0-1A~xnz8$k9Mw5mLM^Wg-c6r5Q>TA*Z zj=cBUCAUZyr=snO;QNYBUl6_Waje|?2+zN;a(HY0`X%=rt@#_(f^Ro!N4g4u*8#)* z(X;p3J09VQ7pW8K-aCeWM|k2XDvhCW!K+40K6o7Ijk1LvX^NQ8l$d!NRr?1btT43NpT(Iue_% zylGKh*#TxhV$I`Rz1i^@Q>7j)Jm*1XqjoH8>_8W?(bPY=R&~nXyOJU{#^Ba%FE^4d zr0JCVecBPYa(M3HigQXQasnT_<0GqcYzJH}r}Vi)Js|8F50|9hsw7d^(z2qPy{V3O z-DiJ;cS>i_k%~zBnFvmmYCm_3-Fu}yANj_o=DW|oBd?x3#`9npbLDY2pgDxhL%gae zZvrcw!lllu$T@>|iou6*wwZ(ICyo-Qm9L0H8N1Qs#CRy8dougXQrK!UTd%7O%EVU*}w zSO~7+`uG}@T+^hnyx{MzGuaEMybI4AqJ(>J?X>6c4dfgRlVTZ5=~5WGd)zRr1_Dxf zkOkO1<%c)m{ssp+f~B}4&I93LyaZEBaNk($>VonpR+R|`jj-f@F~aqsw4l-5^#SC6 z8^A*&yp9(O`Ut)B5jM~W?f(}eyrqqBf&iv4{NF~P0jAs<;2&5|b|XPMn!IyTqu?YQ zMG-zv21PDYaY>rQ(LEn=3!*2TMlX;-)f@w_9V3X=b-Y#}6^$72lH zdNA;_Z3JPWfd4b7l6EBRW7Yh3oD3oL9*V@muC5Z44xO~-ThN?rv$k&b8fM(-S)MWn z(NFk!jUWokryrFmrV0_;i=MGpn(ce2zG}W*BeWUHSvrA(kVwR-Lf{SD#N50^@QR)U zglM(Ktu4)3OS*JrEd{-$M0YqiYhw4VV?+E4h77#;164eZA-+ZiNB@8Vu95GrpcdwW z^F7>1^+(+YybR1+*eS5HV<(S5NXHo*umj9+hK7!X0daYF9r6aV@G%)ZYDrgTT(DSk&?+sL3__>W&7(ODf&l?)M0;i^t}1WA zq_g(`rBDAXSUe{RKFT@x>u*P)N~=kLhdc_aDTaooC1D?k zwi|+P8sxwcbp8lG!;VMQaJ1Shhp=0*wW39}K63hh7FIevh0Gh#D+F~nfU@aTauH$I zneKou+lZ{P=q%{NV{~@f+OMp7)kE|`h|fC^!C#CvH@DM&-b)oM_|A0dSFb^Si?9ZC z7Sm)MkytQ=B9M6Jd(+*SIfkYcp?iNqfqRSzso1*SmTJtuu96p zopMb6Kz2b%pZAj~hqpk*q`o40RKfleEw#n`KIdLp7VDltsg zmHa+x-sJ9$IZ-|3HWM|9Z?a+>+@x~KN8|&-zXT$@nmN_+KlDdoA1d6b6&?a2a!mf| zfdPj5_Q<4Z&m9jF zLD%O&RE`XZR;B^s$R9b0Ju-tei_QTcqly<4H~)Gdod1%1Zf+ODgG1$}xuZu7fK`Mg zI{^u&XUq7rn0h?a#7pHk?allH+iuP44>W@=G=ov{qQbe|zWK|)|H;cIe~~{cywfok zYXE_o2LO>#dZ8C_9ztp}twZfKm_DaO@XnJqDU+!EN(=e|;ls6dL-lrF{zbm8D81v^ zcc5i<(X;k;ckFns$ZW8P5P+K(uG?e761v>pExZPRSlwU#c;3tLy(}OiXxxu}i~7oI z=ik|9@K%h1XDIXn#=V){w$hXdrJ8>lD4&|&$MD{Ja`XIQa>wEh{Bb$4xEDWIo?2YP zr^>MlX2m#dbqH3-^B0UU)2w5Fz0CPfc|o4DIMe(IVx7*A%Q^%O%vwl| zGdR@JU*56!2}5&VIdjQqL%~O~cS*MniMU{84;qzp`NbuPw;!jogLd9o2VG*vqJ|zJ zDz3q%b&^jkncC$(=qJ(!USgJX1^8aFZE4^3D?g;BR}`l_Z)s|;)0gu^`*T${PTE%F&ZZW19qwyqSt;Vd#sRv13at>mKIM$Ta=t^<%asG=fr@CKh&ds)~X z|Mei8ErdWkktFqgAJuA*<)rAN)Yvzc0LtlZO!UR?!xyQmx2RD23#w$CgXl1fxCT2L zzJPE1IZhnSKMHy1RW&+5?c!JAH+$LrATsoWo)!+``oIoCEUCeZ-)k?d3bjq9{mq*E zRY}U=@7e6>`QC*;X>|?61R4`=Gnmrq`A@URKgDqmm6t9rb+0H{9S6EjKdyCtk4~ff*Oj@3 z^tG~K)kMBpcC5;i-&!?uN6(5!qIW#=XfE$b@@e(cb?DIH933vz;lnz7LWk8l{4X8; zLx;z7_%9uPqr+cxnBdpOvuM!64bu^J9WK=23p%XP;fFdrqr)$C_^l3a=+GR{2I#ND zG#!r9;dC9|&!7hat0R0mly&%u4&T<{5gmT6!;3onNrwiV^BbR3)f%KvkSPBg(kY~I ze^%9bNE4B!A^j2QSr2|y>1i%J&{;@Jkd7fW>`~Rvk@o88`@^bQfsp?+^NH(Iy|ex%Q`gt zN$a?)4y`)8ONSXcbn5Va2C*t-I-*L4TXnckho^P;tqx;|iTJVTFja>cI-I3LtrpI= zzxj6Q-)jhBd~U;UD*RtA360PFW=;MtmxPVa{d!IQFP8)n4Ep)in&A64OClGoAIHK1jd%g{3s@qlOc4g*r+zGjqGLBnHm1ek#%3ba<$MOZGc5a!5nXfaP zlj(bA@v<_a7ejqvpqBO$`hQElVfl&W#bC(=mY-g%l{^xa<1TS6p0L_gJhL=!@%-Y< zMMdRYT~x1W?&9**G*bC8=DyrqI_?gmJWT4=Gu=1?{CAg^7cX~ZEiT8RmX@!^xKoWY zni%5;kr5NOUAsS z#jc5^uy zs_~Uawb`eapYAG|LacVVSIjKUDqX=ARDNrHKY7#oe!V#!_f5tLh%QDOxai@0As-XP z#Y`}i@r4}sRB))-)Sfe)@0i*lH?FLGVQg*8F=M0QvOF$0sY8btuEX$lsWD~Ig|avJ zXqQgyxK4amPQy?w{KpNZs-pw)xZgJCaq^%M_?{iPo{NgPF8up)UWncg%6|(@hrvxZ zgh%c$JP~8?T!hXv!POmu^aJu4fJ=~mL7w1qNU_gq@Er18ktf(00Y_>}@Eas6@B~er zwSEXrLZUj00SDuFOmzs-QJMMy^l!Dt-E zXvPBI(@4}0!B=%W!H6AR;vcd4ew@Wg!1LAWloH zK2Ap*PDYGd1aUfIcnZz!K@w4);QZknmy7)4fPWi~^(RV^;m#sa{sth9P;3B#Gmr>R z@Zk}-O+{mZ)kw57IA3uCkf;v9%}5(jhu|kjR3{8LYLu2A2UvwvjXG7MJRJAubohMu zVDN6R8TsviE5~C=kSF*)5-k(K+6nLnfltfCYYrr0JHcEeB7&et#}hn`6#E<|2$+(k z^+WIzq+dK}@Fg;%v$Yu$T!2KK65NeM0~7oiiDpCa-i2TG^bdkB>iM03NA>(CfMb_vL}UWe|4l(sO>oCDZH)=WFW0tK zFTfNe8i3#|JwF?8p`IsLspUN^^Q>M#utv}KFU8hGnvakq0*h3meYXLizV@m^%V*xiD$%{OW2HdRjJUki3$1Hq!kceu6XOM{P1b420g28$d zy zmh^39+bXs_v#okt(o5@JI`)#BU!CIIfG#isR~=hzs!pu7R3}xZR;O2IREyQQ)w8P$ zs~1(5RaaDdtJhU;s7~CRx;dTorfj~vxp_0UC3cHxOWBr+E#58bwrto@S3QdVf9uL4 AAOHXW diff --git a/crates/uv-trampoline/trampolines/uv-trampoline-x86_64-gui.exe b/crates/uv-trampoline/trampolines/uv-trampoline-x86_64-gui.exe index 8cb19cf8fc7cba9d60e88ef504945e020b4dc608..c81d8e4e588e0f63a3b1e4b49ca8a931e4d18252 100755 GIT binary patch delta 21124 zcmb_^30M?YwtrRIjeuY^YP&C}ZG;#V;s%P5mc?oGtn@qM?? z-S4^Qo_o%@=ib77#Na+Roc`C$4dOZ(I&<9er=Kf}R~3#cppqtzbDIEn zAx*IDubJacAzrE^@;&;As2zut`5BH&;@hflT%;FO}nJN0e%{8CJlK3q#nfbT$m-w=YEmKF76&9B)((van56*zFefK{NIlOV(M2=Ge(S2N1>p4!8>k?1f zbuDpprY21Zuid!i398G@#v{u;%`iq)00~6my)ka!sM%> zd{&e%XUWZKeIe@mR#mLb5YllrO1= z6{}G+++C`Rks%r&OA~cYEUTYPIBKjgkf8Pi(`J5qQ~+%rt*DUJRX-~} zncCY|6m!pAM2E+x@uKfxvzXkxa>%vs>md-TUc`Q=IPdH(bb5md_tOv7O?blOzymOC;zhF8<;y7ina89|gD1o;LUC{us)r zvsf{7*e;}lQLd>)t-BAK^Ew!&mMPY5bCsJyR`;XLq0s(tHoZ$ao`F}Jl{|Ad#oevX zh<-p$@;zpr=8u{rc9;etUWX)5KbVV6%(qJNinhto?ylc`u3I$EZ`rL{PV$B>1 z%FXCCh&M)wq0t=!t{Zs571qXY&V5}d#GItRR0bf)ATa$}tu7F8R(2e}-t z=MJUZsHmo3AF2~asV>%zQO&juslTuy>!c_*5;s|EJ<;mEILUny+1Vl5uhvNQb+wJk zwf*|=HOeRb`rH)(;VsrR#fqNe23ml6TMda%+dldYWPJXqmFONL7i zDqr^>Vi7Dg2syPvOu98m2Kz~}!x|^a1z;QbTH@&pS7L2O;t6!P z4Bd3VSk8VZ*iWrPmM_pZcs83A2;NmFuBa`aM_e~AFcUR|y=g|04Y?^s!P5i9w#v@( zN%zItE9Zo@q>?s7b*M};w$^S)GYWe?FML!5~0bxt82Wa4CaqSV}64Il&Y_IL>h~(Sx=qD$diL{!8SdLTwI8_-q z=utz-L&~29eU+dVF+K>#Y(^*62BX!e8;qSaf#x}qR8OLvq;??Z60xe&I>ErH%4^og z40m2tMh+Qn_~i#>{*b{2;i}>pQf`>`gK~4oHbYIO@|QdAFg$%hdDqlaxo}6U(Pvs- zs=OZG-!`Ze_Km9Q=p$eP&(h)TbVm_u&W4#o|X;{!DpJUMTLr&K`!@d z>j+M$kP%f)pfPDUpQ!_?h{?yDU415w)eZ^X#i$ah4fcPHPpoYgc3Fbh&O${taz#1% zG!l$Jvguvg=6=bVp!%uIGw~=gT&TK+C8J+QGUT4v2!PC5{EGgGN05^ILq^{V+^xC5g1D9AqYgf+#t&D+paY5j>#~4)DIv+ zytU4_W2T4D0AeEKa*U^fX{hY2btJK>f1sRCzYB$ePfD=30f&;(vJLox_v$(ky}M1Qs{ z4s(q$HBhCHsRkev{R)1aJ;Vmnvxv|iI^VKP=^sDLkiSfs7T-I54GOTJb7yhfYrOhy zIz*QRTrPBKvt-^$3}L0G{j6+?A84#Dfc@F|&Pgy9rr(*9dCM|m#357DpUq3yfsj>~ z)b(EhZT@o-*Qui1Ta+ISU+h_E=BVua*C45HZn)i-shCIRx{?-qQX+w#-Qa8%{iZ`R zi2^XKYBan#wO8$JMYSvfI&kRe?IzwaD-WCxtpN@@#fYtogIOjg$9yM|9A)z z4@;^UW-!2;U`3bbE0c!B#+<)Qvzy+gmM^9%rNd4o{B$l9N{_gi9`V)MqofVN8}hg6 zd~ERG9c@HIe|EVAvRxe#P>SywZkTya`Qu%&(%Zz?zI$L|qtg~xvbICW;RJUDfUR=w zKwD+zAn^^-KRF2A(~dB3XIQX1SaFoi{rF(c(FtG(CwMcS89uCMFKXs~+=}hdS&I(g z9ESr2C2jZ&{xwAzKH7CDWEnjlqms~A4AUwjG32>vbQWp7Eeu&Up>*Qw7|l7E6nT=} z6H9a2Z$g%BA4!mqm&gM(@GS+N9J$Fn&ZA8FNHmbr>b( z&!jdpc-{lQ(~d3m>a`zfXCA|-SRVy20o6RnVte8tu!V)9v=>kzdUWyvbyM2KV3YHO(^7hAKlWZ0oM$w+w~)(z$@ zf14$%%xd`@x>G&|wulT_&LfUkp?pNIyx-alUs##RaC(R8#I;kv>H|x*o4(P=hH$21lq`-7 z6^sOMWnu6R&asK-ZHI`~i#;v5LPZ6OUCVXCOm1kkdyX1`#p!9FHf@VwkybajpsfvR z&hoTOa!g;mFBh}Vb?%~JjEifKx;Du%)N^fwQ1LhF6q5?erCLruy8gxP@*E9k(+zY7 zU2YW%6-$v$JTLi;1MHz%TJBiFkL;ra%^rruMN0f*=sCY}0kglZt1(|dKlzPoP!&fB zmF0e`-nd9)yQI!;A!4=DZpe2b7t)zHBS}O+8zgU-5W@#f;8aOS2EJj+hW6fCp<)-K)E+DG40?y+CUbEV&JXT;!i0*ai8vKk1n&u0 z9i00B>$rp9c?0=^*=r>;MKEt<*@Ahq^$bgIwW@%kf3vle(((5v2=Oyxh4`svA>Kff zNupv9W5cI3PNXOwuo5DYyxsaT^;{7}6~OQb*xC@d#7kf>+5mykGzrgks~dS3Ln5*= zq@SeITwJ=fREXQiN`;siW+A2{Bn_r3_c zO;j(?S~x_O8P3eY6g`@P^D1tx|f9tTU4O-eiSt8YMl26~|EkShuE-G??DJ1*O5 zD6I~Jsh`x>1B}8ki2@#jpL}YNYbsoVOgD6jh+{H4)Ooik-U?qdYE_bxcIpOw( zDP~mSggCFZVnje{6}Q>?6oXr>RW?K<{|-hcn-KrI1pLg2#ZQ(QKN;Mc)C1dej7z^& zcb>=MuC^vbI+lqOc3?aue_AgWvkt#iH=Lzyu-#fqjp*!Sm(x>VVLeWbJPk>~wTuUmqBA8V7#N?5Aof#finOGfHA+nV;ssFH<=@Jt z2*a4<1LK41>LpN-whgf>7BJFL>KCtzp4uDkYevZAW60wjDfl%w-lhe1rfT{O2fWRP z#ihrS#10a9P3n%@;Fq(7H#%Q*Cr3E{f^E%^3I0B@R`fufa{Yv7Yc>4XW>X8m&4E>H z?Y-7Ug`e2Xbu6_f%yXKLthxUrp97sQh0iGoW_?sPO=^$zc}xzyl3l$HXyY_DR`j$+ z3SI}LeA9WGZ>~|^Y@L9lypeH*-S>ES7UYgiFlY7@;(aq%F)&E|ndE;_sMK8i!8d5z z3+qG730@_nNIqOb_wv*ZGPv`AwS_I$0V^!VTdWu}xd$zIPvl8}v4FtT06LKg8Ok&O0V6Tx_06oEfcRTFc8 z2-xM0UV+}Q$K2Nr37cXNvAI)>h=w5ww}`OrLqv!H+~_5690}c%EXp4!&>*DmX0{z4(kpen6A%EcByr0WV(Edr*U{izQ@){?q|xK{C;awkpx-41=4kDuY|C{~{QZYn6m}oTr3%*e0~S zV+mEbmLCrn8^W+_xL_7WHv$_PJ)FVqE-TXS~bSUwZo=6pjBDIxL z@MlT7lIegM6NPt^$dj)I7GSq4jiVOy>4F0hdC;N0Q%TqKfzC-h0W13LRAuVuEW`3_ zBA{K!(lTAyIi{y!!bs)Bn0_%o!d{1v z3ORCN<-=h$UAa2uLBm^3O5WJXkx!65!&q;Br0f_w(DlkkaJtaxmK_~vbJ^uaQ9elu z@nx-hwGw!X;2>Hb%!3Xp1vu~udplwj$eSOK4{-@mDLhjLp^5tzsK94xV~?j`@tFXD z?ZJA}*`yMN0~)1-xg0}jXAuXGVeKMfl3_}0ae^QgpV2(8a-;PkGAcK-ZIxQN!+L== za*(4C2jdN-II!M%wBDse{zRw^tBnMyi=6{DNY?u|z%iJ*q1bw#`?|rIE&5&HZ_)#? zVnxVd)7R^{518Is66qYtG?N(LP_|IyarSB(EZCTMgh3>^&GNXf;oL^F5;N#J0Z(LNm($ihpPzEmb41)#Iz$I^X0;a#eBHf`Pw=%oG+;2F7&tgERRV^#=USS-x?0--7V0_S+6RXdU#>>U8;a2VWl{ zaZ1G1?yvloK3ct{)Yoy8(u}XGeL0Nlx6IKBX8_Rj;B5yI+iT;tEZv-5;b6KA<1=6hJi5P-o{rzN?lJC z{FZ)N;~wpeW9J8!qUbk=o1`#o(5U!5)>uSDzt_4;=|8@=iyaPeNPH7w4#({;?Q2`u zal4BInbO2_96NC1mLX`%if^ebRJp^_lZu`+Oc|i|7+bij`t6BKiG%bIs~E zanx{+wTjW>08@veyj622VXDt#-fdnQRKfBc5y??QbL|NwhPxmMJ<~1rU!l_PyC2A6l6L-wNiSep*X-cy*Pc8)iJ-2uS|PJnmYQO%r>$UZz3i zb7DN)mfIPG0ZWOi!DXBGbCoIt17AuO+>A5P30d$x<^~2{ltA@@_RT3oD^bU zi9=B(kVb*)7s3^t;+jgorK&);+AWm}>la{6LzbS%#Tc|^6JzjXLh$-9jDBf#8D3Ug zT165TgiX*vN;?ZpY@jlQkaf>` z_xxxqEAq{r$ceZob#vUR5$#2Add4@dcu@2l4i}T(7d+&I!p@mA7CWaJ7^v1e&?S>0 z;Mg9n%4@_0ItyMILS@nUz+rzmRsISY>ZuPyA$3=8!bEc{VCNa*XzObG$JSGn?~N7Z zcuBV5em~!=s)xag*uIQqVcA!qFS>knuFdWHmBBXNTp-l%S1S|fUyjTswdD~^R*n)oIHHs z85g?8jCa$F$zP`aY^8=9wcQ8QH&EsV9NA!BItQWZ!O^HXlyXl}Re$sGPY*JlPb^qX0^z*wXC^zark z12Zub5_<{CTd5=aY11CVd~LYrq|;kPudalY*V@9gi0B*hp-uPMwrP;AZL-XEfPA)+ zd^9*-8s=rTKP_qtrb2q;RWKp>7FZgK$zg1J@+Xpz#xzJcp(1}px?|8}qM3x7R{1iN z1c`=^WWyZZR`j$c0UI$pmFT- zjl>h60KGVglqC71tcugl=-?w+$@LD0B!6ZP)oU(>L960Kq5ATQ?zSs+yyHIGtGpzi zM^7Qx6z79D0kjTshS}Vg_~195)?DYsgNo2=5ITNgeP%ji3+kiZ;Lz z02<&EQmz=l_3IHFw?=qDAK@t);h^Pzn1epTL)r*leT2vK5q>>D*INTj!~jBf*ojQ& zLJ~J)Bp|mM7b(igS-oAC+tg>~MA&Y1>KsDiv``TT4MlTT3A1?&GohkqC(6`yAS6yX z#4&ufYBtIfPb8kkX*h*?ZxC@80Jq&LL9OqlZRF`_imN@%hUD<-1kp2eosQ$Wm$@U_ z4KpD=m6#xv*da9!KO9AH!raIVP$Wb%B^ogkO2p#F?bZ^hrRbM7JridXA;}mE_S_#q zHTDtjLH1~qE$W8x<@e#1iKiK!7b={v5!w-Pi!lkmZ8=bDnb1vd*o+7@%tAxdCE;3* z`=$wun2}WLV|0I#YK4S~8AR0v?F0@fii@Qh_?ScV3V^gsEunbGh+oM!BpalZe8cOA zqWs0Tq{1)la$L-4tFyN$3V9$;+K&Of(lW54B_M539g zBQR9Y{Xd-Fn44k*bFk1pLfJx8Q4gYw^gClk`3JIYRCcifkw9#zZ@|p{=#FhbQT|z7 z_BsT22GIwDQ%gp_{{^Ep8b&YBsCL2#=Yz}YpV$qrj?sGwC>#F&T(rYAkwK_Yza*gu z9pFInpoXk%O(0&}3tT?LxlLUWgG_&P`7kJPP?4D_#90za3qUpF2XssdEEkvb5Iy;* zso76h;To(U`n;qTf6T`3Oz#AUPs?waF(TZ?)RUN8Csce6mSCz<&ByvBet%0NsW0L2 z5e9O^kZReYC4>qKG!mYRJx2=9A00(#jv}<)>w}*fI_i;4gB;iGBf{pP5itkS5<~}D zcD0J?lcEk;Vv@CTR*%}#pPO)XBSN@(R}+gvsB2y2=;tD^1XO`$v~433LFXVBTj}fS zzo2Vu?t2VEMLgP*D~j5aE2@^k&AMk7ONF%L-`=J)a&gZ{zDg1Tl9jXtUZlRs59G$6 zo>7Deph9Djka`+JkO5C=&3WdC;FReZz2+W8(kN8?5fXw-@n&iOW@rJluWcn7l)(>9 z9vU^D1op%b;q=5(s;_1DrfgD`Ih2Pz<((d(yqVF*m56kP|K87Rp6laRMM^^Thr+sk z$iro+&2w$F^WA_zEkY+87GW>%X^9oQt>I+kj1as(Bhtn*%c`kbfC1xg)LVZp$yb1;C}g}tk0-so30q31ucm<JP3-jKmz3K*iv( zF<&Rv#GgHA{IT@y2ENiQ_*kZ$)j9%(Fpk< zCU5OVq;Y;GcK2;$;{gSn_q^qh7wMJ89?E8Me5dn)ximhG1DaDwdp?jD(tJS~iQl)K z3A%{5k|eB|S4ooV$*ZK&o}|qAIC|Z`F|pk;Pm(>WE{cr6$~xi!Qnn@}40i zArzQTiu^=qoLad@yXb{fnSpd&lUaCuqcscBh&r+ELM+{3*paeFoC^FC9ndk4o=A}V z$9uT=Awb}EL=LMv&l15NgVaoen~QBVmxknZ^(kB*cqTwZ(q3=3&EA0>9pEtt1pT`> zVeXGZsk$57p-v}1EVc#1nMhB6ZpzW7PA)7pl`3HyQYCdQI==tymRKQGXcp-P@VSrPh|LeCUK5YbTxSOjNql0gr}bQ z0!fnOLzoemo7EgkcwZ4p_@* zqEOA?n{rwqh~bK7Dbjw_W^d^-60d;&$YI;QwYs*^)5cy@Wywd{_ZF=_?X8u2G;>Ph zhOPQZ`---=vV_UlO*^#Xx%_#S{9~5<9qq02u!-<4b|c7>E?;ng9HB<$vc<-VMHy|Z zQy77{ZI95#ibO8kSRAe~viL9U$^XJ>9v4Vpk`DRXiZ8skJ5N5L66d-Z zvN~+v{$jG1l{m@6er6CjAAwUZnyC?dx#*S*$X$SHKSJpk9=FNwx7mF}bFfJymWo{C z>OWpnn(nhk(z?RYtls;o()s?OOW3L)`SfeR3DS^?$;Mc4iYW2`Y-YU3d;!`Y>k+AT zfomF;N3a9-6XBrMKBvZz%UWZ{S&Xo_=m}#_tH@tK=Ml{dZ%KkY#zM^?u?*{c^vg5~ zUc=4@Q_dx?2%QH`(F|#s&Z$>uVf@hoI%A&zg)BFQvvc2g6HvmzYBu59c5VSwk*2e+ zfFo}+;soiF4mvycYdeUiVm`3tufpcXw_OawwtE6gw3T5v3V<;5zVz=f?0XBtOopLm zINb7-zYZ*MIu|PFGXq-sa_mz4Sq`Frmi`9erhOP9(#x-T9Db6Na}V@&WesE)5MUlF zxMa|X9y?+UI;m%2vm#QQcm*wDv6tH1z3qO6QLH*H&buIjc?Pfr;p~dXd=gIw$AO-Y zpzr-S1afL|XH92%t{Yc5g^KIHgj}KEDpdJ0wvFMYq6(dL)urr=EyB28$u+^7P1YAI9$Ip8nb30UPTGxDH%FP|8+(dY5Z;(2 zfuYP?i+%vM-F^g4eK?x1kG>0uz>g${NV4+m#>nQkL}R;wS4RR6C*=H5CHXJ__w`mxz)>&7tzbEn~MCqp}9E_$~HLP1@GTz zI_!epN6)(a_*Q__>;=;PctZQcMU+FjjgtHhybYtBcusY0Cy6()u@@B$IYod;0;m=B zzU@lX{62=J*Od7Acbd&qv%So)U0F1LjNz?nWy}18;j4jgJ-hk!N3X%9*?w4#oVu`+ zeti$}ZKYf$nk41w{MfF&s@cV;Hw`^;Y;Wbx1-&x|AU|kQzuJa{4jl=ef!yxE zDwu8pHxP&Jaog|G1I?&MheQyQ@{95a~Fsp2Wu>>`A3O?=Jgka*6m&9|2%F)m>;QE_zG!&$bS? z)nKxmy7#=MKy;qmL^b@DziREO^mgTcQNGVhiW&yLr}p{LqXTy*=35MgHwGx;qP~VX z1C+&!UXFeeP6CNO(D7S7=&ST87&V~f&)RJ1o;uu*roim4lUK1q1j~%Ep2b z0=vJE--CUUB_CHalpO`V_pR z_?5fwdoY=|RDxV=Rekh^oE&)S-CNk#Mu(PkzoW0+cQ3w5J?-df_eH=ZZ-+*xejXUg z=YqICW1@M_7Ujy4G|O1VUJmPB@TZ-|MuI=By1z2LXpZ7}5hs|mMj5IwCwD=QwF-qj9EgEpyK z>jMQ*$_Eb(jOh11wY{XKD6J1A^u0uLWA0JIigGU=5MIYH@Y~}aVU03%Y2V1!KaiP^ zZIz>xF74}j?>TCXNnKWRw;&4n?EQpBV0+EC+L{#Ti5}E?__^rQRG%pVhK87YhQ4_U zLlm!(1n<94g}!-uRr}^C`Vw@UP_dAPmiwJSf^nT}`Vdik=|lkK&<{9$c%mDS#BLpP z91y!T5iG}C3Pi1axaGpY^fECEll(huYka~4G#!K4*Gy;CY-G{BWEE<&@0IY$#aZ<; z+Hm{W6G|@%#Q4vIb##cL4??%mdoSD-LR={R;vvI+{5^F_Hx&;*%VMg5NI~lX?s zraQ0pQ9dp1>7p-V=mddbV*bId_wda5ta=0XAU=HxnT}d9pXk@|%f4IMh6w#ZT)c#+ znntHq)PED*(DRZ!ZQtv!m;KS}sL~HL)UmTm8%38P6W%WfYUxh@7`!F`rSLWijQ2d- zPs58KPErgG^De`BlMTg0lIcq3viKM`x(e2^kKp3eoo{2)uU+O+o?2F_haRsjuQ4cU zX&hg(JLch`9SmPpDB~StBabC;sh}9wl%PE7=pQ+8l$OjHrEGH~DM`+3C}aA2WxZ1A z%;bA2?>Te&Em)9!-`tGpa~DXqIn&d`jG2@%TbwanoRO8G#I2kiS^CJz!sRPREL!~F z?$s+thwa|E`d;4EvQFzf+@rx>Ivl3M2|Apn!}&Ta)!`Z)`gB;O!yP(&U5BT1ctMBF z8gy~3dPa;_LtvZ^OLVwFhkw@L-*vc0hX-}|jt;-l;ZHi`E3|$)>u`_`?_|&gSA(80 zONWbe_=pZS>F^Ia+^NF@I&9G4X&oN&heBTc%=3dLdS1Hp=Ahlu-F&FnxBb~iEt0XLfE{IBFxdl*3a;KxG5BR5|F|k#3`IX zxZ{EbDSklDqxkK?MFnjQKG6%C0H-6ojIaw~H3B&)b|7rkpG15qu#L&wG_uC=;_HioTtLIn-lfBXzbXGnPk*h$>%S@gPnML%=l+k^^gmfrHa_=%yQcril4|2~|EFvE zpDZakOzH1`TT@P1y1oyPO)D;0Q7GmwFDNeL&T(mFh53%c%%bAL9Q2{3rj{(6vGAe7 zMGhPFtL0^vl`JY;v4Z7flovVlH1{N*S+u->^~pWU;+aKdD;(5eMmcrKO(}Gwl`LC^ z0i~kl^XW~imbH9ENpWFTNkL(IC6+V8=_qwN(z5m3jO8nf%1V|mD_riFn_pIxzfeOI zoe~ylXx@eix5zL>d3Qsf4h0PMQru4%UBw2}1cv#=+4+to)AN@VPJTq2dk*JhWzORE zqBeA;{1uLjva*sgj(eKw07+*tqY-t0TF>0d)jDHjK~2KNR!~p|N-_LhS{9OGVScGT zrCE;rGKaI&zI<_sPK;TO0)0WaULDZQ?80TGgfBygTH1=)zdPb6o>^81lC9`K@tK8M zNf%?5v)EBI`4LCq?2??Kg2J>V`M{p*d#|&w>=7ENY+1gesAPHj(xOm#VMF)P95)N= z<18x!fzyl1u;?XakFar(|8I#u)1Ov&CRCth&Mqlh?kFtFD5vQ?BXs z3P)MVBb4PTS%nN*Hjq94LB?=;baDx+oLyMB^tM7SyJW@fmD`Z8F9##qX`67}4AUJm z3-b$Tz8tp=DTb1kdaAn+(m$lFH%3!a8talMSVO9_mqjxJCIe6Bm|X$~KmE z$TKwQ?aTN)T;RkV%2@%=1Dx=G;kz7|k=fBpE?#M;+)?W`#c?)kJ4DAgDj*#a2L~(tVEJR-o zT$J+k#vB+g%GVpu8M=OKJc1s z#m1n>BY2U~gaRL63j)o6;B>e-D1IGq!2k`3rGVs4BMK4R7K^%w6RaP|aonFY*ot^h z#0gdo;W#Ve1nUuKG6W~PaGIvXWWYBNQc>w!z#?4yP#g~jxt9=TBTn!cC^!t#6^awQW4Xp#1YLIgP{CZl<$9dpdOiLJz@2)WV6PGl z(Kx_*gq}c-;PYh~a}m6ZK--UB0V$0@{S%y^$5Q~OX>k{e6zT;8*XePBTlIKH2bKY$ z+n+V~ydEd`HUg29py<@%1fN9ci~0n+tkkHu3$PU-4*58gaf4PBfb>YY(uo1z5IKjV_gVdNY!JiRmmJB|^asL7mM*!EZh73SI1bqmzx9-k=c876y=Pg%E ze6C`u8acdaTlkBqFN!ZNd9n0G?j`Z1s+}+I+_kf2XYJ18I~#YN+1a#H-PydeWheI( z#EdehYEtZyZSHLww#8K^R3}xZR*Tiy)$^+Js%xu{S2tFlscx#?eX43S|8zps{|6af BI<5c! delta 20508 zcmbt+4R}n~*8e$^NhBf81cUk<3=)b&sSH6T63#FoJ}O$NCP)M!X);5!+8JijI>xkm zkM`{#5XoU`2~)vkYoLwWiKCj|BCsjF(EBLGTv=P zISa?-3@n+O>&oT0-?{zB9PX~tqpzo7v%f# zd%Zl%&^P|Snsq}q1^)(?sKL|dIGjH^qmaQGT{VO<4q<+OYqKOxwT7u6$9>5%x#8L>x=_{+EQm*Imv#>B(5 zW{2?3Au*7J*{50KM9FDQa!QAtK@=R)_((CBZA}rSFPzd5hg6XveXeAJC^0Z5vObJ= zZxf|)=s3)wRyn1*+D+4GQ6N(+KjAX14v%;iO|htV6S0mk5z|XIMBhQntOIBlC(6|k z9r;W0#fbhw`C(T}QTp5|?GGs}I8L74Vt~hw1vIYnU#y^bo?|as(2v(C-EhVq5pDZR zrikT--T$X`b(r0>E>~+TsGZYRC;0PKRTV%QTV`m6+$r*5!>l{YKm9sLzTc^q0~1eaTr?5K#7 zK#+ZPnl)h+C?d?gz+`72W6D79j*tLRx}`*;lZcumA~z)?V6;+gq(`s52C}JIr0Pru zdRxTGi$*cfDngXbs?#DO9llQviM~%v8Pabc!ao_y2))7JnA|K~qgf=n?V?BB?OECkS%*s*y0WrXf@ik6cg|Je25R{O5r({T#4yi_zju0#3 zNWJ+6RjrTU-C!3nCNK?q>^qn;>wwTwezWx$`9#bY@~t)*yY-t6g?IacW0KJkvK(pVNz-R}_6u!@_?&fD8lg`6-$zenXQZKlf$ z+x8fJ?xw0b1CN^~1Y3<3!zN(+Z-Ezzc`8JdzvGlj8?}lt-M)(~v3)oF4t2Y(s@wOp zjp6xI+Yhxn!W(+dk=M1i^7qQS+s`zdnj^RGu!c|DzPZC>-e9~X*LQrYT{@O3>}(ME z3)oYY7C-E!yz`#N`DOCpPMQ3y?Y>Skcn|R@fp~OGgdM%t*OQYfmG+p@88jN0yzf^! zT~XDw;6eh7p;WWtrWe(vk*gw^bvPg$S#gsCt4cdmUXAb`saR$5UK;H^>|G`Ddx-V} zsOEUCw0GszR^B5QSDB{#G+oiL|ya*yydC?;J4jD9gDL*WfUjtV)3kLf| z<}1!ARf*DRQTo&w>>ZVjKCq9D-9JG?was!!Unpc!9OO7ET-_Z~wG*8E3^YLDdfMx& zdDo|ec@9I@R%W1kH)ni(&ysFV{+HVK(i7|9e24%D6x559D?|FW_A11$x{K4`&6mwx zdmE-+my^2=H4M&|OS=y0LZq#@wh>d15)N`opJqtaAV4+0vrw+++TJ6Q_`w}TfZd5F z66-^^$$l~BlY)f4ry_Q@7`qG6gn}XFz)&%&`8VW7hHi75+a;L7_dY}o)2x|7Jjcwh z!*<*uq#hTtDuh^=L$c3KiK!CVJn3fQiS)#Z2h$S|tLEB;7%2=hxx1ob53_0JB|t*a zGFgS-FGF>CMcE9}l^LOFXd~<#Vgh>Gc4| zr3pKS#M+apgr{D{g4hm+BG6r`RLu)9hqNCy37dPw5c&}f?7Jwk%CJ>u2&vVi`j=9 zqSmww8Yzbq{uE)Ps@f&V;ic&y7?F)O7<_{ALr7JrAK4y<)3(FvMQe9AHZnSAzziTd zgt%3>KXlkm3I2PCIW6tNPEUjwjOj5ACTC}>NT==Ql9sE&FcKeXz^?BF$3V3CbC^7G z-75}m1cGFXBufm!&R5xuoUaEOg*&MciGe5?#Ol;YtuJ$9sV26?sU4MffhDz3e^u4p z2yeKypT&a?czKB7t^^ChfiKhLwwA{XhYRFqE#G*C7cg0h1TTgpR@BBQ58YzYs41ZD zVA27EIWE|`&qK~@P&a09$SQ+77Ex*~`tl=b30J4k#MhNuU(>dVfw7VP!>$&@kX97b zR4VsiT;`j|3i zN0WCR){^-O1OWS$t>C_H@d)XAERqL_B1jC)1bwg+BE^mOI|K4NJrWGZzLGEXc-*k; zXL(l70fslflY>2b8D{+~zt^+OaO;|UZ?9Jk!|ZZRuRew=U&@yvI?5)iWdsIE3WWED zVe&PExx+ymSOw)9RK@X5zyyZ`zQh>Oz!{9`4JVO!Lb+sz1fG;f_U$BJwDyYmv5@t4 z34w5>P&W1M(4h@%0JtRv!h@(2ZHL@#1L3O+( zTN&P!#9F>cm<4b~wa+@Go1|7oNU2l$u_}D;LOHR|J+s!JGext11)drY!MtK1#!ct| zvqlQ{B^1j9_6jDaHARSxDL|zV8$OxZsNtyqYRnM)c6Sh~!@DCxKiCNCLivq8{qL=X z#S|qwf>3M~GA7!qOos$SObfKeUYRCy+0n6wN$%LU$C%G@U<$*jcOC-8 z-ZN;>fDs$DiiH_C3Lqd4e}zW(HK?uuVo>C9QhGOL4N&gdK=`%=a*?&WoYFU$A0R)` zH>+b~(DeILS~`{1z-uGrP3K3`j}07G}0ld zp;u97Up>8tef8L$a%}%De5O3GfB%kFLVKUILPYP;vb{Oi{!onkX#Yw4$8u%=As#rn z6Xd?ZO2LnFI2?Ah@3zrB7yWv1u7_1~uZQ8UK0u8-lXfu4;j?g%sy#UCAn2k!QO!%y z1_o%5YW^96!CREm(VLp5G&Uy(+t^%W&0S7W@_a$Zh{wK##j3CE2ToCOA&QQD6cS}2 z)wWeu{-7%Yt9GIqHry%Qf+?86;%_qIN;YeSLo3$w%S_rX*1Rj4XGp))9@VUK(-~~V zwK5|&0ZTbRF^Y7|@i8W&AFm^r3B$77nuDQ~oD0w#{znnzGN&|5lpcx9_sugA3y9Xd zOC$nWk&eJPxl>%WXIQ>3IT~8vFu3dJ2$G|W`vwC9dyjL1AyR&%QyK{GgQ>aSogC;) zocF ztf9LA{RXi1-H?FBI+tcG+EZ{3;Tmhw6g@T~lneVKuK4vAv=yGlQ6G}=tGs4FU*mg} z`Cvc-|G2CS=;g^mJ72>f!9S5YV54(H$c0#)5=r|e`f^O%hjc^2Vsj!tlMcBe>Nq;F zt@xEkWoa9SbY~YeqZTtn>C4(N&iG%+#DW)Rm8md6__ekZSPMwiZ5!!jxg|G=>LLna1S%8nT7*Eo|1C z$k9cX=sR#n^zAni$KpW6^A0d!A8BswldMy%OYeI#+dbdcFxoXYe|I(*kug}8kY#l_dOPoP0fY!WyCDsf!{C}Ocd(0<%hxv|A)32z=7t8 zLE{LAT0yd5A;BU0n4lfyU?t(lzdE5(BM-Bgx0H0K8x55bhhx`6bbc5D48bfo%Vyjx zWO_N)M8a(sOc9~ozo{yb89I(|5T}Sq!#Wur`cYmuEJdDXd#LjQgf;AvtHK3^xUMWj zP@9M=v+(6(<-@iN!voXhj>88T?wu|>hX2)&d{h2uxV8N>g*xFHsk#LbkAAB8$~1Z4 zh>nKn!Lm4_YwUT%qs&cZGmla|OkZ{m)r=Hn=myK09~e|JxCilUzi*hR}MdEn@5PxeuS ze%ifc(}@i(ZUYn%Yug8)*w94e?`YPNj@fa92%48@Z5{=nI{j?lk_|{ivEv^O!{#Yu zbAsWI04Uc!Mc+URF<^2AQX(B9UqcH#P1vA$l-AG&K-fyw7Tlv7tw5%U<{nFI44T_( zElqmMKYuEZwBHjqO`~Wcqe#E)2+gBWC`iRsC@wK7LO1TnB`K3Uo3PCknI<(Jd5<~x z8s�jFJjmjDX}l0HB1iD|8Z7|AJM&fFv|+iKfcC)Vn#WHfqCtueAvQG_qKP zMB){@ufe)*Xbn?ousqaiw`fd1g64ZQqWUnRZh|Pp;GxlI^}DM>xUC4-=J*}f96&MX zx7Nw$XLR!HB?(MaKZIUEvA%Y!XkZq$tuu(0sIz}eb`}TXT6;$PMKQR>T89p}oK=h` zv8upFtSkn0SQ}^$Y!~bw(NHk3SDexoE8-Z02K5=zHyP3uvai1(N`it&H`1jB)jZ3C zqVx%@X*<+YJts}9=*r!eN%45=YRUPM?a z;&+DalhQ79kU5}y#Vn%VDw2(rHds>tYfVHt<>;9Dr?a&AaKtw}$}>JT73 z8jDc#R0__)T|tJjejx3nU*m=^#{&DcfsLJS_vKh|J^GQALVml?!r_o-7h)m6)hQiY zps&3u#lmB25pyWF5Xim3yT*xNEX^hQ`oQmb#%jkzU%m;O@)4VaQFa!~Q{L}^c7aDC zoq=(%NQnN=c3gZgKy1ZD%di*Ymr@b){oPK%jO0$d-;6IWXO%%pE}c_K`ISd~-@WtZx9) z&L$8Hyk(_(4-)A!v}B)!#S=gwk9fkqmkFfpXU!`Msh{lO;9hMtLTXK-2PAJ`ToDVnGtd8#d?gU&W|K`c^v#UlES z<1&-Q&66%dC3kA-LdR<|OLL4XME;O^k-C$ttWGi2c1M1$!9!}j$kllyt;y9c^bK}q zwcnbHM#(LO@>3{6Iu+~Z>}~?8#&~V3E`g>u^0C?(*ur?jB3O3idlJ?iPZ0BP>gdhlhOSDFX5Vo zk)*E&*~)o@;p7QTHLwNI+RQ!(!=5sl`FcElg}zi(Wj~%aNxo=Nc%(5JNq9Adw|5k! z$&d+84<|co>$-#Ewvuv4lWL|+VDfr&CMCZJNJ7e3)VMOTJkzWN8IqzLgUiA7n``i0 z3ohEU?n1dr><*;S1C@qyN`=+}XTb6n69_SYn=~hmyS-0o+cObDMn*S9o-PhT6n9m5 zsTO3>5pl7Aq2L*zw;PExUoYtU82$V(2l~!V2DmagO7ZH)B;1W&txcneZE|Wuo&90V zXc)56`;|7I(c<6IMuxb(L5ud-zJqdoC&D05Q-|~ec`^z%m{Gn3SH`sJlwCRqSJqlJ z&&Je5>(f|vx^$78SP#|g1eP5WhTVk2;^DJ?$PmWdCPy|CR0=B}z;CDR6Jce0G@#9@ zW0ZMrDF^?7t$vW07>wC50$cr8)F>DG5@Ltm>^kidnM5a4i%g|*m0UEe{6;nm`8&l8 zFCgq}f#5P)@F${CP`u^6X%zlcDf%i9Jv4*~f2yz?O6G;~wx}fsQU@hPW&`u^P)-i` z_Y71=AX9EW!9S)?&iLMv>I5USsVC^y+oiIDnMZZ&~93 zMc8vPI%Lnuw5;+ocPhkborE|H&PIv#TPLGlh+SjNrS@1P%}N4QO#RkaA0x1id4rTy zZ&{b2=)GldFKyg)Cs@IzDnI9%4YNXSxfgWE`wYS;+ytFb2I00&3{WtPC*$&Zcy==OWpuTr z9un5=k31syUxpD)JQsQmKzRqz4soo3wsaSM(T|rfOw_QW8-XA}BFR@~fIFBU=aX(@ z39Uj<6zcg7qvLHHO>ZJida@- z3=$$5gM^5~QU%jGtH;DL8#HNy-V-~8_?Zbp{8Wn&Kfxr#hY@$l28hA87?&L%@jbYq z7|56*-DX@qt89hsC)dQ)hJk`VlWL@pc0;H%(8j@lY@`*zdOn3SBUG1)$x!_7aLP%J63V{;q~Mn}8*gcX z|0EE!*lpAgHxusIeESN|sDT6BQQAr2aBZ1bttkokfPLMe8E`<-(kj2TjPRgDiLb+0 z^vMHWcc^?1%IseHG@U`aYEOuo0#M+3??JqXper8w+)*r;xRW8>Av5ox{mzhn0EG@A z{TSXm1pfdKin1uAxoGqo%8gHSUbm$s&@bb8bwTBg2Ad*#IlAk(5MFcIPj zd<5Xfg)m+xMBlAQml?sO(dKVQrUV|#eKmAHyaqxUqwsVXVgdWE93mW&5H#O&FEsAy z?%Lui!iZ+<-Jvipg`+3v#KQ>j+YA2BaK|AA?Kr6%p-&HRIUw3TYFB^>+~=Y5Y%0ud zJB_fZ1s)4m8{5kR9~kaYncG^;B@YswuT0JsoGCG}GkI|0YRDL-O zh^@DZBQy)-?nLTHn&fK;ceV62L0#H9c&;fVcoQy}zBGB0vAoHK;_RRY9!2nt z-q8`TA85foTRDdej5-Y*Cip%>>0gV+x>b1yR%HsT%6M2616e9&Ef8&4q0HV< z(L(v>LUrwp9r$3rK396Er8=NBdrxzt8OX^#I{d6B$WZ1Tl^{K=zm{AKBwLg0Tqr$0rD} z;8pB+oUV6R&k*$zKGB(uo2{|LU5>nX3NqxyGZ|cG&0%l@bJ|UZuO?$Ym|1iCN7S2a z`bL7?+6?(x9l(vhqaQ4fw@M1OMTHE1)5{iwgwwd_fxZA z$_p28u^Xy~?$IQ~1&Vk!VsN5>1|mK)i`pz~Zj=QDVRK`MfX^42!diAcp*#ztNIF7H zq?pa+LA<8BE5xK#%u}U{K2L+e^%)9b?nyK$n+%R;-F|W5p(HVE4YTl~ukw%`UliFR zYup>XwkY>Y2of4;t9^zp01;n+ZjK&v8)?>#b$`KgNQlV{Mdc8H-=DKRIA~X;bi|XS zzH&q%#pFmxHN=Hl;GBf_jPfL6#}2C(cuYtinXSOuU_S>se7CW$r6>g^@`33_7|WKJ z2+<`8pfj)pLB*4Jav0l@_{%sFcrs25%u29(``wO!4f0pI()BM>pmL1Lpm{GD!FrsB z?7{R~IH<^P4(%3GI|T1fmzrTb6%0dL%lgr$LuZsAx}_nMBx8$}5gpJAqDl;|W2aPU ze3)ct^n!GPW3g(Z*vF^U$EmF5PW;p0B7K<&+w5JymOM zq9SNM-XD!xfDF9-ciV*?nAyAw$i_CfyHu|Hx2kUAMTjdtiaQiq0#UW^B7gwMyHWfyR?9~@WK`g+tzDq9P;kF;^DMdq?r#=z!w8orMA4}98H)!GQN_(` zp+9cAh+G1Lv!^GKwBR0U+}t}%c%KGc!>*}R^Y=E?V^+;p(CuRLD~CU2UmG;$ehy_#(s=?UYVul%H_N)Go`g9dNmv(glZlK-f7N(Mr#yaiXyKR%v^CeI@UD z(B9)+-YK2KNGfjQ&($IXS9`i6?B45q?G>Mz?Y_KEE+3!lvEw@*k;n6a7A~slJ-Y%97maN)IFi*X#jzMP|aTqNq?iSTp`#rHe_X~0$1 zVghjMi{3>P9O&x^6yYT~3$%xDtWe@RQtc&*`|F7_Ab35^u5JT0x4}u9LB#!^QbMZHGG4q!4NqZ;pOL|-ubjmwI2kmc71n~A z0R)-q&qx#nkY0ZbWq}L!j10qE&Oq-%0>J^(;9H%+p$Gb-j}I=`Po5&M&O0}yeu~AWk#zwtBbf}-yzD{8@S$T@I zJ#61-j|r;@LbIqhX#NW*fqr}ppiKWeUdq^=bUk|!!hO#T)ODn~;i#i4EgIM<{X%D8 zk_ciIjf$6HQlxL&0Yw*sxNEtiY8mLgnSV#XOX6Fh7x!%}DXEH=y`;;1<7P@o3EBz2P42149%GWDWiz7zYFrr_9t4 zTL>|n5aWSReu!kv2F3%6#3@67%}3z3lGy5sW$zHk(m|@+j||D}MpRM2Cx0`evq!sA zO>DYVZQE!??fXc2GNgmGDUSFC^4Zjnu2p}6K_wouaj?KQfX}dfzYn}r@c}H3(OTds zO6T!il0*89H21svFlius6mGWD$eP_yYv`%aLl>;WQAe8l5e8zfga_$@)e0=TU=_Zi z3)ZhQY}atXil@hmp-jvqtQ?QuxQrE6eouP<$ss{yPU430B0N6(T;QzoJo)#)=;rRY zTJY~-eb#uiPJu_^VeBTlX_FG^gSC8Lu8C82Vx{>+8Qox$oYVobLt+CmU=KsGOz3rB zlqm=R^=Z3fT6}F3c!G2AxA)R56oUiAINZjHwmX7vG6cX8aQ=))!;WvR z5qU)^C-JrmoeCG#s@k*vUP|du6*RwzQ6Ztn02K3^6gEQ6n9)FdOUzNy&VxRDtIiH# z`<1%Cd5B)<(q#uO{V)HGIHl{+zgD8>@OGpd(^+ppaC5N-bVSpH9GACX3Yi(Qf)xAR zjZ_DrIZ1{WHKCVblB9p&S-QQZwEE_Ob##rZ?vQ_*X^kdk!}BS_cgX#+`?AMPDh|M= zP{=^lWO}C65D7DJlk4}(RRQCS*J z3R3UNY!v3WKHSAr5(e)thD%6+F(KJIcW`N@p%7+je!Jt(bAH2bjIR$kj^^4^z4<~wR zda>~duwYO%9~?weRUrs-w^W>wjN^k7>*2O7kZNrvXn5md-e=8m?Ys^dIE8E|0|LpJ zWF%=;@_^dJQ>i6nl}Kfu-=mYK2+<3A^+hk{=ay3UdiOxkOzo8OCh8Qk8}#kL8yTm( zfY26*p@is3h;j`v7Kl*0TWpsKP_YMhX`E7^RoZ~4-A5Ae2S#@S*V%hM{?>poVl)6T zdcf->0rn8}*Cb%)8_Vm2`UKJ6))%_nQQm8o^L1gSN^a=5#i^ zvr|5k)6ewnYfRvwVpMnLHMvdhP{XFzWJhkofO}rYhezy_9(WxvW)GqZT=2Klzdiy# z-y$sI=U91buEq1i7WR<|B8F*QY3h-g1Z-Day;4`V>je&ZZ}oJ?z}?cv4?m-EZMgCw z0PbXJ&p>XwvR>gG0MJ;(r!Gx@<`JqwJ0gZDl6DE8np+`0fgq4g!?!+i`s_i5X++|*OIeFf|RzVhy!WLF4r{wH;UHJa8JZ~4D zAUg}DM>uVjxMq^yC`dL|!j;NO!4r+Rh4cTyKe#<=fsHr+7Z}I{AwVB&QhcjKZD1eb z7cFexql3e@%*46yhk3ZxKdU4`qyTI2g3pZ2vm4|$7mhJcV8ZCK-izq6FL5R?prVHy zR``rzy+xJ_M;Lk>mum{!w`>hx`+uKf8&?JKz)P5-pNVw8s#4eCGj zMOqbof;s-rET_ySCeWNX)HE~kQQhTdi#kp|e*?9_m^JW`%I6pzk3Vf1*Sr)+!T&j| zBP_-O=1iC4Gx$L7RI&qs0p|dc4KC9*!&BiCP)!%%o8`>n_}DoZskQ>QJTM5#4%~gq z&lP*$E7@fj@XH`B1w7*50oYHZ4!Hykf1eI>3Ci!g`t!>6ORm0Q2J=&LblLsU$@gjP zrr#%zFY6ZF`hKk#cfVXxmNY9Le^Op(DRD1$&0myf$tx)-Dj9BZ44TxKEh^3{$#u;y zT5QSBonM$Y*WxO&%qdz_T$ty|vy?7dJg1=5qI_}ZSV;?jq=~hG7U*j z%Z6oR`Hiw;S(g0qvT55pKQ<&{d!{#&_r&?M_DMQ)=y0YE7wYhF9X_MOjXHc=hac*& zMu-2c!z()cO^305Z9al^A}>M8 z|Bmz=(gvi$IM(-~e(S=?2n_uT{0pZdL7v6t1Unhg9__QXSIQNC%OE=T!B{ zFL4hCNa;1?DIG@YfGC_GrMGq1SZ;(Ks%Y%+ncnDgz|lxAB5guigtP+bFGxW>Pvz&E z<;NobT(dIO&;LXDQItt6lSrAgAGxzXua#w;wq*%`_`evkZbrq{EbT^6>KE z*2e!30@jp0(O#1|?%E2Dn=&e1yEdJkFEp(gtmW#d>+|AKysX1|9Y(&WP1{L_2^#cpV|Bz_9r|^+QHMKpcuhP)#@941oOWK49I!w`FrVjIU zsI|f^yk1_V!}U7cN)YpNJ9NZB9iG%-;{b|YuGgUvyV(5aKiNW>pZgE@@ITo?Hb3{D z?csm2h5G#e(H??>|K}~ly$1cr{|t`cd&t+N&Ym=GOkq)Jo|wCMZebp`k{eT!m+Q(K z3$L2Rt

    5i)K%ny&!Ln%g%A@bzEji(VV=}QefKf>1Fd>dYS9PkDb4GE*q1(m*vOK zFDZ4=fazsf9QPzYF3&ZlXwf1}=$yZJOrD*4Nkc6zEh@~*D4LtcaW6N+Ome%5-L5g2 z9Cw-_Q%h|?L2hAYuB%{T?xMWZW!l=ZxSv>^yRfMW z#N{q_EY2^obBi?0T%F;ZOyy~Li;9U}O!bw4dfE%=KfAfY@?%T#z>=#h zKP6AASss?*E_BULUFOQ0R+Ke=Zr+%J+!C%btjlC~Udb|=sbmogkearR>tmEht!dvm z$v74KcbAmpEq0~NFTti3l`O-&lZ;d4(CW^fDO69pm02`@u`916y^Q0s38xX$b2lF|+DT*^)Zk5n7GrBk`C$b`<#(2?yiw!;^oQp^Z&omZ?0AbI0XCQ&Va@z zxKhspdbk?BK=8fx9Jc|Dj2$>G9*G7O0VD5$sY9M%64F8B>j1lS(ulACjz>BQJi*6w zJi%9xXq$;bt%ed4gXd5hn=F_TZRBh1r1Tkh0Mz z8mB}FiSn-iRwET7Pw-cyGUN&N!*hcl`AR_gNS(HWU?iSEXvJ*+*CWw51plt%37$lv zz8=c-#d`{MCRl_-6$IPh+HVc=1Q+36a3k^rw;=66{yjjP|5$sR_c)yO7`F)GyvOiv zz>g@QJ;8Z{u?5IWfbR~*{u8ChaF>uM&)L=zLdgG8$#7?=urmwRs)T zvrjJ&Jgw)i0&;~~qbR^qBqCxNV7o<{R1*vpX?sj?OtGd~QvmajXaa(3^gO|r^gO}$ zwY-OAj_VZ!uj%=$MIfwM1hn!S#Bc;02_MXiso5XYhFF`>cv-0o0GN{Z!O!pe(T1qaj&m> zz2 Result { // Create the virtualenv at the given location. let virtualenv = virtualenv::create( @@ -60,6 +65,8 @@ pub fn create_venv( allow_existing, relocatable, seed, + upgradeable, + preview, )?; // Create the corresponding `PythonEnvironment`. diff --git a/crates/uv-virtualenv/src/virtualenv.rs b/crates/uv-virtualenv/src/virtualenv.rs index a641e5541..bb6db01a3 100644 --- a/crates/uv-virtualenv/src/virtualenv.rs +++ b/crates/uv-virtualenv/src/virtualenv.rs @@ -10,8 +10,10 @@ use fs_err::File; use itertools::Itertools; use tracing::debug; +use uv_configuration::PreviewMode; use uv_fs::{CWD, Simplified, cachedir}; use uv_pypi_types::Scheme; +use uv_python::managed::{PythonMinorVersionLink, create_link_to_executable}; use uv_python::{Interpreter, VirtualEnvironment}; use uv_shell::escape_posix_for_single_quotes; use uv_version::version; @@ -53,6 +55,8 @@ pub(crate) fn create( allow_existing: bool, relocatable: bool, seed: bool, + upgradeable: bool, + preview: PreviewMode, ) -> Result { // Determine the base Python executable; that is, the Python executable that should be // considered the "base" for the virtual environment. @@ -143,13 +147,49 @@ pub(crate) fn create( // Create a `.gitignore` file to ignore all files in the venv. fs::write(location.join(".gitignore"), "*")?; + let executable_target = if upgradeable && interpreter.is_standalone() { + if let Some(minor_version_link) = PythonMinorVersionLink::from_executable( + base_python.as_path(), + &interpreter.key(), + preview, + ) { + if !minor_version_link.exists() { + base_python.clone() + } else { + let debug_symlink_term = if cfg!(windows) { + "junction" + } else { + "symlink directory" + }; + debug!( + "Using {} {} instead of base Python path: {}", + debug_symlink_term, + &minor_version_link.symlink_directory.display(), + &base_python.display() + ); + minor_version_link.symlink_executable.clone() + } + } else { + base_python.clone() + } + } else { + base_python.clone() + }; + // Per PEP 405, the Python `home` is the parent directory of the interpreter. - let python_home = base_python.parent().ok_or_else(|| { - io::Error::new( - io::ErrorKind::NotFound, - "The Python interpreter needs to have a parent directory", - ) - })?; + // In preview mode, for standalone interpreters, this `home` value will include a + // symlink directory on Unix or junction on Windows to enable transparent Python patch + // upgrades. + let python_home = executable_target + .parent() + .ok_or_else(|| { + io::Error::new( + io::ErrorKind::NotFound, + "The Python interpreter needs to have a parent directory", + ) + })? + .to_path_buf(); + let python_home = python_home.as_path(); // Different names for the python interpreter fs::create_dir_all(&scripts)?; @@ -157,7 +197,7 @@ pub(crate) fn create( #[cfg(unix)] { - uv_fs::replace_symlink(&base_python, &executable)?; + uv_fs::replace_symlink(&executable_target, &executable)?; uv_fs::replace_symlink( "python", scripts.join(format!("python{}", interpreter.python_major())), @@ -184,91 +224,102 @@ pub(crate) fn create( } } - // No symlinking on Windows, at least not on a regular non-dev non-admin Windows install. + // On Windows, we use trampolines that point to an executable target. For standalone + // interpreters, this target path includes a minor version junction to enable + // transparent upgrades. if cfg!(windows) { - copy_launcher_windows( - WindowsExecutable::Python, - interpreter, - &base_python, - &scripts, - python_home, - )?; - - if interpreter.markers().implementation_name() == "graalpy" { - copy_launcher_windows( - WindowsExecutable::GraalPy, - interpreter, - &base_python, - &scripts, - python_home, - )?; - copy_launcher_windows( - WindowsExecutable::PythonMajor, - interpreter, - &base_python, - &scripts, - python_home, - )?; + if interpreter.is_standalone() { + let target = scripts.join(WindowsExecutable::Python.exe(interpreter)); + create_link_to_executable(target.as_path(), executable_target.clone()) + .map_err(Error::Python)?; + let targetw = scripts.join(WindowsExecutable::Pythonw.exe(interpreter)); + create_link_to_executable(targetw.as_path(), executable_target) + .map_err(Error::Python)?; } else { copy_launcher_windows( - WindowsExecutable::Pythonw, + WindowsExecutable::Python, interpreter, &base_python, &scripts, python_home, )?; - } - if interpreter.markers().implementation_name() == "pypy" { - copy_launcher_windows( - WindowsExecutable::PythonMajor, - interpreter, - &base_python, - &scripts, - python_home, - )?; - copy_launcher_windows( - WindowsExecutable::PythonMajorMinor, - interpreter, - &base_python, - &scripts, - python_home, - )?; - copy_launcher_windows( - WindowsExecutable::PyPy, - interpreter, - &base_python, - &scripts, - python_home, - )?; - copy_launcher_windows( - WindowsExecutable::PyPyMajor, - interpreter, - &base_python, - &scripts, - python_home, - )?; - copy_launcher_windows( - WindowsExecutable::PyPyMajorMinor, - interpreter, - &base_python, - &scripts, - python_home, - )?; - copy_launcher_windows( - WindowsExecutable::PyPyw, - interpreter, - &base_python, - &scripts, - python_home, - )?; - copy_launcher_windows( - WindowsExecutable::PyPyMajorMinorw, - interpreter, - &base_python, - &scripts, - python_home, - )?; + if interpreter.markers().implementation_name() == "graalpy" { + copy_launcher_windows( + WindowsExecutable::GraalPy, + interpreter, + &base_python, + &scripts, + python_home, + )?; + copy_launcher_windows( + WindowsExecutable::PythonMajor, + interpreter, + &base_python, + &scripts, + python_home, + )?; + } else { + copy_launcher_windows( + WindowsExecutable::Pythonw, + interpreter, + &base_python, + &scripts, + python_home, + )?; + } + + if interpreter.markers().implementation_name() == "pypy" { + copy_launcher_windows( + WindowsExecutable::PythonMajor, + interpreter, + &base_python, + &scripts, + python_home, + )?; + copy_launcher_windows( + WindowsExecutable::PythonMajorMinor, + interpreter, + &base_python, + &scripts, + python_home, + )?; + copy_launcher_windows( + WindowsExecutable::PyPy, + interpreter, + &base_python, + &scripts, + python_home, + )?; + copy_launcher_windows( + WindowsExecutable::PyPyMajor, + interpreter, + &base_python, + &scripts, + python_home, + )?; + copy_launcher_windows( + WindowsExecutable::PyPyMajorMinor, + interpreter, + &base_python, + &scripts, + python_home, + )?; + copy_launcher_windows( + WindowsExecutable::PyPyw, + interpreter, + &base_python, + &scripts, + python_home, + )?; + copy_launcher_windows( + WindowsExecutable::PyPyMajorMinorw, + interpreter, + &base_python, + &scripts, + python_home, + )?; + } } } diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml index b3accc211..de4fa3c38 100644 --- a/crates/uv/Cargo.toml +++ b/crates/uv/Cargo.toml @@ -70,10 +70,12 @@ clap = { workspace = true, features = ["derive", "string", "wrap_help"] } console = { workspace = true } ctrlc = { workspace = true } dotenvy = { workspace = true } +dunce = { workspace = true } flate2 = { workspace = true, default-features = false } fs-err = { workspace = true, features = ["tokio"] } futures = { workspace = true } http = { workspace = true } +indexmap = { workspace = true } indicatif = { workspace = true } indoc = { workspace = true } itertools = { workspace = true } diff --git a/crates/uv/src/commands/build_frontend.rs b/crates/uv/src/commands/build_frontend.rs index dd174ca06..9b97b40b1 100644 --- a/crates/uv/src/commands/build_frontend.rs +++ b/crates/uv/src/commands/build_frontend.rs @@ -499,6 +499,7 @@ async fn build_package( install_mirrors.python_install_mirror.as_deref(), install_mirrors.pypy_install_mirror.as_deref(), install_mirrors.python_downloads_json_url.as_deref(), + preview, ) .await? .into_interpreter(); diff --git a/crates/uv/src/commands/pip/check.rs b/crates/uv/src/commands/pip/check.rs index f504503af..bfbb20ee6 100644 --- a/crates/uv/src/commands/pip/check.rs +++ b/crates/uv/src/commands/pip/check.rs @@ -5,6 +5,7 @@ use anyhow::Result; use owo_colors::OwoColorize; use uv_cache::Cache; +use uv_configuration::PreviewMode; use uv_distribution_types::{Diagnostic, InstalledDist}; use uv_installer::{SitePackages, SitePackagesDiagnostic}; use uv_python::{EnvironmentPreference, PythonEnvironment, PythonRequest}; @@ -19,6 +20,7 @@ pub(crate) fn pip_check( system: bool, cache: &Cache, printer: Printer, + preview: PreviewMode, ) -> Result { let start = Instant::now(); @@ -27,6 +29,7 @@ pub(crate) fn pip_check( &python.map(PythonRequest::parse).unwrap_or_default(), EnvironmentPreference::from_system_flag(system, false), cache, + preview, )?; report_target_environment(&environment, cache, printer)?; diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs index 197455aae..db80c2a8a 100644 --- a/crates/uv/src/commands/pip/compile.rs +++ b/crates/uv/src/commands/pip/compile.rs @@ -271,7 +271,13 @@ pub(crate) async fn pip_compile( let environment_preference = EnvironmentPreference::from_system_flag(system, false); let interpreter = if let Some(python) = python.as_ref() { let request = PythonRequest::parse(python); - PythonInstallation::find(&request, environment_preference, python_preference, &cache) + PythonInstallation::find( + &request, + environment_preference, + python_preference, + &cache, + preview, + ) } else { // TODO(zanieb): The split here hints at a problem with the request abstraction; we should // be able to use `PythonInstallation::find(...)` here. @@ -281,7 +287,13 @@ pub(crate) async fn pip_compile( } else { PythonRequest::default() }; - PythonInstallation::find_best(&request, environment_preference, python_preference, &cache) + PythonInstallation::find_best( + &request, + environment_preference, + python_preference, + &cache, + preview, + ) }? .into_interpreter(); diff --git a/crates/uv/src/commands/pip/freeze.rs b/crates/uv/src/commands/pip/freeze.rs index 7ad5517af..8c8491d45 100644 --- a/crates/uv/src/commands/pip/freeze.rs +++ b/crates/uv/src/commands/pip/freeze.rs @@ -6,6 +6,7 @@ use itertools::Itertools; use owo_colors::OwoColorize; use uv_cache::Cache; +use uv_configuration::PreviewMode; use uv_distribution_types::{Diagnostic, InstalledDist, Name}; use uv_installer::SitePackages; use uv_python::{EnvironmentPreference, PythonEnvironment, PythonRequest}; @@ -23,12 +24,14 @@ pub(crate) fn pip_freeze( paths: Option>, cache: &Cache, printer: Printer, + preview: PreviewMode, ) -> Result { // Detect the current Python interpreter. let environment = PythonEnvironment::find( &python.map(PythonRequest::parse).unwrap_or_default(), EnvironmentPreference::from_system_flag(system, false), cache, + preview, )?; report_target_environment(&environment, cache, printer)?; diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs index e4f524c57..a92c36665 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -182,6 +182,7 @@ pub(crate) async fn pip_install( EnvironmentPreference::from_system_flag(system, false), python_preference, &cache, + preview, )?; report_interpreter(&installation, true, printer)?; PythonEnvironment::from_installation(installation) @@ -193,6 +194,7 @@ pub(crate) async fn pip_install( .unwrap_or_default(), EnvironmentPreference::from_system_flag(system, true), &cache, + preview, )?; report_target_environment(&environment, &cache, printer)?; environment diff --git a/crates/uv/src/commands/pip/list.rs b/crates/uv/src/commands/pip/list.rs index 824c9db2b..356574436 100644 --- a/crates/uv/src/commands/pip/list.rs +++ b/crates/uv/src/commands/pip/list.rs @@ -15,7 +15,7 @@ use uv_cache::{Cache, Refresh}; use uv_cache_info::Timestamp; use uv_cli::ListFormat; use uv_client::{BaseClientBuilder, RegistryClientBuilder}; -use uv_configuration::{Concurrency, IndexStrategy, KeyringProviderType}; +use uv_configuration::{Concurrency, IndexStrategy, KeyringProviderType, PreviewMode}; use uv_distribution_filename::DistFilename; use uv_distribution_types::{ Diagnostic, IndexCapabilities, IndexLocations, InstalledDist, Name, RequiresPython, @@ -54,6 +54,7 @@ pub(crate) async fn pip_list( system: bool, cache: &Cache, printer: Printer, + preview: PreviewMode, ) -> Result { // Disallow `--outdated` with `--format freeze`. if outdated && matches!(format, ListFormat::Freeze) { @@ -65,6 +66,7 @@ pub(crate) async fn pip_list( &python.map(PythonRequest::parse).unwrap_or_default(), EnvironmentPreference::from_system_flag(system, false), cache, + preview, )?; report_target_environment(&environment, cache, printer)?; diff --git a/crates/uv/src/commands/pip/show.rs b/crates/uv/src/commands/pip/show.rs index a77c29cd5..4d2b3c3a7 100644 --- a/crates/uv/src/commands/pip/show.rs +++ b/crates/uv/src/commands/pip/show.rs @@ -7,6 +7,7 @@ use owo_colors::OwoColorize; use rustc_hash::FxHashMap; use uv_cache::Cache; +use uv_configuration::PreviewMode; use uv_distribution_types::{Diagnostic, Name}; use uv_fs::Simplified; use uv_install_wheel::read_record_file; @@ -27,6 +28,7 @@ pub(crate) fn pip_show( files: bool, cache: &Cache, printer: Printer, + preview: PreviewMode, ) -> Result { if packages.is_empty() { #[allow(clippy::print_stderr)] @@ -46,6 +48,7 @@ pub(crate) fn pip_show( &python.map(PythonRequest::parse).unwrap_or_default(), EnvironmentPreference::from_system_flag(system, false), cache, + preview, )?; report_target_environment(&environment, cache, printer)?; diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index e5bf92ae4..e5145400a 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -157,6 +157,7 @@ pub(crate) async fn pip_sync( EnvironmentPreference::from_system_flag(system, false), python_preference, &cache, + preview, )?; report_interpreter(&installation, true, printer)?; PythonEnvironment::from_installation(installation) @@ -168,6 +169,7 @@ pub(crate) async fn pip_sync( .unwrap_or_default(), EnvironmentPreference::from_system_flag(system, true), &cache, + preview, )?; report_target_environment(&environment, &cache, printer)?; environment diff --git a/crates/uv/src/commands/pip/tree.rs b/crates/uv/src/commands/pip/tree.rs index aed364068..b0ba44c35 100644 --- a/crates/uv/src/commands/pip/tree.rs +++ b/crates/uv/src/commands/pip/tree.rs @@ -13,7 +13,7 @@ use tokio::sync::Semaphore; use uv_cache::{Cache, Refresh}; use uv_cache_info::Timestamp; use uv_client::{BaseClientBuilder, RegistryClientBuilder}; -use uv_configuration::{Concurrency, IndexStrategy, KeyringProviderType}; +use uv_configuration::{Concurrency, IndexStrategy, KeyringProviderType, PreviewMode}; use uv_distribution_types::{Diagnostic, IndexCapabilities, IndexLocations, Name, RequiresPython}; use uv_installer::SitePackages; use uv_normalize::PackageName; @@ -52,12 +52,14 @@ pub(crate) async fn pip_tree( system: bool, cache: &Cache, printer: Printer, + preview: PreviewMode, ) -> Result { // Detect the current Python interpreter. let environment = PythonEnvironment::find( &python.map(PythonRequest::parse).unwrap_or_default(), EnvironmentPreference::from_system_flag(system, false), cache, + preview, )?; report_target_environment(&environment, cache, printer)?; diff --git a/crates/uv/src/commands/pip/uninstall.rs b/crates/uv/src/commands/pip/uninstall.rs index 787ba5aae..4424fee37 100644 --- a/crates/uv/src/commands/pip/uninstall.rs +++ b/crates/uv/src/commands/pip/uninstall.rs @@ -7,7 +7,7 @@ use tracing::debug; use uv_cache::Cache; use uv_client::BaseClientBuilder; -use uv_configuration::{DryRun, KeyringProviderType}; +use uv_configuration::{DryRun, KeyringProviderType, PreviewMode}; use uv_distribution_types::Requirement; use uv_distribution_types::{InstalledMetadata, Name, UnresolvedRequirement}; use uv_fs::Simplified; @@ -37,6 +37,7 @@ pub(crate) async fn pip_uninstall( network_settings: &NetworkSettings, dry_run: DryRun, printer: Printer, + preview: PreviewMode, ) -> Result { let start = std::time::Instant::now(); @@ -57,6 +58,7 @@ pub(crate) async fn pip_uninstall( .unwrap_or_default(), EnvironmentPreference::from_system_flag(system, true), &cache, + preview, )?; report_target_environment(&environment, &cache, printer)?; diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index 1c5297f90..ae20b31d4 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -195,6 +195,7 @@ pub(crate) async fn add( &client_builder, cache, &reporter, + preview, ) .await?; Pep723Script::init(&path, requires_python.specifiers()).await? @@ -217,6 +218,7 @@ pub(crate) async fn add( active, cache, printer, + preview, ) .await? .into_interpreter(); @@ -286,6 +288,7 @@ pub(crate) async fn add( active, cache, printer, + preview, ) .await? .into_interpreter(); @@ -307,6 +310,7 @@ pub(crate) async fn add( cache, DryRun::Disabled, printer, + preview, ) .await? .into_environment()?; diff --git a/crates/uv/src/commands/project/environment.rs b/crates/uv/src/commands/project/environment.rs index f7ba006c5..da3dc7f63 100644 --- a/crates/uv/src/commands/project/environment.rs +++ b/crates/uv/src/commands/project/environment.rs @@ -7,7 +7,7 @@ use uv_cache_key::{cache_digest, hash_digest}; use uv_configuration::{Concurrency, Constraints, PreviewMode}; use uv_distribution_types::{Name, Resolution}; use uv_fs::PythonExt; -use uv_python::{Interpreter, PythonEnvironment}; +use uv_python::{Interpreter, PythonEnvironment, canonicalize_executable}; use crate::commands::pip::loggers::{InstallLogger, ResolveLogger}; use crate::commands::pip::operations::Modifications; @@ -74,7 +74,8 @@ impl CachedEnvironment { // Hash the interpreter based on its path. // TODO(charlie): Come up with a robust hash for the interpreter. - let interpreter_hash = cache_digest(&interpreter.sys_executable()); + let interpreter_hash = + cache_digest(&canonicalize_executable(interpreter.sys_executable())?); // Search in the content-addressed cache. let cache_entry = cache.entry(CacheBucket::Environments, interpreter_hash, resolution_hash); @@ -97,6 +98,8 @@ impl CachedEnvironment { false, true, false, + false, + preview, )?; sync_environment( diff --git a/crates/uv/src/commands/project/export.rs b/crates/uv/src/commands/project/export.rs index ac228989c..88a847d04 100644 --- a/crates/uv/src/commands/project/export.rs +++ b/crates/uv/src/commands/project/export.rs @@ -142,6 +142,7 @@ pub(crate) async fn export( Some(false), cache, printer, + preview, ) .await? .into_interpreter(), @@ -159,6 +160,7 @@ pub(crate) async fn export( Some(false), cache, printer, + preview, ) .await? .into_interpreter(), diff --git a/crates/uv/src/commands/project/init.rs b/crates/uv/src/commands/project/init.rs index 71aacdc1b..15fed409e 100644 --- a/crates/uv/src/commands/project/init.rs +++ b/crates/uv/src/commands/project/init.rs @@ -87,6 +87,7 @@ pub(crate) async fn init( pin_python, package, no_config, + preview, ) .await?; @@ -202,6 +203,7 @@ async fn init_script( pin_python: bool, package: bool, no_config: bool, + preview: PreviewMode, ) -> Result<()> { if no_workspace { warn_user_once!("`--no-workspace` is a no-op for Python scripts, which are standalone"); @@ -258,6 +260,7 @@ async fn init_script( &client_builder, cache, &reporter, + preview, ) .await?; @@ -434,6 +437,7 @@ async fn init_project( install_mirrors.python_install_mirror.as_deref(), install_mirrors.pypy_install_mirror.as_deref(), install_mirrors.python_downloads_json_url.as_deref(), + preview, ) .await? .into_interpreter(); @@ -461,6 +465,7 @@ async fn init_project( install_mirrors.python_install_mirror.as_deref(), install_mirrors.pypy_install_mirror.as_deref(), install_mirrors.python_downloads_json_url.as_deref(), + preview, ) .await? .into_interpreter(); @@ -527,6 +532,7 @@ async fn init_project( install_mirrors.python_install_mirror.as_deref(), install_mirrors.pypy_install_mirror.as_deref(), install_mirrors.python_downloads_json_url.as_deref(), + preview, ) .await? .into_interpreter(); @@ -554,6 +560,7 @@ async fn init_project( install_mirrors.python_install_mirror.as_deref(), install_mirrors.pypy_install_mirror.as_deref(), install_mirrors.python_downloads_json_url.as_deref(), + preview, ) .await? .into_interpreter(); diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index 1ab4441b0..97ee01767 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -114,6 +114,7 @@ pub(crate) async fn lock( &client_builder, cache, &reporter, + preview, ) .await?; Some(Pep723Script::init(&path, requires_python.specifiers()).await?) @@ -155,6 +156,7 @@ pub(crate) async fn lock( Some(false), cache, printer, + preview, ) .await? .into_interpreter(), @@ -170,6 +172,7 @@ pub(crate) async fn lock( Some(false), cache, printer, + preview, ) .await? .into_interpreter(), diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index 85defd4dd..a3249b11a 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -625,6 +625,7 @@ impl ScriptInterpreter { active: Option, cache: &Cache, printer: Printer, + preview: PreviewMode, ) -> Result { // For now, we assume that scripts are never evaluated in the context of a workspace. let workspace = None; @@ -682,6 +683,7 @@ impl ScriptInterpreter { install_mirrors.python_install_mirror.as_deref(), install_mirrors.pypy_install_mirror.as_deref(), install_mirrors.python_downloads_json_url.as_deref(), + preview, ) .await? .into_interpreter(); @@ -841,6 +843,7 @@ impl ProjectInterpreter { active: Option, cache: &Cache, printer: Printer, + preview: PreviewMode, ) -> Result { // Resolve the Python request and requirement for the workspace. let WorkspacePython { @@ -937,6 +940,7 @@ impl ProjectInterpreter { install_mirrors.python_install_mirror.as_deref(), install_mirrors.pypy_install_mirror.as_deref(), install_mirrors.python_downloads_json_url.as_deref(), + preview, ) .await?; @@ -1213,10 +1217,16 @@ impl ProjectEnvironment { cache: &Cache, dry_run: DryRun, printer: Printer, + preview: PreviewMode, ) -> Result { // Lock the project environment to avoid synchronization issues. let _lock = ProjectInterpreter::lock(workspace).await?; + let upgradeable = preview.is_enabled() + && python + .as_ref() + .is_none_or(|request| !request.includes_patch()); + match ProjectInterpreter::discover( workspace, workspace.install_path().as_ref(), @@ -1231,6 +1241,7 @@ impl ProjectEnvironment { active, cache, printer, + preview, ) .await? { @@ -1300,6 +1311,8 @@ impl ProjectEnvironment { false, false, false, + upgradeable, + preview, )?; return Ok(if replace { Self::WouldReplace(root, environment, temp_dir) @@ -1337,6 +1350,8 @@ impl ProjectEnvironment { false, false, false, + upgradeable, + preview, )?; if replace { @@ -1420,9 +1435,13 @@ impl ScriptEnvironment { cache: &Cache, dry_run: DryRun, printer: Printer, + preview: PreviewMode, ) -> Result { // Lock the script environment to avoid synchronization issues. let _lock = ScriptInterpreter::lock(script).await?; + let upgradeable = python_request + .as_ref() + .is_none_or(|request| !request.includes_patch()); match ScriptInterpreter::discover( script, @@ -1436,6 +1455,7 @@ impl ScriptEnvironment { active, cache, printer, + preview, ) .await? { @@ -1468,6 +1488,8 @@ impl ScriptEnvironment { false, false, false, + upgradeable, + preview, )?; return Ok(if root.exists() { Self::WouldReplace(root, environment, temp_dir) @@ -1502,6 +1524,8 @@ impl ScriptEnvironment { false, false, false, + upgradeable, + preview, )?; Ok(if replaced { @@ -2333,6 +2357,7 @@ pub(crate) async fn init_script_python_requirement( client_builder: &BaseClientBuilder<'_>, cache: &Cache, reporter: &PythonDownloadReporter, + preview: PreviewMode, ) -> anyhow::Result { let python_request = if let Some(request) = python { // (1) Explicit request from user @@ -2364,6 +2389,7 @@ pub(crate) async fn init_script_python_requirement( install_mirrors.python_install_mirror.as_deref(), install_mirrors.pypy_install_mirror.as_deref(), install_mirrors.python_downloads_json_url.as_deref(), + preview, ) .await? .into_interpreter(); diff --git a/crates/uv/src/commands/project/remove.rs b/crates/uv/src/commands/project/remove.rs index d17cd88ed..7fd02277e 100644 --- a/crates/uv/src/commands/project/remove.rs +++ b/crates/uv/src/commands/project/remove.rs @@ -229,6 +229,7 @@ pub(crate) async fn remove( active, cache, printer, + preview, ) .await? .into_interpreter(); @@ -250,6 +251,7 @@ pub(crate) async fn remove( cache, DryRun::Disabled, printer, + preview, ) .await? .into_environment()?; @@ -270,6 +272,7 @@ pub(crate) async fn remove( active, cache, printer, + preview, ) .await? .into_interpreter(); diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index f97ffdbc1..8a510fa8c 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -235,6 +235,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl cache, DryRun::Disabled, printer, + preview, ) .await? .into_environment()?; @@ -359,6 +360,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl cache, DryRun::Disabled, printer, + preview, ) .await? .into_environment()?; @@ -433,6 +435,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl active.map_or(Some(false), Some), cache, printer, + preview, ) .await? .into_interpreter(); @@ -446,6 +449,8 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl false, false, false, + false, + preview, )?; Some(environment.into_interpreter()) @@ -624,6 +629,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl install_mirrors.python_install_mirror.as_deref(), install_mirrors.pypy_install_mirror.as_deref(), install_mirrors.python_downloads_json_url.as_deref(), + preview, ) .await? .into_interpreter(); @@ -648,6 +654,8 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl false, false, false, + false, + preview, )? } else { // If we're not isolating the environment, reuse the base environment for the @@ -666,6 +674,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl cache, DryRun::Disabled, printer, + preview, ) .await? .into_environment()? @@ -850,6 +859,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl install_mirrors.python_install_mirror.as_deref(), install_mirrors.pypy_install_mirror.as_deref(), install_mirrors.python_downloads_json_url.as_deref(), + preview, ) .await?; @@ -869,6 +879,8 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl false, false, false, + false, + preview, )?; venv.into_interpreter() } else { diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index 940b3a653..f1a73b8c8 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -145,6 +145,7 @@ pub(crate) async fn sync( cache, dry_run, printer, + preview, ) .await?, ), @@ -162,6 +163,7 @@ pub(crate) async fn sync( cache, dry_run, printer, + preview, ) .await?, ), diff --git a/crates/uv/src/commands/project/tree.rs b/crates/uv/src/commands/project/tree.rs index 9c42a8a86..2ff6ad98e 100644 --- a/crates/uv/src/commands/project/tree.rs +++ b/crates/uv/src/commands/project/tree.rs @@ -97,6 +97,7 @@ pub(crate) async fn tree( Some(false), cache, printer, + preview, ) .await? .into_interpreter(), @@ -114,6 +115,7 @@ pub(crate) async fn tree( 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 f76744186..fdba41978 100644 --- a/crates/uv/src/commands/project/version.rs +++ b/crates/uv/src/commands/project/version.rs @@ -296,6 +296,7 @@ async fn print_frozen_version( active, cache, printer, + preview, ) .await? .into_interpreter(); @@ -403,6 +404,7 @@ async fn lock_and_sync( active, cache, printer, + preview, ) .await? .into_interpreter(); @@ -424,6 +426,7 @@ async fn lock_and_sync( cache, DryRun::Disabled, printer, + preview, ) .await? .into_environment()?; diff --git a/crates/uv/src/commands/python/find.rs b/crates/uv/src/commands/python/find.rs index 1e5693c65..e188e9d20 100644 --- a/crates/uv/src/commands/python/find.rs +++ b/crates/uv/src/commands/python/find.rs @@ -1,9 +1,9 @@ use anyhow::Result; use std::fmt::Write; use std::path::Path; -use uv_configuration::DependencyGroupsWithDefaults; use uv_cache::Cache; +use uv_configuration::{DependencyGroupsWithDefaults, PreviewMode}; use uv_fs::Simplified; use uv_python::{ EnvironmentPreference, PythonDownloads, PythonInstallation, PythonPreference, PythonRequest, @@ -32,6 +32,7 @@ pub(crate) async fn find( python_preference: PythonPreference, cache: &Cache, printer: Printer, + preview: PreviewMode, ) -> Result { let environment_preference = if system { EnvironmentPreference::OnlySystem @@ -77,6 +78,7 @@ pub(crate) async fn find( environment_preference, python_preference, cache, + preview, )?; // Warn if the discovered Python version is incompatible with the current workspace @@ -121,6 +123,7 @@ pub(crate) async fn find_script( no_config: bool, cache: &Cache, printer: Printer, + preview: PreviewMode, ) -> Result { let interpreter = match ScriptInterpreter::discover( script, @@ -134,6 +137,7 @@ pub(crate) async fn find_script( Some(false), cache, printer, + preview, ) .await { diff --git a/crates/uv/src/commands/python/install.rs b/crates/uv/src/commands/python/install.rs index 8f5beedc9..7ad96fffe 100644 --- a/crates/uv/src/commands/python/install.rs +++ b/crates/uv/src/commands/python/install.rs @@ -2,10 +2,12 @@ use std::borrow::Cow; use std::fmt::Write; use std::io::ErrorKind; use std::path::{Path, PathBuf}; +use std::str::FromStr; use anyhow::{Error, Result}; use futures::StreamExt; use futures::stream::FuturesUnordered; +use indexmap::IndexSet; use itertools::{Either, Itertools}; use owo_colors::OwoColorize; use rustc_hash::{FxHashMap, FxHashSet}; @@ -15,12 +17,13 @@ use uv_configuration::PreviewMode; use uv_fs::Simplified; use uv_python::downloads::{self, DownloadResult, ManagedPythonDownload, PythonDownloadRequest}; use uv_python::managed::{ - ManagedPythonInstallation, ManagedPythonInstallations, python_executable_dir, + ManagedPythonInstallation, ManagedPythonInstallations, PythonMinorVersionLink, + create_link_to_executable, python_executable_dir, }; use uv_python::platform::{Arch, Libc}; use uv_python::{ - PythonDownloads, PythonInstallationKey, PythonRequest, PythonVersionFile, - VersionFileDiscoveryOptions, VersionFilePreference, + PythonDownloads, PythonInstallationKey, PythonInstallationMinorVersionKey, PythonRequest, + PythonVersionFile, VersionFileDiscoveryOptions, VersionFilePreference, VersionRequest, }; use uv_shell::Shell; use uv_trampoline_builder::{Launcher, LauncherKind}; @@ -32,7 +35,7 @@ use crate::commands::{ExitStatus, elapsed}; use crate::printer::Printer; use crate::settings::NetworkSettings; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] struct InstallRequest { /// The original request from the user request: PythonRequest, @@ -82,6 +85,10 @@ impl InstallRequest { fn matches_installation(&self, installation: &ManagedPythonInstallation) -> bool { self.download_request.satisfied_by_key(installation.key()) } + + fn python_request(&self) -> &PythonRequest { + &self.request + } } impl std::fmt::Display for InstallRequest { @@ -132,6 +139,7 @@ pub(crate) async fn install( install_dir: Option, targets: Vec, reinstall: bool, + upgrade: bool, force: bool, python_install_mirror: Option, pypy_install_mirror: Option, @@ -153,34 +161,66 @@ pub(crate) async fn install( return Ok(ExitStatus::Failure); } + if upgrade && preview.is_disabled() { + warn_user!( + "`uv python upgrade` is experimental and may change without warning. Pass `--preview` to disable this warning" + ); + } + if default && targets.len() > 1 { anyhow::bail!("The `--default` flag cannot be used with multiple targets"); } + // Read the existing installations, lock the directory for the duration + let installations = ManagedPythonInstallations::from_settings(install_dir.clone())?.init()?; + let installations_dir = installations.root(); + let scratch_dir = installations.scratch(); + let _lock = installations.lock().await?; + let existing_installations: Vec<_> = installations + .find_all()? + .inspect(|installation| trace!("Found existing installation {}", installation.key())) + .collect(); + // Resolve the requests let mut is_default_install = false; + let mut is_unspecified_upgrade = false; let requests: Vec<_> = if targets.is_empty() { - PythonVersionFile::discover( - project_dir, - &VersionFileDiscoveryOptions::default() - .with_no_config(no_config) - .with_preference(VersionFilePreference::Versions), - ) - .await? - .map(PythonVersionFile::into_versions) - .unwrap_or_else(|| { - // If no version file is found and no requests were made - is_default_install = true; - vec![if reinstall { - // On bare `--reinstall`, reinstall all Python versions - PythonRequest::Any - } else { - PythonRequest::Default - }] - }) - .into_iter() - .map(|a| InstallRequest::new(a, python_downloads_json_url.as_deref())) - .collect::>>()? + if upgrade { + is_unspecified_upgrade = true; + let mut minor_version_requests = IndexSet::::default(); + for installation in &existing_installations { + let request = VersionRequest::major_minor_request_from_key(installation.key()); + if let Ok(request) = InstallRequest::new( + PythonRequest::Version(request), + python_downloads_json_url.as_deref(), + ) { + minor_version_requests.insert(request); + } + } + minor_version_requests.into_iter().collect::>() + } else { + PythonVersionFile::discover( + project_dir, + &VersionFileDiscoveryOptions::default() + .with_no_config(no_config) + .with_preference(VersionFilePreference::Versions), + ) + .await? + .map(PythonVersionFile::into_versions) + .unwrap_or_else(|| { + // If no version file is found and no requests were made + is_default_install = true; + vec![if reinstall { + // On bare `--reinstall`, reinstall all Python versions + PythonRequest::Any + } else { + PythonRequest::Default + }] + }) + .into_iter() + .map(|a| InstallRequest::new(a, python_downloads_json_url.as_deref())) + .collect::>>()? + } } else { targets .iter() @@ -190,18 +230,39 @@ pub(crate) async fn install( }; let Some(first_request) = requests.first() else { + if upgrade { + writeln!( + printer.stderr(), + "There are no installed versions to upgrade" + )?; + } return Ok(ExitStatus::Success); }; - // Read the existing installations, lock the directory for the duration - let installations = ManagedPythonInstallations::from_settings(install_dir)?.init()?; - let installations_dir = installations.root(); - let scratch_dir = installations.scratch(); - let _lock = installations.lock().await?; - let existing_installations: Vec<_> = installations - .find_all()? - .inspect(|installation| trace!("Found existing installation {}", installation.key())) - .collect(); + let requested_minor_versions = requests + .iter() + .filter_map(|request| { + if let PythonRequest::Version(VersionRequest::MajorMinor(major, minor, ..)) = + request.python_request() + { + uv_pep440::Version::from_str(&format!("{major}.{minor}")).ok() + } else { + None + } + }) + .collect::>(); + + if upgrade + && requests + .iter() + .any(|request| request.request.includes_patch()) + { + writeln!( + printer.stderr(), + "error: `uv python upgrade` only accepts minor versions" + )?; + return Ok(ExitStatus::Failure); + } // Find requests that are already satisfied let mut changelog = Changelog::default(); @@ -259,15 +320,20 @@ pub(crate) async fn install( } } } - (vec![], unsatisfied) } else { // If we can find one existing installation that matches the request, it is satisfied requests.iter().partition_map(|request| { - if let Some(installation) = existing_installations - .iter() - .find(|installation| request.matches_installation(installation)) - { + if let Some(installation) = existing_installations.iter().find(|installation| { + if upgrade { + // If this is an upgrade, the requested version is a minor version + // but the requested download is the highest patch for that minor + // version. We need to install it unless an exact match is found. + request.download.key() == installation.key() + } else { + request.matches_installation(installation) + } + }) { debug!( "Found `{}` for request `{}`", installation.key().green(), @@ -385,18 +451,24 @@ pub(crate) async fn install( .expect("We should have a bin directory with preview enabled") .as_path(); + let upgradeable = preview.is_enabled() && is_default_install + || requested_minor_versions.contains(&installation.key().version().python_version()); + create_bin_links( installation, bin, reinstall, force, default, + upgradeable, + upgrade, is_default_install, first_request, &existing_installations, &installations, &mut changelog, &mut errors, + preview, )?; if preview.is_enabled() { @@ -407,14 +479,51 @@ pub(crate) async fn install( } } + let minor_versions = + PythonInstallationMinorVersionKey::highest_installations_by_minor_version_key( + installations + .iter() + .copied() + .chain(existing_installations.iter()), + ); + + for installation in minor_versions.values() { + if upgrade { + // During an upgrade, update existing symlinks but avoid + // creating new ones. + installation.update_minor_version_link(preview)?; + } else { + installation.ensure_minor_version_link(preview)?; + } + } + if changelog.installed.is_empty() && errors.is_empty() { if is_default_install { writeln!( printer.stderr(), "Python is already installed. Use `uv python install ` to install another version.", )?; + } else if upgrade && requests.is_empty() { + writeln!( + printer.stderr(), + "There are no installed versions to upgrade" + )?; } else if requests.len() > 1 { - writeln!(printer.stderr(), "All requested versions already installed")?; + if upgrade { + if is_unspecified_upgrade { + writeln!( + printer.stderr(), + "All versions already on latest supported patch release" + )?; + } else { + writeln!( + printer.stderr(), + "All requested versions already on latest supported patch release" + )?; + } + } else { + writeln!(printer.stderr(), "All requested versions already installed")?; + } } return Ok(ExitStatus::Success); } @@ -520,12 +629,15 @@ fn create_bin_links( reinstall: bool, force: bool, default: bool, + upgradeable: bool, + upgrade: bool, is_default_install: bool, first_request: &InstallRequest, existing_installations: &[ManagedPythonInstallation], installations: &[&ManagedPythonInstallation], changelog: &mut Changelog, errors: &mut Vec<(PythonInstallationKey, Error)>, + preview: PreviewMode, ) -> Result<(), Error> { let targets = if (default || is_default_install) && first_request.matches_installation(installation) { @@ -540,7 +652,19 @@ fn create_bin_links( for target in targets { let target = bin.join(target); - match installation.create_bin_link(&target) { + let executable = if upgradeable { + if let Some(minor_version_link) = + PythonMinorVersionLink::from_installation(installation, preview) + { + minor_version_link.symlink_executable.clone() + } else { + installation.executable(false) + } + } else { + installation.executable(false) + }; + + match create_link_to_executable(&target, executable.clone()) { Ok(()) => { debug!( "Installed executable at `{}` for {}", @@ -589,13 +713,23 @@ fn create_bin_links( // There's an existing executable we don't manage, require `--force` if valid_link { if !force { - errors.push(( - installation.key().clone(), - anyhow::anyhow!( - "Executable already exists at `{}` but is not managed by uv; use `--force` to replace it", - to.simplified_display() - ), - )); + if upgrade { + warn_user!( + "Executable already exists at `{}` but is not managed by uv; use `uv python install {}.{}{} --force` to replace it", + to.simplified_display(), + installation.key().major(), + installation.key().minor(), + installation.key().variant().suffix() + ); + } else { + errors.push(( + installation.key().clone(), + anyhow::anyhow!( + "Executable already exists at `{}` but is not managed by uv; use `--force` to replace it", + to.simplified_display() + ), + )); + } continue; } debug!( @@ -676,7 +810,7 @@ fn create_bin_links( .remove(&target); } - installation.create_bin_link(&target)?; + create_link_to_executable(&target, executable)?; debug!( "Updated executable at `{}` to {}", target.simplified_display(), @@ -747,8 +881,7 @@ fn warn_if_not_on_path(bin: &Path) { /// Find the [`ManagedPythonInstallation`] corresponding to an executable link installed at the /// given path, if any. /// -/// Like [`ManagedPythonInstallation::is_bin_link`], but this method will only resolve the -/// given path one time. +/// Will resolve symlinks on Unix. On Windows, will resolve the target link for a trampoline. fn find_matching_bin_link<'a>( mut installations: impl Iterator, path: &Path, @@ -757,13 +890,13 @@ fn find_matching_bin_link<'a>( if !path.is_symlink() { return None; } - path.read_link().ok()? + fs_err::canonicalize(path).ok()? } else if cfg!(windows) { let launcher = Launcher::try_from_path(path).ok()??; if !matches!(launcher.kind, LauncherKind::Python) { return None; } - launcher.python_path + dunce::canonicalize(launcher.python_path).ok()? } else { unreachable!("Only Windows and Unix are supported") }; diff --git a/crates/uv/src/commands/python/list.rs b/crates/uv/src/commands/python/list.rs index 71bfb9c55..2cd54747c 100644 --- a/crates/uv/src/commands/python/list.rs +++ b/crates/uv/src/commands/python/list.rs @@ -2,6 +2,7 @@ use serde::Serialize; use std::collections::BTreeSet; use std::fmt::Write; use uv_cli::PythonListFormat; +use uv_configuration::PreviewMode; use uv_pep440::Version; use anyhow::Result; @@ -64,6 +65,7 @@ pub(crate) async fn list( python_downloads: PythonDownloads, cache: &Cache, printer: Printer, + preview: PreviewMode, ) -> Result { let request = request.as_deref().map(PythonRequest::parse); let base_download_request = if python_preference == PythonPreference::OnlySystem { @@ -124,6 +126,7 @@ pub(crate) async fn list( EnvironmentPreference::OnlySystem, python_preference, cache, + preview, ) // Raise discovery errors if critical .filter(|result| { diff --git a/crates/uv/src/commands/python/pin.rs b/crates/uv/src/commands/python/pin.rs index a0af7ec41..26714e4d7 100644 --- a/crates/uv/src/commands/python/pin.rs +++ b/crates/uv/src/commands/python/pin.rs @@ -8,7 +8,7 @@ use tracing::debug; use uv_cache::Cache; use uv_client::BaseClientBuilder; -use uv_configuration::DependencyGroupsWithDefaults; +use uv_configuration::{DependencyGroupsWithDefaults, PreviewMode}; use uv_dirs::user_uv_config_dir; use uv_fs::Simplified; use uv_python::{ @@ -40,6 +40,7 @@ pub(crate) async fn pin( network_settings: NetworkSettings, cache: &Cache, printer: Printer, + preview: PreviewMode, ) -> Result { let workspace_cache = WorkspaceCache::default(); let virtual_project = if no_project { @@ -94,6 +95,7 @@ pub(crate) async fn pin( virtual_project, python_preference, cache, + preview, ); } } @@ -124,6 +126,7 @@ pub(crate) async fn pin( install_mirrors.python_install_mirror.as_deref(), install_mirrors.pypy_install_mirror.as_deref(), install_mirrors.python_downloads_json_url.as_deref(), + preview, ) .await { @@ -260,6 +263,7 @@ fn warn_if_existing_pin_incompatible_with_project( virtual_project: &VirtualProject, python_preference: PythonPreference, cache: &Cache, + preview: PreviewMode, ) { // Check if the pinned version is compatible with the project. if let Some(pin_version) = pep440_version_from_request(pin) { @@ -284,6 +288,7 @@ fn warn_if_existing_pin_incompatible_with_project( EnvironmentPreference::OnlySystem, python_preference, cache, + preview, ) { Ok(python) => { let python_version = python.python_version(); diff --git a/crates/uv/src/commands/python/uninstall.rs b/crates/uv/src/commands/python/uninstall.rs index ac159344c..8a63b015c 100644 --- a/crates/uv/src/commands/python/uninstall.rs +++ b/crates/uv/src/commands/python/uninstall.rs @@ -5,6 +5,7 @@ use std::path::PathBuf; use anyhow::Result; use futures::StreamExt; use futures::stream::FuturesUnordered; +use indexmap::IndexSet; use itertools::Itertools; use owo_colors::OwoColorize; use rustc_hash::{FxHashMap, FxHashSet}; @@ -13,8 +14,10 @@ use tracing::{debug, warn}; use uv_configuration::PreviewMode; use uv_fs::Simplified; use uv_python::downloads::PythonDownloadRequest; -use uv_python::managed::{ManagedPythonInstallations, python_executable_dir}; -use uv_python::{PythonInstallationKey, PythonRequest}; +use uv_python::managed::{ + ManagedPythonInstallations, PythonMinorVersionLink, python_executable_dir, +}; +use uv_python::{PythonInstallationKey, PythonInstallationMinorVersionKey, PythonRequest}; use crate::commands::python::install::format_executables; use crate::commands::python::{ChangeEvent, ChangeEventKind}; @@ -87,7 +90,6 @@ async fn do_uninstall( // Always include pre-releases in uninstalls .map(|result| result.map(|request| request.with_prereleases(true))) .collect::>>()?; - let installed_installations: Vec<_> = installations.find_all()?.collect(); let mut matching_installations = BTreeSet::default(); for (request, download_request) in requests.iter().zip(download_requests) { @@ -218,6 +220,63 @@ async fn do_uninstall( uv_python::windows_registry::remove_orphan_registry_entries(&installed_installations); } + // Read all existing managed installations and find the highest installed patch + // for each installed minor version. Ensure the minor version link directory + // is still valid. + let uninstalled_minor_versions = &uninstalled.iter().fold( + IndexSet::<&PythonInstallationMinorVersionKey>::default(), + |mut minor_versions, key| { + minor_versions.insert(PythonInstallationMinorVersionKey::ref_cast(key)); + minor_versions + }, + ); + let remaining_installations: Vec<_> = installations.find_all()?.collect(); + let remaining_minor_versions = + PythonInstallationMinorVersionKey::highest_installations_by_minor_version_key( + remaining_installations.iter(), + ); + + for (_, installation) in remaining_minor_versions + .iter() + .filter(|(minor_version, _)| uninstalled_minor_versions.contains(minor_version)) + { + installation.ensure_minor_version_link(preview)?; + } + // For each uninstalled installation, check if there are no remaining installations + // for its minor version. If there are none remaining, remove the symlink directory + // (or junction on Windows) if it exists. + for installation in &matching_installations { + if !remaining_minor_versions.contains_key(installation.minor_version_key()) { + if let Some(minor_version_link) = + PythonMinorVersionLink::from_installation(installation, preview) + { + if minor_version_link.exists() { + let result = if cfg!(windows) { + fs_err::remove_dir(minor_version_link.symlink_directory.as_path()) + } else { + fs_err::remove_file(minor_version_link.symlink_directory.as_path()) + }; + if result.is_err() { + return Err(anyhow::anyhow!( + "Failed to remove symlink directory {}", + minor_version_link.symlink_directory.display() + )); + } + let symlink_term = if cfg!(windows) { + "junction" + } else { + "symlink directory" + }; + debug!( + "Removed {}: {}", + symlink_term, + minor_version_link.symlink_directory.to_string_lossy() + ); + } + } + } + } + // Report on any uninstalled installations. if !uninstalled.is_empty() { if let [uninstalled] = uninstalled.as_slice() { diff --git a/crates/uv/src/commands/tool/common.rs b/crates/uv/src/commands/tool/common.rs index 807225cbc..166b4fc6f 100644 --- a/crates/uv/src/commands/tool/common.rs +++ b/crates/uv/src/commands/tool/common.rs @@ -7,6 +7,7 @@ use std::{collections::BTreeSet, ffi::OsString}; use tracing::{debug, warn}; use uv_cache::Cache; use uv_client::BaseClientBuilder; +use uv_configuration::PreviewMode; use uv_distribution_types::Requirement; use uv_distribution_types::{InstalledDist, Name}; use uv_fs::Simplified; @@ -80,6 +81,7 @@ pub(crate) async fn refine_interpreter( python_preference: PythonPreference, python_downloads: PythonDownloads, cache: &Cache, + preview: PreviewMode, ) -> anyhow::Result, ProjectError> { let pip::operations::Error::Resolve(uv_resolver::ResolveError::NoSolution(no_solution_err)) = err @@ -151,6 +153,7 @@ pub(crate) async fn refine_interpreter( install_mirrors.python_install_mirror.as_deref(), install_mirrors.pypy_install_mirror.as_deref(), install_mirrors.python_downloads_json_url.as_deref(), + preview, ) .await? .into_interpreter(); diff --git a/crates/uv/src/commands/tool/install.rs b/crates/uv/src/commands/tool/install.rs index a65ad3af2..e816e771e 100644 --- a/crates/uv/src/commands/tool/install.rs +++ b/crates/uv/src/commands/tool/install.rs @@ -87,6 +87,7 @@ pub(crate) async fn install( install_mirrors.python_install_mirror.as_deref(), install_mirrors.pypy_install_mirror.as_deref(), install_mirrors.python_downloads_json_url.as_deref(), + preview, ) .await? .into_interpreter(); @@ -508,6 +509,7 @@ pub(crate) async fn install( python_preference, python_downloads, &cache, + preview, ) .await .ok() @@ -554,7 +556,7 @@ pub(crate) async fn install( }, }; - let environment = installed_tools.create_environment(&from.name, interpreter)?; + let environment = installed_tools.create_environment(&from.name, interpreter, preview)?; // At this point, we removed any existing environment, so we should remove any of its // executables. diff --git a/crates/uv/src/commands/tool/run.rs b/crates/uv/src/commands/tool/run.rs index 4d270c445..2746d65ad 100644 --- a/crates/uv/src/commands/tool/run.rs +++ b/crates/uv/src/commands/tool/run.rs @@ -747,6 +747,7 @@ async fn get_or_create_environment( install_mirrors.python_install_mirror.as_deref(), install_mirrors.pypy_install_mirror.as_deref(), install_mirrors.python_downloads_json_url.as_deref(), + preview, ) .await? .into_interpreter(); @@ -1036,6 +1037,7 @@ async fn get_or_create_environment( python_preference, python_downloads, cache, + preview, ) .await .ok() diff --git a/crates/uv/src/commands/tool/upgrade.rs b/crates/uv/src/commands/tool/upgrade.rs index c930ecada..166e00349 100644 --- a/crates/uv/src/commands/tool/upgrade.rs +++ b/crates/uv/src/commands/tool/upgrade.rs @@ -99,6 +99,7 @@ pub(crate) async fn upgrade( install_mirrors.python_install_mirror.as_deref(), install_mirrors.pypy_install_mirror.as_deref(), install_mirrors.python_downloads_json_url.as_deref(), + preview, ) .await? .into_interpreter(), @@ -308,7 +309,7 @@ async fn upgrade_tool( ) .await?; - let environment = installed_tools.create_environment(name, interpreter.clone())?; + let environment = installed_tools.create_environment(name, interpreter.clone(), preview)?; let environment = sync_environment( environment, diff --git a/crates/uv/src/commands/venv.rs b/crates/uv/src/commands/venv.rs index a50c0e155..fe20634d0 100644 --- a/crates/uv/src/commands/venv.rs +++ b/crates/uv/src/commands/venv.rs @@ -47,7 +47,7 @@ use super::project::default_dependency_groups; pub(crate) async fn venv( project_dir: &Path, path: Option, - python_request: Option<&str>, + python_request: Option, install_mirrors: PythonInstallMirrors, python_preference: PythonPreference, python_downloads: PythonDownloads, @@ -130,7 +130,7 @@ enum VenvError { async fn venv_impl( project_dir: &Path, path: Option, - python_request: Option<&str>, + python_request: Option, install_mirrors: PythonInstallMirrors, link_mode: LinkMode, index_locations: &IndexLocations, @@ -212,7 +212,7 @@ async fn venv_impl( python_request, requires_python, } = WorkspacePython::from_request( - python_request.map(PythonRequest::parse), + python_request, project.as_ref().map(VirtualProject::workspace), &groups, project_dir, @@ -234,6 +234,7 @@ async fn venv_impl( install_mirrors.python_install_mirror.as_deref(), install_mirrors.pypy_install_mirror.as_deref(), install_mirrors.python_downloads_json_url.as_deref(), + preview, ) .await .into_diagnostic()?; @@ -276,6 +277,11 @@ async fn venv_impl( ) .into_diagnostic()?; + let upgradeable = preview.is_enabled() + && python_request + .as_ref() + .is_none_or(|request| !request.includes_patch()); + // Create the virtual environment. let venv = uv_virtualenv::create_venv( &path, @@ -285,6 +291,8 @@ async fn venv_impl( allow_existing, relocatable, seed, + upgradeable, + preview, ) .map_err(VenvError::Creation)?; diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 51041bcbc..fd2e28fae 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -35,6 +35,7 @@ use uv_fs::{CWD, Simplified}; use uv_pep440::release_specifiers_to_ranges; use uv_pep508::VersionOrUrl; use uv_pypi_types::{ParsedDirectoryUrl, ParsedUrl}; +use uv_python::PythonRequest; use uv_requirements::RequirementsSource; use uv_requirements_txt::RequirementsTxtRequirement; use uv_scripts::{Pep723Error, Pep723Item, Pep723ItemRef, Pep723Metadata, Pep723Script}; @@ -793,6 +794,7 @@ async fn run(mut cli: Cli) -> Result { &globals.network_settings, args.dry_run, printer, + globals.preview, ) .await } @@ -814,6 +816,7 @@ async fn run(mut cli: Cli) -> Result { args.paths, &cache, printer, + globals.preview, ) } Commands::Pip(PipNamespace { @@ -845,6 +848,7 @@ async fn run(mut cli: Cli) -> Result { args.settings.system, &cache, printer, + globals.preview, ) .await } @@ -866,6 +870,7 @@ async fn run(mut cli: Cli) -> Result { args.files, &cache, printer, + globals.preview, ) } Commands::Pip(PipNamespace { @@ -897,6 +902,7 @@ async fn run(mut cli: Cli) -> Result { args.settings.system, &cache, printer, + globals.preview, ) .await } @@ -915,6 +921,7 @@ async fn run(mut cli: Cli) -> Result { args.settings.system, &cache, printer, + globals.preview, ) } Commands::Cache(CacheNamespace { @@ -1016,10 +1023,13 @@ async fn run(mut cli: Cli) -> Result { } }); + let python_request: Option = + args.settings.python.as_deref().map(PythonRequest::parse); + commands::venv( &project_dir, args.path, - args.settings.python.as_deref(), + python_request, args.settings.install_mirrors, globals.python_preference, globals.python_downloads, @@ -1370,6 +1380,7 @@ async fn run(mut cli: Cli) -> Result { globals.python_downloads, &cache, printer, + globals.preview, ) .await } @@ -1379,12 +1390,43 @@ async fn run(mut cli: Cli) -> Result { // Resolve the settings from the command-line arguments and workspace configuration. let args = settings::PythonInstallSettings::resolve(args, filesystem); show_settings!(args); + // TODO(john): If we later want to support `--upgrade`, we need to replace this. + let upgrade = false; commands::python_install( &project_dir, args.install_dir, args.targets, args.reinstall, + upgrade, + args.force, + args.python_install_mirror, + args.pypy_install_mirror, + args.python_downloads_json_url, + globals.network_settings, + args.default, + globals.python_downloads, + cli.top_level.no_config, + globals.preview, + printer, + ) + .await + } + Commands::Python(PythonNamespace { + command: PythonCommand::Upgrade(args), + }) => { + // Resolve the settings from the command-line arguments and workspace configuration. + let args = settings::PythonUpgradeSettings::resolve(args, filesystem); + show_settings!(args); + let reinstall = false; + let upgrade = true; + + commands::python_install( + &project_dir, + args.install_dir, + args.targets, + reinstall, + upgrade, args.force, args.python_install_mirror, args.pypy_install_mirror, @@ -1433,6 +1475,7 @@ async fn run(mut cli: Cli) -> Result { cli.top_level.no_config, &cache, printer, + globals.preview, ) .await } else { @@ -1446,6 +1489,7 @@ async fn run(mut cli: Cli) -> Result { globals.python_preference, &cache, printer, + globals.preview, ) .await } @@ -1472,6 +1516,7 @@ async fn run(mut cli: Cli) -> Result { globals.network_settings, &cache, printer, + globals.preview, ) .await } diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index fb1a62b41..5cbeb1886 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -10,9 +10,9 @@ use uv_cli::{ AddArgs, ColorChoice, ExternalCommand, GlobalArgs, InitArgs, ListFormat, LockArgs, Maybe, PipCheckArgs, PipCompileArgs, PipFreezeArgs, PipInstallArgs, PipListArgs, PipShowArgs, PipSyncArgs, PipTreeArgs, PipUninstallArgs, PythonFindArgs, PythonInstallArgs, PythonListArgs, - PythonListFormat, PythonPinArgs, PythonUninstallArgs, RemoveArgs, RunArgs, SyncArgs, - ToolDirArgs, ToolInstallArgs, ToolListArgs, ToolRunArgs, ToolUninstallArgs, TreeArgs, VenvArgs, - VersionArgs, VersionBump, VersionFormat, + PythonListFormat, PythonPinArgs, PythonUninstallArgs, PythonUpgradeArgs, RemoveArgs, RunArgs, + SyncArgs, ToolDirArgs, ToolInstallArgs, ToolListArgs, ToolRunArgs, ToolUninstallArgs, TreeArgs, + VenvArgs, VersionArgs, VersionBump, VersionFormat, }; use uv_cli::{ AuthorFrom, BuildArgs, ExportArgs, PublishArgs, PythonDirArgs, ResolverInstallerArgs, @@ -973,6 +973,59 @@ impl PythonInstallSettings { } } +/// The resolved settings to use for a `python upgrade` invocation. +#[allow(clippy::struct_excessive_bools)] +#[derive(Debug, Clone)] +pub(crate) struct PythonUpgradeSettings { + pub(crate) install_dir: Option, + pub(crate) targets: Vec, + pub(crate) force: bool, + pub(crate) python_install_mirror: Option, + pub(crate) pypy_install_mirror: Option, + pub(crate) python_downloads_json_url: Option, + pub(crate) default: bool, +} + +impl PythonUpgradeSettings { + /// Resolve the [`PythonUpgradeSettings`] from the CLI and filesystem configuration. + #[allow(clippy::needless_pass_by_value)] + pub(crate) fn resolve(args: PythonUpgradeArgs, filesystem: Option) -> Self { + let options = filesystem.map(FilesystemOptions::into_options); + let (python_mirror, pypy_mirror, python_downloads_json_url) = match options { + Some(options) => ( + options.install_mirrors.python_install_mirror, + options.install_mirrors.pypy_install_mirror, + options.install_mirrors.python_downloads_json_url, + ), + None => (None, None, None), + }; + let python_mirror = args.mirror.or(python_mirror); + let pypy_mirror = args.pypy_mirror.or(pypy_mirror); + let python_downloads_json_url = + args.python_downloads_json_url.or(python_downloads_json_url); + let force = false; + let default = false; + + let PythonUpgradeArgs { + install_dir, + targets, + mirror: _, + pypy_mirror: _, + python_downloads_json_url: _, + } = args; + + Self { + install_dir, + targets, + force, + python_install_mirror: python_mirror, + pypy_install_mirror: pypy_mirror, + python_downloads_json_url, + default, + } + } +} + /// The resolved settings to use for a `python uninstall` invocation. #[derive(Debug, Clone)] pub(crate) struct PythonUninstallSettings { diff --git a/crates/uv/tests/it/common/mod.rs b/crates/uv/tests/it/common/mod.rs index 7ef7cfff6..4d65aa4a3 100644 --- a/crates/uv/tests/it/common/mod.rs +++ b/crates/uv/tests/it/common/mod.rs @@ -22,6 +22,7 @@ use regex::Regex; use tokio::io::AsyncWriteExt; use uv_cache::Cache; +use uv_configuration::PreviewMode; use uv_fs::Simplified; use uv_python::managed::ManagedPythonInstallations; use uv_python::{ @@ -959,6 +960,14 @@ impl TestContext { command } + /// Create a `uv python upgrade` command with options shared across scenarios. + pub fn python_upgrade(&self) -> Command { + let mut command = self.new_command(); + self.add_shared_options(&mut command, true); + command.arg("python").arg("upgrade"); + command + } + /// Create a `uv python pin` command with options shared across scenarios. pub fn python_pin(&self) -> Command { let mut command = self.new_command(); @@ -1434,6 +1443,7 @@ pub fn python_installations_for_versions( EnvironmentPreference::OnlySystem, PythonPreference::Managed, &cache, + PreviewMode::Disabled, ) { python.into_interpreter().sys_executable().to_owned() } else { diff --git a/crates/uv/tests/it/help.rs b/crates/uv/tests/it/help.rs index 6fd9bd466..8faebd040 100644 --- a/crates/uv/tests/it/help.rs +++ b/crates/uv/tests/it/help.rs @@ -292,6 +292,8 @@ fn help_subcommand() { Commands: list List the available Python installations install Download and install Python versions + upgrade Upgrade installed Python versions to the latest supported patch release (requires the + `--preview` flag) find Search for a Python installation pin Pin to a specific Python version dir Show the uv Python installation directory @@ -719,6 +721,8 @@ fn help_flag_subcommand() { Commands: list List the available Python installations install Download and install Python versions + upgrade Upgrade installed Python versions to the latest supported patch release (requires the + `--preview` flag) find Search for a Python installation pin Pin to a specific Python version dir Show the uv Python installation directory @@ -915,6 +919,7 @@ fn help_unknown_subsubcommand() { error: There is no command `foobar` for `uv python`. Did you mean one of: list install + upgrade find pin dir diff --git a/crates/uv/tests/it/main.rs b/crates/uv/tests/it/main.rs index 7835fa461..872c88d4b 100644 --- a/crates/uv/tests/it/main.rs +++ b/crates/uv/tests/it/main.rs @@ -84,6 +84,9 @@ mod python_install; #[cfg(feature = "python")] mod python_pin; +#[cfg(feature = "python-managed")] +mod python_upgrade; + #[cfg(all(feature = "python", feature = "pypi"))] mod run; diff --git a/crates/uv/tests/it/python_install.rs b/crates/uv/tests/it/python_install.rs index 913711c7c..d5dec1977 100644 --- a/crates/uv/tests/it/python_install.rs +++ b/crates/uv/tests/it/python_install.rs @@ -1,3 +1,6 @@ +#[cfg(windows)] +use std::path::PathBuf; + use std::{env, path::Path, process::Command}; use crate::common::{TestContext, uv_snapshot}; @@ -8,6 +11,7 @@ use assert_fs::{ use indoc::indoc; use predicates::prelude::predicate; use tracing::debug; + use uv_fs::Simplified; use uv_static::EnvVars; @@ -351,6 +355,32 @@ fn python_install_preview() { #[cfg(unix)] bin_python.assert(predicate::path::is_symlink()); + // The link should be to a path containing a minor version symlink directory + #[cfg(unix)] + { + insta::with_settings!({ + filters => context.filters(), + }, { + insta::assert_snapshot!( + &bin_python + .read_link() + .unwrap_or_else(|_| panic!("{} should be readable", bin_python.path().display())) + .as_os_str().to_string_lossy(), + @"[TEMP_DIR]/managed/cpython-3.13-[PLATFORM]/bin/python3.13" + ); + }); + } + #[cfg(windows)] + { + insta::with_settings!({ + filters => context.filters(), + }, { + insta::assert_snapshot!( + launcher_path(&bin_python).display(), @"[TEMP_DIR]/managed/cpython-3.13-[PLATFORM]/python" + ); + }); + } + // The executable should "work" uv_snapshot!(context.filters(), Command::new(bin_python.as_os_str()) .arg("-c").arg("import subprocess; print('hello world')"), @r###" @@ -459,8 +489,60 @@ fn python_install_preview() { // The executable should be removed bin_python.assert(predicate::path::missing()); + // Install a minor version + uv_snapshot!(context.filters(), context.python_install().arg("3.11").arg("--preview"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.11.13 in [TIME] + + cpython-3.11.13-[PLATFORM] (python3.11) + "); + + let bin_python = context + .bin_dir + .child(format!("python3.11{}", std::env::consts::EXE_SUFFIX)); + + // The link should be to a path containing a minor version symlink directory + #[cfg(unix)] + { + insta::with_settings!({ + filters => context.filters(), + }, { + insta::assert_snapshot!( + &bin_python + .read_link() + .unwrap_or_else(|_| panic!("{} should be readable", bin_python.path().display())) + .as_os_str().to_string_lossy(), + @"[TEMP_DIR]/managed/cpython-3.11-[PLATFORM]/bin/python3.11" + ); + }); + } + #[cfg(windows)] + { + insta::with_settings!({ + filters => context.filters(), + }, { + insta::assert_snapshot!( + launcher_path(&bin_python).display(), @"[TEMP_DIR]/managed/cpython-3.11-[PLATFORM]/python" + ); + }); + } + + uv_snapshot!(context.filters(), context.python_uninstall().arg("3.11"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Searching for Python versions matching: Python 3.11 + Uninstalled Python 3.11.13 in [TIME] + - cpython-3.11.13-[PLATFORM] (python3.11) + "); + // Install multiple patch versions - uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.12.8").arg("3.12.6"), @r###" + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.12.8").arg("3.12.6"), @r" success: true exit_code: 0 ----- stdout ----- @@ -469,13 +551,13 @@ fn python_install_preview() { Installed 2 versions in [TIME] + cpython-3.12.6-[PLATFORM] + cpython-3.12.8-[PLATFORM] (python3.12) - "###); + "); let bin_python = context .bin_dir .child(format!("python3.12{}", std::env::consts::EXE_SUFFIX)); - // The link should be for the newer patch version + // The link should resolve to the newer patch version if cfg!(unix) { insta::with_settings!({ filters => context.filters(), @@ -517,6 +599,32 @@ fn python_install_preview_upgrade() { + cpython-3.12.5-[PLATFORM] (python3.12) "###); + // Installing with a patch version should cause the link to be to the patch installation. + #[cfg(unix)] + { + insta::with_settings!({ + filters => context.filters(), + }, { + insta::assert_snapshot!( + &bin_python + .read_link() + .unwrap_or_else(|_| panic!("{} should be readable", bin_python.display())) + .display(), + @"[TEMP_DIR]/managed/cpython-3.12.5-[PLATFORM]/bin/python3.12" + ); + }); + } + #[cfg(windows)] + { + insta::with_settings!({ + filters => context.filters(), + }, { + insta::assert_snapshot!( + launcher_path(&bin_python).display(), @"[TEMP_DIR]/managed/cpython-3.12.5-[PLATFORM]/python" + ); + }); + } + // Installing 3.12.4 should not replace the executable, but also shouldn't fail uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.12.4"), @r###" success: true @@ -1023,22 +1131,25 @@ fn python_install_default() { } } -fn read_link_path(path: &Path) -> String { - if cfg!(unix) { - path.read_link() - .unwrap_or_else(|_| panic!("{} should be readable", path.display())) - .simplified_display() - .to_string() - } else if cfg!(windows) { - let launcher = uv_trampoline_builder::Launcher::try_from_path(path) - .ok() - .unwrap_or_else(|| panic!("{} should be readable", path.display())) - .unwrap_or_else(|| panic!("{} should be a valid launcher", path.display())); +#[cfg(windows)] +fn launcher_path(path: &Path) -> PathBuf { + let launcher = uv_trampoline_builder::Launcher::try_from_path(path) + .unwrap_or_else(|_| panic!("{} should be readable", path.display())) + .unwrap_or_else(|| panic!("{} should be a valid launcher", path.display())); + launcher.python_path +} - launcher.python_path.simplified_display().to_string() - } else { - unreachable!() - } +fn read_link_path(path: &Path) -> String { + #[cfg(unix)] + let canonical_path = fs_err::canonicalize(path); + + #[cfg(windows)] + let canonical_path = dunce::canonicalize(launcher_path(path)); + + canonical_path + .unwrap_or_else(|_| panic!("{} should be readable", path.display())) + .simplified_display() + .to_string() } #[test] @@ -1486,3 +1597,557 @@ fn python_install_emulated_macos() { ----- stderr ----- "); } + +// A virtual environment should track the latest patch version installed. +#[test] +fn install_transparent_patch_upgrade_uv_venv() { + let context = TestContext::new_with_versions(&["3.13"]) + .with_filtered_python_keys() + .with_filtered_exe_suffix() + .with_managed_python_dirs() + .with_filtered_python_install_bin(); + + // Install a lower patch version. + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.12.9"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.12.9 in [TIME] + + cpython-3.12.9-[PLATFORM] (python3.12) + " + ); + + // Create a virtual environment. + uv_snapshot!(context.filters(), context.venv().arg("-p").arg("3.12") + .arg(context.venv.as_os_str()), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.9 + Creating virtual environment at: .venv + Activate with: source .venv/[BIN]/activate + " + ); + + uv_snapshot!(context.filters(), context.run().arg("python").arg("--version"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.12.9 + + ----- stderr ----- + " + ); + + // Install a higher patch version. + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.12.11"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.12.11 in [TIME] + + cpython-3.12.11-[PLATFORM] (python3.12) + " + ); + + // Virtual environment should reflect higher version. + uv_snapshot!(context.filters(), context.run().arg("python").arg("--version"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.12.11 + + ----- stderr ----- + " + ); + + // Install a lower patch version. + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.12.8"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.12.8 in [TIME] + + cpython-3.12.8-[PLATFORM] + " + ); + + // Virtual environment should reflect highest version. + uv_snapshot!(context.filters(), context.run().arg("python").arg("--version"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.12.11 + + ----- stderr ----- + " + ); +} + +// When installing multiple patches simultaneously, a virtual environment on that +// minor version should point to the highest. +#[test] +fn install_multiple_patches() { + let context = TestContext::new_with_versions(&[]) + .with_filtered_python_keys() + .with_filtered_exe_suffix() + .with_managed_python_dirs() + .with_filtered_python_install_bin(); + + // Install 3.12 patches in ascending order list + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.12.9").arg("3.12.11"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed 2 versions in [TIME] + + cpython-3.12.9-[PLATFORM] + + cpython-3.12.11-[PLATFORM] (python3.12) + " + ); + + // Create a virtual environment. + uv_snapshot!(context.filters(), context.venv().arg("-p").arg("3.12") + .arg(context.venv.as_os_str()), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.11 + Creating virtual environment at: .venv + Activate with: source .venv/[BIN]/activate + " + ); + + // Virtual environment should be on highest installed patch. + uv_snapshot!(context.filters(), context.run().arg("python").arg("--version"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.12.11 + + ----- stderr ----- + " + ); + + // Remove the original virtual environment + fs_err::remove_dir_all(&context.venv).unwrap(); + + // Install 3.10 patches in descending order list + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.10.17").arg("3.10.8"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed 2 versions in [TIME] + + cpython-3.10.8-[PLATFORM] + + cpython-3.10.17-[PLATFORM] (python3.10) + " + ); + + // Create a virtual environment on 3.10. + uv_snapshot!(context.filters(), context.venv().arg("-p").arg("3.10") + .arg(context.venv.as_os_str()), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.10.17 + Creating virtual environment at: .venv + Activate with: source .venv/[BIN]/activate + " + ); + + // Virtual environment should be on highest installed patch. + uv_snapshot!(context.filters(), context.run().arg("python").arg("--version"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.10.17 + + ----- stderr ----- + " + ); +} + +// After uninstalling the highest patch, a virtual environment should point to the +// next highest. +#[test] +fn uninstall_highest_patch() { + let context = TestContext::new_with_versions(&[]) + .with_filtered_python_keys() + .with_filtered_exe_suffix() + .with_managed_python_dirs() + .with_filtered_python_install_bin(); + + // Install patches in ascending order list + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.12.11").arg("3.12.9").arg("3.12.8"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed 3 versions in [TIME] + + cpython-3.12.8-[PLATFORM] + + cpython-3.12.9-[PLATFORM] + + cpython-3.12.11-[PLATFORM] (python3.12) + " + ); + + uv_snapshot!(context.filters(), context.venv().arg("-p").arg("3.12") + .arg(context.venv.as_os_str()), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.11 + Creating virtual environment at: .venv + Activate with: source .venv/[BIN]/activate + " + ); + + uv_snapshot!(context.filters(), context.run().arg("python").arg("--version"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.12.11 + + ----- stderr ----- + " + ); + + // Uninstall the highest patch version + uv_snapshot!(context.filters(), context.python_uninstall().arg("--preview").arg("3.12.11"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Searching for Python versions matching: Python 3.12.11 + Uninstalled Python 3.12.11 in [TIME] + - cpython-3.12.11-[PLATFORM] (python3.12) + " + ); + + // Virtual environment should be on highest patch version remaining. + uv_snapshot!(context.filters(), context.run().arg("python").arg("--version"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.12.9 + + ----- stderr ----- + " + ); +} + +// Virtual environments only record minor versions. `uv venv -p 3.x.y` will +// not prevent a virtual environment from tracking the latest patch version +// installed. +#[test] +fn install_no_transparent_upgrade_with_venv_patch_specification() { + let context = TestContext::new_with_versions(&["3.13"]) + .with_filtered_python_keys() + .with_filtered_exe_suffix() + .with_managed_python_dirs() + .with_filtered_python_install_bin(); + + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.12.9"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.12.9 in [TIME] + + cpython-3.12.9-[PLATFORM] (python3.12) + " + ); + + // Create a virtual environment with a patch version + uv_snapshot!(context.filters(), context.venv().arg("-p").arg("3.12.9") + .arg(context.venv.as_os_str()), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.9 + Creating virtual environment at: .venv + Activate with: source .venv/[BIN]/activate + " + ); + + uv_snapshot!(context.filters(), context.run().arg("python").arg("--version"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.12.9 + + ----- stderr ----- + " + ); + + // Install a higher patch version. + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.12.11"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.12.11 in [TIME] + + cpython-3.12.11-[PLATFORM] (python3.12) + " + ); + + // The virtual environment Python version is transparently upgraded. + uv_snapshot!(context.filters(), context.run().arg("python").arg("--version"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.12.9 + + ----- stderr ----- + " + ); +} + +// A virtual environment created using the `venv` module should track +// the latest patch version installed. +#[test] +fn install_transparent_patch_upgrade_venv_module() { + let context = TestContext::new_with_versions(&[]) + .with_filtered_python_keys() + .with_filtered_exe_suffix() + .with_managed_python_dirs() + .with_filtered_python_install_bin(); + + let bin_dir = context.temp_dir.child("bin"); + + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.12.9"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.12.9 in [TIME] + + cpython-3.12.9-[PLATFORM] (python3.12) + " + ); + + uv_snapshot!(context.filters(), context.run().arg("python").arg("--version"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.12.9 + + ----- stderr ----- + " + ); + + // Create a virtual environment using venv module. + uv_snapshot!(context.filters(), context.run().arg("python").arg("-m").arg("venv").arg(context.venv.as_os_str()).arg("--without-pip") + .env(EnvVars::PATH, bin_dir.as_os_str()), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + "); + + uv_snapshot!(context.filters(), context.run().arg("python").arg("--version"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.12.9 + + ----- stderr ----- + " + ); + + // Install a higher patch version + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.12.11"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.12.11 in [TIME] + + cpython-3.12.11-[PLATFORM] (python3.12) + " + ); + + // Virtual environment should reflect highest patch version. + uv_snapshot!(context.filters(), context.run().arg("python").arg("--version"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.12.11 + + ----- stderr ----- + " + ); +} + +// Automatically installing a lower patch version when running a command like +// `uv run` should not downgrade virtual environments. +#[test] +fn install_lower_patch_automatically() { + let context = TestContext::new_with_versions(&[]) + .with_filtered_python_keys() + .with_filtered_exe_suffix() + .with_managed_python_dirs() + .with_filtered_python_install_bin(); + + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.12.11"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.12.11 in [TIME] + + cpython-3.12.11-[PLATFORM] (python3.12) + " + ); + + uv_snapshot!(context.filters(), context.venv().arg("-p").arg("3.12") + .arg(context.venv.as_os_str()), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.11 + Creating virtual environment at: .venv + Activate with: source .venv/[BIN]/activate + " + ); + + uv_snapshot!(context.filters(), context.init().arg("-p").arg("3.12.9").arg("proj"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Initialized project `proj` at `[TEMP_DIR]/proj` + " + ); + + // Create a new virtual environment to trigger automatic installation of + // lower patch version + uv_snapshot!(context.filters(), context.venv() + .arg("--directory").arg("proj") + .arg("-p").arg("3.12.9"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.9 + Creating virtual environment at: .venv + Activate with: source .venv/[BIN]/activate + "); + + // Original virtual environment should still point to higher patch + uv_snapshot!(context.filters(), context.run().arg("python").arg("--version"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.12.11 + + ----- stderr ----- + " + ); +} + +#[test] +fn uninstall_last_patch() { + let context = TestContext::new_with_versions(&[]) + .with_filtered_python_keys() + .with_filtered_exe_suffix() + .with_managed_python_dirs() + .with_filtered_virtualenv_bin(); + + uv_snapshot!(context.filters(), context.python_install().arg("--preview").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) + " + ); + + uv_snapshot!(context.filters(), context.venv().arg("-p").arg("3.10"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.10.17 + Creating virtual environment at: .venv + Activate with: source .venv/[BIN]/activate + " + ); + + uv_snapshot!(context.filters(), context.run().arg("python").arg("--version"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.10.17 + + ----- stderr ----- + " + ); + + uv_snapshot!(context.filters(), context.python_uninstall().arg("--preview").arg("3.10.17"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Searching for Python versions matching: Python 3.10.17 + Uninstalled Python 3.10.17 in [TIME] + - cpython-3.10.17-[PLATFORM] (python3.10) + " + ); + + let mut filters = context.filters(); + filters.push(("python3", "python")); + + #[cfg(unix)] + uv_snapshot!(filters, context.run().arg("python").arg("--version"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Failed to inspect Python interpreter from active virtual environment at `.venv/[BIN]/python` + Caused by: Broken symlink at `.venv/[BIN]/python`, was the underlying Python interpreter removed? + + hint: Consider recreating the environment (e.g., with `uv venv`) + " + ); + + #[cfg(windows)] + uv_snapshot!(filters, context.run().arg("python").arg("--version"), @r#" + success: false + exit_code: 103 + ----- stdout ----- + + ----- stderr ----- + No Python at '"[TEMP_DIR]/managed/cpython-3.10-[PLATFORM]/python' + "# + ); +} diff --git a/crates/uv/tests/it/python_upgrade.rs b/crates/uv/tests/it/python_upgrade.rs new file mode 100644 index 000000000..cbea1d404 --- /dev/null +++ b/crates/uv/tests/it/python_upgrade.rs @@ -0,0 +1,703 @@ +use crate::common::{TestContext, uv_snapshot}; +use anyhow::Result; +use assert_fs::fixture::FileTouch; +use assert_fs::prelude::PathChild; + +use uv_static::EnvVars; + +#[test] +fn python_upgrade() { + let context: TestContext = TestContext::new_with_versions(&[]) + .with_filtered_python_keys() + .with_filtered_exe_suffix() + .with_managed_python_dirs(); + + // Install an earlier patch version + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.10.8"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.10.8 in [TIME] + + cpython-3.10.8-[PLATFORM] (python3.10) + "); + + // Don't accept patch version as argument to upgrade command + uv_snapshot!(context.filters(), context.python_upgrade().arg("--preview").arg("3.10.8"), @r" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + error: `uv python upgrade` only accepts minor versions + "); + + // Upgrade patch version + uv_snapshot!(context.filters(), context.python_upgrade().arg("--preview").arg("3.10"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.10.18 in [TIME] + + cpython-3.10.18-[PLATFORM] (python3.10) + "); + + // Should be a no-op when already upgraded + uv_snapshot!(context.filters(), context.python_upgrade().arg("--preview").arg("3.10"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + "); +} + +#[test] +fn python_upgrade_without_version() { + let context: TestContext = TestContext::new_with_versions(&[]) + .with_filtered_python_keys() + .with_filtered_exe_suffix() + .with_managed_python_dirs(); + + // Should be a no-op when no versions have been installed + uv_snapshot!(context.filters(), context.python_upgrade().arg("--preview"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + There are no installed versions to upgrade + "); + + // Install earlier patch versions for different minor versions + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.11.8").arg("3.12.8").arg("3.13.1"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed 3 versions in [TIME] + + cpython-3.11.8-[PLATFORM] (python3.11) + + cpython-3.12.8-[PLATFORM] (python3.12) + + cpython-3.13.1-[PLATFORM] (python3.13) + "); + + let mut filters = context.filters().clone(); + filters.push((r"3.13.\d+", "3.13.[X]")); + + // Upgrade one patch version + uv_snapshot!(filters, context.python_upgrade().arg("--preview").arg("3.13"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.13.[X] in [TIME] + + cpython-3.13.[X]-[PLATFORM] (python3.13) + "); + + // Providing no minor version to `uv python upgrade` should upgrade the rest + // of the patch versions + uv_snapshot!(context.filters(), context.python_upgrade().arg("--preview"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed 2 versions in [TIME] + + cpython-3.11.13-[PLATFORM] (python3.11) + + cpython-3.12.11-[PLATFORM] (python3.12) + "); + + // Should be a no-op when every version is already upgraded + uv_snapshot!(context.filters(), context.python_upgrade().arg("--preview"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + All versions already on latest supported patch release + "); +} + +#[test] +fn python_upgrade_transparent_from_venv() { + let context: TestContext = TestContext::new_with_versions(&["3.13"]) + .with_filtered_python_keys() + .with_filtered_exe_suffix() + .with_managed_python_dirs(); + + // Install an earlier patch version + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.10.8"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.10.8 in [TIME] + + cpython-3.10.8-[PLATFORM] (python3.10) + "); + + // Create a virtual environment + uv_snapshot!(context.filters(), context.venv(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.10.8 + Creating virtual environment at: .venv + Activate with: source .venv/[BIN]/activate + "); + + uv_snapshot!(context.filters(), context.run().arg("python").arg("--version"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.10.8 + + ----- stderr ----- + " + ); + + let second_venv = ".venv2"; + + // Create a second virtual environment with minor version request + uv_snapshot!(context.filters(), context.venv().arg(second_venv).arg("-p").arg("3.10"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.10.8 + Creating virtual environment at: .venv2 + Activate with: source .venv2/[BIN]/activate + "); + + uv_snapshot!(context.filters(), context.run().arg("python").arg("--version") + .env(EnvVars::VIRTUAL_ENV, second_venv), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.10.8 + + ----- stderr ----- + " + ); + + // Upgrade patch version + uv_snapshot!(context.filters(), context.python_upgrade().arg("--preview").arg("3.10"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.10.18 in [TIME] + + cpython-3.10.18-[PLATFORM] (python3.10) + "); + + // First virtual environment should reflect upgraded patch + uv_snapshot!(context.filters(), context.run().arg("python").arg("--version"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.10.18 + + ----- stderr ----- + " + ); + + // Second virtual environment should reflect upgraded patch + uv_snapshot!(context.filters(), context.run().arg("python").arg("--version") + .env(EnvVars::VIRTUAL_ENV, second_venv), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.10.18 + + ----- stderr ----- + " + ); +} + +// Installing Python in preview mode should not prevent virtual environments +// from transparently upgrading. +#[test] +fn python_upgrade_transparent_from_venv_preview() { + let context: TestContext = TestContext::new_with_versions(&["3.13"]) + .with_filtered_python_keys() + .with_filtered_exe_suffix() + .with_managed_python_dirs(); + + // Install an earlier patch version using `--preview` + uv_snapshot!(context.filters(), context.python_install().arg("3.10.8").arg("--preview"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.10.8 in [TIME] + + cpython-3.10.8-[PLATFORM] (python3.10) + "); + + // Create a virtual environment + uv_snapshot!(context.filters(), context.venv().arg("-p").arg("3.10"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.10.8 + Creating virtual environment at: .venv + Activate with: source .venv/[BIN]/activate + "); + + uv_snapshot!(context.filters(), context.run().arg("python").arg("--version"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.10.8 + + ----- stderr ----- + " + ); + + // Upgrade patch version + uv_snapshot!(context.filters(), context.python_upgrade().arg("--preview").arg("3.10"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.10.18 in [TIME] + + cpython-3.10.18-[PLATFORM] (python3.10) + "); + + // Virtual environment should reflect upgraded patch + uv_snapshot!(context.filters(), context.run().arg("python").arg("--version"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.10.18 + + ----- stderr ----- + " + ); +} + +#[test] +fn python_upgrade_ignored_with_python_pin() { + let context: TestContext = TestContext::new_with_versions(&["3.13"]) + .with_filtered_python_keys() + .with_filtered_exe_suffix() + .with_managed_python_dirs(); + + // Install an earlier patch version + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.10.8"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.10.8 in [TIME] + + cpython-3.10.8-[PLATFORM] (python3.10) + "); + + // Create a virtual environment + uv_snapshot!(context.filters(), context.venv(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.10.8 + Creating virtual environment at: .venv + Activate with: source .venv/[BIN]/activate + "); + + // Pin to older patch version + uv_snapshot!(context.filters(), context.python_pin().arg("3.10.8"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Pinned `.python-version` to `3.10.8` + + ----- stderr ----- + "); + + // Upgrade patch version + uv_snapshot!(context.filters(), context.python_upgrade().arg("--preview").arg("3.10"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.10.18 in [TIME] + + cpython-3.10.18-[PLATFORM] (python3.10) + "); + + // Virtual environment should continue to respect pinned patch version + uv_snapshot!(context.filters(), context.run().arg("python").arg("--version"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.10.8 + + ----- stderr ----- + " + ); +} + +// Virtual environments only record minor versions. `uv venv -p 3.x.y` will +// not prevent transparent upgrades. +#[test] +fn python_no_transparent_upgrade_with_venv_patch_specification() { + let context: TestContext = TestContext::new_with_versions(&["3.13"]) + .with_filtered_python_keys() + .with_filtered_exe_suffix() + .with_managed_python_dirs(); + + // Install an earlier patch version + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.10.8"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.10.8 in [TIME] + + cpython-3.10.8-[PLATFORM] (python3.10) + "); + + // Create a virtual environment with a patch version + uv_snapshot!(context.filters(), context.venv().arg("-p").arg("3.10.8"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.10.8 + Creating virtual environment at: .venv + Activate with: source .venv/[BIN]/activate + "); + + uv_snapshot!(context.filters(), context.run().arg("python").arg("--version"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.10.8 + + ----- stderr ----- + " + ); + + // Upgrade patch version + uv_snapshot!(context.filters(), context.python_upgrade().arg("--preview").arg("3.10"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.10.18 in [TIME] + + cpython-3.10.18-[PLATFORM] (python3.10) + "); + + // The virtual environment Python version is transparently upgraded. + uv_snapshot!(context.filters(), context.run().arg("python").arg("--version"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.10.8 + + ----- stderr ----- + " + ); +} + +// Transparent upgrades should work for virtual environments created within +// virtual environments. +#[test] +fn python_transparent_upgrade_venv_venv() { + let context: TestContext = TestContext::new_with_versions(&["3.13"]) + .with_filtered_python_keys() + .with_filtered_exe_suffix() + .with_filtered_virtualenv_bin() + .with_managed_python_dirs(); + + // Install an earlier patch version + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.10.8"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.10.8 in [TIME] + + cpython-3.10.8-[PLATFORM] (python3.10) + "); + + // Create an initial virtual environment + uv_snapshot!(context.filters(), context.venv().arg("-p").arg("3.10"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.10.8 + Creating virtual environment at: .venv + Activate with: source .venv/[BIN]/activate + "); + + let venv_python = if cfg!(windows) { + context.venv.child("Scripts/python.exe") + } else { + context.venv.child("bin/python") + }; + + let second_venv = ".venv2"; + + // Create a new virtual environment from within a virtual environment + uv_snapshot!(context.filters(), context.venv() + .arg(second_venv) + .arg("-p").arg(venv_python.as_os_str()), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.10.8 interpreter at: .venv/[BIN]/python + Creating virtual environment at: .venv2 + Activate with: source .venv2/[BIN]/activate + "); + + // Check version from within second virtual environment + uv_snapshot!(context.filters(), context.run() + .arg("python").arg("--version") + .env(EnvVars::VIRTUAL_ENV, second_venv), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.10.8 + + ----- stderr ----- + " + ); + + // Upgrade patch version + uv_snapshot!(context.filters(), context.python_upgrade().arg("--preview").arg("3.10"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.10.18 in [TIME] + + cpython-3.10.18-[PLATFORM] (python3.10) + "); + + // Should have transparently upgraded in second virtual environment + uv_snapshot!(context.filters(), context.run() + .arg("python").arg("--version") + .env(EnvVars::VIRTUAL_ENV, second_venv), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.10.18 + + ----- stderr ----- + " + ); +} + +// Transparent upgrades should work for virtual environments created using +// the `venv` module. +#[test] +fn python_upgrade_transparent_from_venv_module() { + let context = TestContext::new_with_versions(&["3.13"]) + .with_filtered_python_keys() + .with_filtered_exe_suffix() + .with_managed_python_dirs() + .with_filtered_python_install_bin(); + + let bin_dir = context.temp_dir.child("bin"); + + // Install earlier patch version + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.12.9"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.12.9 in [TIME] + + cpython-3.12.9-[PLATFORM] (python3.12) + "); + + // Create a virtual environment using venv module + uv_snapshot!(context.filters(), context.run().arg("python").arg("-m").arg("venv").arg(context.venv.as_os_str()).arg("--without-pip") + .env(EnvVars::PATH, bin_dir.as_os_str()), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + "); + + uv_snapshot!(context.filters(), context.run().arg("python").arg("--version"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.12.9 + + ----- stderr ----- + " + ); + + // Upgrade patch version + uv_snapshot!(context.filters(), context.python_upgrade().arg("--preview").arg("3.12"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.12.11 in [TIME] + + cpython-3.12.11-[PLATFORM] (python3.12) + " + ); + + // Virtual environment should reflect upgraded patch + uv_snapshot!(context.filters(), context.run().arg("python").arg("--version"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.12.11 + + ----- stderr ----- + " + ); +} + +// Transparent Python upgrades should work in environments created using +// the `venv` module within an existing virtual environment. +#[test] +fn python_upgrade_transparent_from_venv_module_in_venv() { + let context = TestContext::new_with_versions(&["3.13"]) + .with_filtered_python_keys() + .with_filtered_exe_suffix() + .with_managed_python_dirs() + .with_filtered_python_install_bin(); + + let bin_dir = context.temp_dir.child("bin"); + + // Install earlier patch version + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.10.8"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.10.8 in [TIME] + + cpython-3.10.8-[PLATFORM] (python3.10) + "); + + // Create first virtual environment + uv_snapshot!(context.filters(), context.venv().arg("-p").arg("3.10"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.10.8 + Creating virtual environment at: .venv + Activate with: source .venv/[BIN]/activate + "); + + let second_venv = ".venv2"; + + // Create a virtual environment using `venv`` module from within the first virtual environment. + uv_snapshot!(context.filters(), context.run() + .arg("python").arg("-m").arg("venv").arg(second_venv).arg("--without-pip") + .env(EnvVars::PATH, bin_dir.as_os_str()), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + "); + + // Check version within second virtual environment + uv_snapshot!(context.filters(), context.run() + .env(EnvVars::VIRTUAL_ENV, second_venv) + .arg("python").arg("--version"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.10.8 + + ----- stderr ----- + " + ); + + // Upgrade patch version + uv_snapshot!(context.filters(), context.python_upgrade().arg("--preview").arg("3.10"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.10.18 in [TIME] + + cpython-3.10.18-[PLATFORM] (python3.10) + " + ); + + // Second virtual environment should reflect upgraded patch. + uv_snapshot!(context.filters(), context.run() + .env(EnvVars::VIRTUAL_ENV, second_venv) + .arg("python").arg("--version"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.10.18 + + ----- stderr ----- + " + ); +} + +// Tests that `uv python upgrade 3.12` will warn if trying to install over non-managed +// interpreter. +#[test] +fn python_upgrade_force_install() -> Result<()> { + let context = TestContext::new_with_versions(&["3.13"]) + .with_filtered_python_keys() + .with_filtered_exe_suffix() + .with_managed_python_dirs(); + + context + .bin_dir + .child(format!("python3.12{}", std::env::consts::EXE_SUFFIX)) + .touch()?; + + // Try to upgrade with a non-managed interpreter installed in `bin`. + uv_snapshot!(context.filters(), context.python_upgrade().arg("--preview").arg("3.12"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: Executable already exists at `[BIN]/python3.12` but is not managed by uv; use `uv python install 3.12 --force` to replace it + Installed Python 3.12.11 in [TIME] + + cpython-3.12.11-[PLATFORM] + "); + + // Force the `bin` install. + uv_snapshot!(context.filters(), context.python_install().arg("3.12").arg("--force").arg("--preview").arg("3.12"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.12.11 in [TIME] + + cpython-3.12.11-[PLATFORM] (python3.12) + "); + + Ok(()) +} diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index 966dd41d2..e6fe831d3 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -9619,9 +9619,7 @@ fn sync_when_virtual_environment_incompatible_with_interpreter() -> Result<()> { }, { let contents = fs_err::read_to_string(&pyvenv_cfg).unwrap(); let lines: Vec<&str> = contents.split('\n').collect(); - assert_snapshot!(lines[3], @r###" - version_info = 3.12.[X] - "###); + assert_snapshot!(lines[3], @"version_info = 3.12.[X]"); }); // Simulate an incompatible `pyvenv.cfg:version_info` value created @@ -9660,9 +9658,7 @@ fn sync_when_virtual_environment_incompatible_with_interpreter() -> Result<()> { }, { let contents = fs_err::read_to_string(&pyvenv_cfg).unwrap(); let lines: Vec<&str> = contents.split('\n').collect(); - assert_snapshot!(lines[3], @r###" - version_info = 3.12.[X] - "###); + assert_snapshot!(lines[3], @"version_info = 3.12.[X]"); }); Ok(()) diff --git a/crates/uv/tests/it/tool_upgrade.rs b/crates/uv/tests/it/tool_upgrade.rs index a36db9b92..70309f04d 100644 --- a/crates/uv/tests/it/tool_upgrade.rs +++ b/crates/uv/tests/it/tool_upgrade.rs @@ -741,9 +741,7 @@ fn tool_upgrade_python() { }, { let content = fs_err::read_to_string(tool_dir.join("babel").join("pyvenv.cfg")).unwrap(); let lines: Vec<&str> = content.split('\n').collect(); - assert_snapshot!(lines[lines.len() - 3], @r###" - version_info = 3.12.[X] - "###); + assert_snapshot!(lines[lines.len() - 3], @"version_info = 3.12.[X]"); }); } @@ -826,9 +824,7 @@ fn tool_upgrade_python_with_all() { }, { let content = fs_err::read_to_string(tool_dir.join("babel").join("pyvenv.cfg")).unwrap(); let lines: Vec<&str> = content.split('\n').collect(); - assert_snapshot!(lines[lines.len() - 3], @r###" - version_info = 3.12.[X] - "###); + assert_snapshot!(lines[lines.len() - 3], @"version_info = 3.12.[X]"); }); insta::with_settings!({ @@ -836,8 +832,6 @@ fn tool_upgrade_python_with_all() { }, { let content = fs_err::read_to_string(tool_dir.join("python-dotenv").join("pyvenv.cfg")).unwrap(); let lines: Vec<&str> = content.split('\n').collect(); - assert_snapshot!(lines[lines.len() - 3], @r###" - version_info = 3.12.[X] - "###); + assert_snapshot!(lines[lines.len() - 3], @"version_info = 3.12.[X]"); }); } diff --git a/crates/uv/tests/it/venv.rs b/crates/uv/tests/it/venv.rs index bc35f9490..f1860efa2 100644 --- a/crates/uv/tests/it/venv.rs +++ b/crates/uv/tests/it/venv.rs @@ -868,14 +868,14 @@ fn create_venv_unknown_python_patch() { "### ); } else { - uv_snapshot!(&mut command, @r###" + uv_snapshot!(&mut command, @r" success: false exit_code: 1 ----- stdout ----- ----- stderr ----- × No interpreter found for Python 3.12.100 in managed installations or search path - "### + " ); } diff --git a/docs/concepts/python-versions.md b/docs/concepts/python-versions.md index 0d409ff50..a7472bea8 100644 --- a/docs/concepts/python-versions.md +++ b/docs/concepts/python-versions.md @@ -123,7 +123,7 @@ present, uv will install all the Python versions listed in the file. !!! important - Support for installing Python executables is in _preview_, this means the behavior is experimental + Support for installing Python executables is in _preview_. This means the behavior is experimental and subject to change. To install Python executables into your `PATH`, provide the `--preview` option: @@ -158,6 +158,70 @@ $ uv python install 3.12.6 --preview # Does not update `python3.12` $ uv python install 3.12.8 --preview # Updates `python3.12` to point to 3.12.8 ``` +## Upgrading Python versions + +!!! important + + Support for upgrading Python versions is in _preview_. This means the behavior is experimental + and subject to change. + + Upgrades are only supported for uv-managed Python versions. + + Upgrades are not currently supported for PyPy and GraalPy. + +uv allows transparently upgrading Python versions to the latest patch release, e.g., 3.13.4 to +3.13.5. uv does not allow transparently upgrading across minor Python versions, e.g., 3.12 to 3.13, +because changing minor versions can affect dependency resolution. + +uv-managed Python versions can be upgraded to the latest supported patch release with the +`python upgrade` command: + +To upgrade a Python version to the latest supported patch release: + +```console +$ uv python upgrade 3.12 +``` + +To upgrade all installed Python versions: + +```console +$ uv python upgrade +``` + +After an upgrade, uv will prefer the new version, but will retain the existing version as it may +still be used by virtual environments. + +If the Python version was installed with preview enabled, e.g., `uv python install 3.12 --preview`, +virtual environments using the Python version will be automatically upgraded to the new patch +version. + +!!! note + + If the virtual environment was created _before_ opting in to the preview mode, it will not be + included in the automatic upgrades. + +If a virtual environment was created with an explicitly requested patch version, e.g., +`uv venv -p 3.10.8`, it will not be transparently upgraded to a new version. + +### Minor version directories + +Automatic upgrades for virtual environments are implemented using a directory with the Python minor +version, e.g.: + +``` +~/.local/share/uv/python/cpython-3.12-macos-aarch64-none +``` + +which is a symbolic link (on Unix) or junction (on Windows) pointing to a specific patch version: + +```console +$ readlink ~/.local/share/uv/python/cpython-3.12-macos-aarch64-none +~/.local/share/uv/python/cpython-3.12.11-macos-aarch64-none +``` + +If this link is resolved by another tool, e.g., by canonicalizing the Python interpreter path, and +used to create a virtual environment, it will not be automatically upgraded. + ## Project Python versions uv will respect Python requirements defined in `requires-python` in the `pyproject.toml` file during diff --git a/docs/guides/install-python.md b/docs/guides/install-python.md index 0b80589a5..da841eac6 100644 --- a/docs/guides/install-python.md +++ b/docs/guides/install-python.md @@ -120,6 +120,28 @@ To force uv to use the system Python, provide the `--no-managed-python` flag. Se [Python version preference](../concepts/python-versions.md#requiring-or-disabling-managed-python-versions) documentation for more details. +## Upgrading Python versions + +!!! important + + Support for upgrading Python patch versions is in _preview_. This means the behavior is + experimental and subject to change. + +To upgrade a Python version to the latest supported patch release: + +```console +$ uv python upgrade 3.12 +``` + +To upgrade all uv-managed Python versions: + +```console +$ uv python upgrade +``` + +See the [`python upgrade`](../concepts/python-versions.md#upgrading-python-versions) documentation +for more details. + ## Next steps To learn more about `uv python`, see the [Python version concept](../concepts/python-versions.md) diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 9ae05a8e0..48b0351fe 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -2559,6 +2559,7 @@ uv python [OPTIONS]

    uv python list

    List the available Python installations

    uv python install

    Download and install Python versions

    +
    uv python upgrade

    Upgrade installed Python versions to the latest supported patch release (requires the --preview flag)

    uv python find

    Search for a Python installation

    uv python pin

    Pin to a specific Python version

    uv python dir

    Show the uv Python installation directory

    @@ -2753,6 +2754,91 @@ uv python install [OPTIONS] [TARGETS]...

    You can configure fine-grained logging using the RUST_LOG environment variable. (https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives)

    +### uv python upgrade + +Upgrade installed Python versions to the latest supported patch release (requires the `--preview` flag). + +A target Python minor version to upgrade may be provided, e.g., `3.13`. Multiple versions may be provided to perform more than one upgrade. + +If no target version is provided, then uv will upgrade all managed CPython versions. + +During an upgrade, uv will not uninstall outdated patch versions. + +When an upgrade is performed, virtual environments created by uv will automatically use the new version. However, if the virtual environment was created before the upgrade functionality was added, it will continue to use the old Python version; to enable upgrades, the environment must be recreated. + +Upgrades are not yet supported for alternative implementations, like PyPy. + +

    Usage

    + +``` +uv python upgrade [OPTIONS] [TARGETS]... +``` + +

    Arguments

    + +
    TARGETS

    The Python minor version(s) to upgrade.

    +

    If no target version is provided, then uv will upgrade all managed CPython versions.

    +
    + +

    Options

    + +
    --allow-insecure-host, --trusted-host allow-insecure-host

    Allow insecure connections to a host.

    +

    Can be provided multiple times.

    +

    Expects to receive either a hostname (e.g., localhost), a host-port pair (e.g., localhost:8080), or a URL (e.g., https://localhost).

    +

    WARNING: Hosts included in this list will not be verified against the system's certificate store. Only use --allow-insecure-host in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.

    +

    May also be set with the UV_INSECURE_HOST environment variable.

    --cache-dir cache-dir

    Path to the cache directory.

    +

    Defaults to $XDG_CACHE_HOME/uv or $HOME/.cache/uv on macOS and Linux, and %LOCALAPPDATA%\uv\cache on Windows.

    +

    To view the location of the cache directory, run uv cache dir.

    +

    May also be set with the UV_CACHE_DIR environment variable.

    --color color-choice

    Control the use of color in output.

    +

    By default, uv will automatically detect support for colors when writing to a terminal.

    +

    Possible values:

    +
      +
    • auto: Enables colored output only when the output is going to a terminal or TTY with support
    • +
    • always: Enables colored output regardless of the detected environment
    • +
    • never: Disables colored output
    • +
    --config-file config-file

    The path to a uv.toml file to use for configuration.

    +

    While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.

    +

    May also be set with the UV_CONFIG_FILE environment variable.

    --directory directory

    Change to the given directory prior to running the command.

    +

    Relative paths are resolved with the given directory as the base.

    +

    See --project to only change the project root directory.

    +
    --help, -h

    Display the concise help for this command

    +
    --install-dir, -i install-dir

    The directory Python installations are stored in.

    +

    If provided, UV_PYTHON_INSTALL_DIR will need to be set for subsequent operations for uv to discover the Python installation.

    +

    See uv python dir to view the current Python installation directory. Defaults to ~/.local/share/uv/python.

    +

    May also be set with the UV_PYTHON_INSTALL_DIR environment variable.

    --managed-python

    Require use of uv-managed Python versions.

    +

    By default, uv prefers using Python versions it manages. However, it will use system Python versions if a uv-managed Python is not installed. This option disables use of system Python versions.

    +

    May also be set with the UV_MANAGED_PYTHON environment variable.

    --mirror mirror

    Set the URL to use as the source for downloading Python installations.

    +

    The provided URL will replace https://github.com/astral-sh/python-build-standalone/releases/download in, e.g., https://github.com/astral-sh/python-build-standalone/releases/download/20240713/cpython-3.12.4%2B20240713-aarch64-apple-darwin-install_only.tar.gz.

    +

    Distributions can be read from a local directory by using the file:// URL scheme.

    +

    May also be set with the UV_PYTHON_INSTALL_MIRROR environment variable.

    --native-tls

    Whether to load TLS certificates from the platform's native certificate store.

    +

    By default, uv loads certificates from the bundled webpki-roots crate. The webpki-roots are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).

    +

    However, in some cases, you may want to use the platform's native certificate store, especially if you're relying on a corporate trust root (e.g., for a mandatory proxy) that's included in your system's certificate store.

    +

    May also be set with the UV_NATIVE_TLS environment variable.

    --no-cache, --no-cache-dir, -n

    Avoid reading from or writing to the cache, instead using a temporary directory for the duration of the operation

    +

    May also be set with the UV_NO_CACHE environment variable.

    --no-config

    Avoid discovering configuration files (pyproject.toml, uv.toml).

    +

    Normally, configuration files are discovered in the current directory, parent directories, or user configuration directories.

    +

    May also be set with the UV_NO_CONFIG environment variable.

    --no-managed-python

    Disable use of uv-managed Python versions.

    +

    Instead, uv will search for a suitable Python version on the system.

    +

    May also be set with the UV_NO_MANAGED_PYTHON environment variable.

    --no-progress

    Hide all progress outputs.

    +

    For example, spinners or progress bars.

    +

    May also be set with the UV_NO_PROGRESS environment variable.

    --no-python-downloads

    Disable automatic downloads of Python.

    +
    --offline

    Disable network access.

    +

    When disabled, uv will only use locally cached data and locally available files.

    +

    May also be set with the UV_OFFLINE environment variable.

    --project project

    Run the command within the given project directory.

    +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project's virtual environment (.venv).

    +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    +

    See --directory to change the working directory entirely.

    +

    This setting has no effect when used in the uv pip interface.

    +

    May also be set with the UV_PROJECT environment variable.

    --pypy-mirror pypy-mirror

    Set the URL to use as the source for downloading PyPy installations.

    +

    The provided URL will replace https://downloads.python.org/pypy in, e.g., https://downloads.python.org/pypy/pypy3.8-v7.3.7-osx64.tar.bz2.

    +

    Distributions can be read from a local directory by using the file:// URL scheme.

    +

    May also be set with the UV_PYPY_INSTALL_MIRROR environment variable.

    --python-downloads-json-url python-downloads-json-url

    URL pointing to JSON of custom Python installations.

    +

    Note that currently, only local paths are supported.

    +

    May also be set with the UV_PYTHON_DOWNLOADS_JSON_URL environment variable.

    --quiet, -q

    Use quiet output.

    +

    Repeating this option, e.g., -qq, will enable a silent mode in which uv will write no output to stdout.

    +
    --verbose, -v

    Use verbose output.

    +

    You can configure fine-grained logging using the RUST_LOG environment variable. (https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives)

    +
    + ### uv python find Search for a Python installation. From c710246d76a583cfee6aa925ec72aceb1bf86bce Mon Sep 17 00:00:00 2001 From: Aria Desires Date: Fri, 20 Jun 2025 14:20:01 -0400 Subject: [PATCH 025/349] Update cargo-dist (#14156) Also took the time to migrate to the external config format to normalize our projects for team comfort (`ty` *has* to use this format for its workspace structure). --- .github/workflows/release.yml | 2 +- Cargo.toml | 83 -------------------------------- dist-workspace.toml | 89 +++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 84 deletions(-) create mode 100644 dist-workspace.toml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b1c77c316..2688c3fc8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -69,7 +69,7 @@ jobs: # we specify bash to get pipefail; it guards against the `curl` command # failing. otherwise `sh` won't catch that `curl` returned non-0 shell: bash - run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/cargo-dist/releases/download/v0.28.4/cargo-dist-installer.sh | sh" + run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/cargo-dist/releases/download/v0.28.7-prerelease.1/cargo-dist-installer.sh | sh" - name: Cache dist uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47 with: diff --git a/Cargo.toml b/Cargo.toml index c6d5729e2..4269c6cfa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -297,89 +297,6 @@ codegen-units = 1 [profile.dist] inherits = "release" -# Config for 'dist' -[workspace.metadata.dist] -# The preferred dist version to use in CI (Cargo.toml SemVer syntax) -cargo-dist-version = "0.28.4" -# make a package being included in our releases opt-in instead of opt-out -dist = false -# CI backends to support -ci = "github" -# The installers to generate for each app -installers = ["shell", "powershell"] -# The archive format to use for windows builds (defaults .zip) -windows-archive = ".zip" -# The archive format to use for non-windows builds (defaults .tar.xz) -unix-archive = ".tar.gz" -# Target platforms to build apps for (Rust target-triple syntax) -targets = [ - "aarch64-apple-darwin", - "aarch64-pc-windows-msvc", - "aarch64-unknown-linux-gnu", - "aarch64-unknown-linux-musl", - "arm-unknown-linux-musleabihf", - "armv7-unknown-linux-gnueabihf", - "armv7-unknown-linux-musleabihf", - "i686-pc-windows-msvc", - "i686-unknown-linux-gnu", - "i686-unknown-linux-musl", - "powerpc64-unknown-linux-gnu", - "powerpc64le-unknown-linux-gnu", - "riscv64gc-unknown-linux-gnu", - "s390x-unknown-linux-gnu", - "x86_64-apple-darwin", - "x86_64-pc-windows-msvc", - "x86_64-unknown-linux-gnu", - "x86_64-unknown-linux-musl", -] -# Whether to auto-include files like READMEs, LICENSEs, and CHANGELOGs (default true) -auto-includes = false -# Whether dist should create a Github Release or use an existing draft -create-release = true -# Which actions to run on pull requests -pr-run-mode = "plan" -# Whether CI should trigger releases with dispatches instead of tag pushes -dispatch-releases = true -# Which phase dist should use to create the GitHub release -github-release = "announce" -# Whether CI should include auto-generated code to build local artifacts -build-local-artifacts = false -# Local artifacts jobs to run in CI -local-artifacts-jobs = ["./build-binaries", "./build-docker"] -# Publish jobs to run in CI -publish-jobs = ["./publish-pypi"] -# Post-announce jobs to run in CI -post-announce-jobs = ["./publish-docs"] -# Custom permissions for GitHub Jobs -github-custom-job-permissions = { "build-docker" = { packages = "write", contents = "read", id-token = "write", attestations = "write" } } -# Whether to install an updater program -install-updater = false -# Path that installers should place binaries in -install-path = ["$XDG_BIN_HOME/", "$XDG_DATA_HOME/../bin", "~/.local/bin"] - -[workspace.metadata.dist.github-custom-runners] -global = "depot-ubuntu-latest-4" - -[workspace.metadata.dist.min-glibc-version] -# Override glibc version for specific target triplets. -aarch64-unknown-linux-gnu = "2.28" -riscv64gc-unknown-linux-gnu = "2.31" -# Override all remaining glibc versions. -"*" = "2.17" - -[workspace.metadata.dist.github-action-commits] -"actions/checkout" = "11bd71901bbe5b1630ceea73d27597364c9af683" # v4 -"actions/upload-artifact" = "6027e3dd177782cd8ab9af838c04fd81a07f1d47" # v4.6.2 -"actions/download-artifact" = "d3f86a106a0bac45b974a628896c90dbdf5c8093" # v4.3.0 -"actions/attest-build-provenance" = "c074443f1aee8d4aeeae555aebba3282517141b2" #v2.2.3 - [patch.crates-io] reqwest-middleware = { git = "https://github.com/astral-sh/reqwest-middleware", rev = "ad8b9d332d1773fde8b4cd008486de5973e0a3f8" } reqwest-retry = { git = "https://github.com/astral-sh/reqwest-middleware", rev = "ad8b9d332d1773fde8b4cd008486de5973e0a3f8" } - -[workspace.metadata.dist.binaries] -"*" = ["uv", "uvx"] -# Add "uvw" binary for Windows targets -aarch64-pc-windows-msvc = ["uv", "uvx", "uvw"] -i686-pc-windows-msvc = ["uv", "uvx", "uvw"] -x86_64-pc-windows-msvc = ["uv", "uvx", "uvw"] diff --git a/dist-workspace.toml b/dist-workspace.toml new file mode 100644 index 000000000..3e16bd4cf --- /dev/null +++ b/dist-workspace.toml @@ -0,0 +1,89 @@ +[workspace] +members = ["cargo:."] + +# Config for 'dist' +[dist] +# The preferred dist version to use in CI (Cargo.toml SemVer syntax) +cargo-dist-version = "0.28.7-prerelease.1" +# make a package being included in our releases opt-in instead of opt-out +dist = false +# CI backends to support +ci = "github" +# The installers to generate for each app +installers = ["shell", "powershell"] +# The archive format to use for windows builds (defaults .zip) +windows-archive = ".zip" +# The archive format to use for non-windows builds (defaults .tar.xz) +unix-archive = ".tar.gz" +# Target platforms to build apps for (Rust target-triple syntax) +targets = [ + "aarch64-apple-darwin", + "aarch64-unknown-linux-gnu", + "aarch64-unknown-linux-musl", + "aarch64-pc-windows-msvc", + "arm-unknown-linux-musleabihf", + "armv7-unknown-linux-gnueabihf", + "armv7-unknown-linux-musleabihf", + "x86_64-apple-darwin", + "powerpc64-unknown-linux-gnu", + "powerpc64le-unknown-linux-gnu", + "riscv64gc-unknown-linux-gnu", + "s390x-unknown-linux-gnu", + "x86_64-unknown-linux-gnu", + "x86_64-unknown-linux-musl", + "x86_64-pc-windows-msvc", + "i686-unknown-linux-gnu", + "i686-unknown-linux-musl", + "i686-pc-windows-msvc" +] +# Whether to auto-include files like READMEs, LICENSEs, and CHANGELOGs (default true) +auto-includes = false +# Whether dist should create a Github Release or use an existing draft +create-release = true +# Which actions to run on pull requests +pr-run-mode = "plan" +# Whether CI should trigger releases with dispatches instead of tag pushes +dispatch-releases = true +# Which phase dist should use to create the GitHub release +github-release = "announce" +# Whether CI should include auto-generated code to build local artifacts +build-local-artifacts = false +# Local artifacts jobs to run in CI +local-artifacts-jobs = ["./build-binaries", "./build-docker"] +# Publish jobs to run in CI +publish-jobs = ["./publish-pypi"] +# Post-announce jobs to run in CI +post-announce-jobs = ["./publish-docs"] +# Custom permissions for GitHub Jobs +github-custom-job-permissions = { "build-docker" = { packages = "write", contents = "read", id-token = "write", attestations = "write" } } +# Whether to install an updater program +install-updater = false +# Path that installers should place binaries in +install-path = [ + "$XDG_BIN_HOME/", + "$XDG_DATA_HOME/../bin", + "~/.local/bin" +] + +[dist.github-custom-runners] +global = "depot-ubuntu-latest-4" + +[dist.min-glibc-version] +# Override glibc version for specific target triplets. +aarch64-unknown-linux-gnu = "2.28" +riscv64gc-unknown-linux-gnu = "2.31" +# Override all remaining glibc versions. +"*" = "2.17" + +[dist.github-action-commits] +"actions/checkout" = "11bd71901bbe5b1630ceea73d27597364c9af683" # v4 +"actions/upload-artifact" = "6027e3dd177782cd8ab9af838c04fd81a07f1d47" # v4.6.2 +"actions/download-artifact" = "d3f86a106a0bac45b974a628896c90dbdf5c8093" # v4.3.0 +"actions/attest-build-provenance" = "c074443f1aee8d4aeeae555aebba3282517141b2" #v2.2.3 + +[dist.binaries] +"*" = ["uv", "uvx"] +# Add "uvw" binary for Windows targets +aarch64-pc-windows-msvc = ["uv", "uvx", "uvw"] +i686-pc-windows-msvc = ["uv", "uvx", "uvw"] +x86_64-pc-windows-msvc = ["uv", "uvx", "uvw"] From 563e9495ba015a4938e2a6330fcbd1c83e3fa2cc Mon Sep 17 00:00:00 2001 From: Lucas Vittor Date: Fri, 20 Jun 2025 17:05:17 -0300 Subject: [PATCH 026/349] Replace cuda124 with cuda128 (#14168) ## Summary Replace wrong `cuda124` version to the correct `cuda128` version in torch docs ## Test Plan --- docs/guides/integration/pytorch.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/guides/integration/pytorch.md b/docs/guides/integration/pytorch.md index fb1d82b31..7a2500ec5 100644 --- a/docs/guides/integration/pytorch.md +++ b/docs/guides/integration/pytorch.md @@ -355,11 +355,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 cu124`). +`uv sync --extra cu128`). 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 cu124`: +`uv sync --extra cpu` and CUDA-enabled builds for `uv sync --extra cu128`: ```toml [project] @@ -410,7 +410,7 @@ explicit = true !!! note Since GPU-accelerated builds aren't available on macOS, the above configuration will fail to install - on macOS when the `cu124` extra is enabled. + on macOS when the `cu128` extra is enabled. ## The `uv pip` interface From 1dbe7504528f7734b7721f54c8e90c0c691e786b Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Fri, 20 Jun 2025 15:31:32 -0500 Subject: [PATCH 027/349] Refactor `PythonVersionFile` global loading (#14107) I was looking into `uv tool` not supporting version files, and noticed this implementation was confusing and skipped handling like a tracing log if `--no-config` excludes selection a file. I've refactored it in preparation for the next change. --- crates/uv-python/src/version_files.rs | 76 ++++++++++++++------------- crates/uv/src/commands/python/pin.rs | 17 +++--- 2 files changed, 46 insertions(+), 47 deletions(-) diff --git a/crates/uv-python/src/version_files.rs b/crates/uv-python/src/version_files.rs index 894654a3c..a9cd05b7e 100644 --- a/crates/uv-python/src/version_files.rs +++ b/crates/uv-python/src/version_files.rs @@ -37,11 +37,14 @@ pub enum FilePreference { pub struct DiscoveryOptions<'a> { /// The path to stop discovery at. stop_discovery_at: Option<&'a Path>, - /// When `no_config` is set, Python version files will be ignored. + /// Ignore Python version files. /// /// Discovery will still run in order to display a log about the ignored file. no_config: bool, + /// Whether `.python-version` or `.python-versions` should be preferred. preference: FilePreference, + /// Whether to ignore local version files, and only search for a global one. + no_local: bool, } impl<'a> DiscoveryOptions<'a> { @@ -62,6 +65,11 @@ impl<'a> DiscoveryOptions<'a> { ..self } } + + #[must_use] + pub fn with_no_local(self, no_local: bool) -> Self { + Self { no_local, ..self } + } } impl PythonVersionFile { @@ -70,33 +78,38 @@ impl PythonVersionFile { working_directory: impl AsRef, options: &DiscoveryOptions<'_>, ) -> Result, std::io::Error> { - let Some(path) = Self::find_nearest(&working_directory, options) else { - if let Some(stop_discovery_at) = options.stop_discovery_at { - if stop_discovery_at == working_directory.as_ref() { - debug!( - "No Python version file found in workspace: {}", - working_directory.as_ref().display() - ); + let allow_local = !options.no_local; + let Some(path) = allow_local.then(|| { + // First, try to find a local version file. + let local = Self::find_nearest(&working_directory, options); + if local.is_none() { + // Log where we searched for the file, if not found + if let Some(stop_discovery_at) = options.stop_discovery_at { + if stop_discovery_at == working_directory.as_ref() { + debug!( + "No Python version file found in workspace: {}", + working_directory.as_ref().display() + ); + } else { + debug!( + "No Python version file found between working directory `{}` and workspace root `{}`", + working_directory.as_ref().display(), + stop_discovery_at.display() + ); + } } else { debug!( - "No Python version file found between working directory `{}` and workspace root `{}`", - working_directory.as_ref().display(), - stop_discovery_at.display() + "No Python version file found in ancestors of working directory: {}", + working_directory.as_ref().display() ); } - } else { - debug!( - "No Python version file found in ancestors of working directory: {}", - working_directory.as_ref().display() - ); } - // Not found in directory or its ancestors. Looking in user-level config. - return Ok(match user_uv_config_dir() { - Some(user_dir) => Self::discover_user_config(user_dir, options) - .await? - .or(None), - None => None, - }); + local + }).flatten().or_else(|| { + // Search for a global config + Self::find_global(options) + }) else { + return Ok(None); }; if options.no_config { @@ -111,20 +124,9 @@ impl PythonVersionFile { Self::try_from_path(path).await } - pub async fn discover_user_config( - user_config_working_directory: impl AsRef, - options: &DiscoveryOptions<'_>, - ) -> Result, std::io::Error> { - if !options.no_config { - if let Some(path) = - Self::find_in_directory(user_config_working_directory.as_ref(), options) - .into_iter() - .find(|path| path.is_file()) - { - return Self::try_from_path(path).await; - } - } - Ok(None) + fn find_global(options: &DiscoveryOptions<'_>) -> Option { + let user_config_dir = user_uv_config_dir()?; + Self::find_in_directory(&user_config_dir, options) } fn find_nearest(path: impl AsRef, options: &DiscoveryOptions<'_>) -> Option { diff --git a/crates/uv/src/commands/python/pin.rs b/crates/uv/src/commands/python/pin.rs index 26714e4d7..f0dc06cff 100644 --- a/crates/uv/src/commands/python/pin.rs +++ b/crates/uv/src/commands/python/pin.rs @@ -57,16 +57,13 @@ pub(crate) async fn pin( } }; - let version_file = if global { - if let Some(path) = user_uv_config_dir() { - PythonVersionFile::discover_user_config(path, &VersionFileDiscoveryOptions::default()) - .await - } else { - Ok(None) - } - } else { - PythonVersionFile::discover(project_dir, &VersionFileDiscoveryOptions::default()).await - }; + // Search for an existing file, we won't necessarily write to this, we'll construct a target + // path if there's a request later on. + let version_file = PythonVersionFile::discover( + project_dir, + &VersionFileDiscoveryOptions::default().with_no_local(global), + ) + .await; if rm { let Some(file) = version_file? else { From 0133bcc8ca36e47eaf42946a157b8dbb1f3b1be8 Mon Sep 17 00:00:00 2001 From: Jack O'Connor Date: Fri, 20 Jun 2025 13:34:45 -0700 Subject: [PATCH 028/349] (f)lock during `uv run` (#14153) This is very similar to the locking added for `uv sync`, `uv add`, and `uv remove` in https://github.com/astral-sh/uv/pull/13869. Improving our (f)locking in general is tracked in https://github.com/astral-sh/uv/issues/13883. --- crates/uv/src/commands/project/run.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index 8a510fa8c..ee3caeafa 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -240,6 +240,8 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl .await? .into_environment()?; + let _lock = environment.lock().await?; + // Determine the lock mode. let mode = if frozen { LockMode::Frozen @@ -382,6 +384,8 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl ) }); + let _lock = environment.lock().await?; + match update_environment( environment, spec, @@ -694,6 +698,8 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl .map(|lock| (lock, project.workspace().install_path().to_owned())); } } else { + let _lock = venv.lock().await?; + // Determine the lock mode. let mode = if frozen { LockMode::Frozen From e59835d50c8181af40badf03e8ef9d4152d25d65 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Fri, 20 Jun 2025 20:33:20 -0400 Subject: [PATCH 029/349] Add XPU to `--torch-backend` (#14172) ## Summary Like ROCm, no auto-detection for now. --- clippy.toml | 1 + crates/uv-resolver/src/resolver/system.rs | 6 ++++++ crates/uv-torch/src/backend.rs | 10 ++++++++++ docs/reference/cli.md | 3 +++ uv.schema.json | 7 +++++++ 5 files changed, 27 insertions(+) diff --git a/clippy.toml b/clippy.toml index bb1f365b5..6b3031c84 100644 --- a/clippy.toml +++ b/clippy.toml @@ -7,6 +7,7 @@ doc-valid-idents = [ "ReFS", "PyTorch", "ROCm", + "XPU", ".." # Include the defaults ] diff --git a/crates/uv-resolver/src/resolver/system.rs b/crates/uv-resolver/src/resolver/system.rs index a47000846..a815697da 100644 --- a/crates/uv-resolver/src/resolver/system.rs +++ b/crates/uv-resolver/src/resolver/system.rs @@ -86,4 +86,10 @@ mod tests { let url = DisplaySafeUrl::parse("https://download.pytorch.org/whl/cpu").unwrap(); assert_eq!(SystemDependency::from_index(&url), None); } + + #[test] + fn pytorch_xpu() { + let url = DisplaySafeUrl::parse("https://download.pytorch.org/whl/xpu").unwrap(); + assert_eq!(SystemDependency::from_index(&url), None); + } } diff --git a/crates/uv-torch/src/backend.rs b/crates/uv-torch/src/backend.rs index 958ea3d9e..60d43f3d7 100644 --- a/crates/uv-torch/src/backend.rs +++ b/crates/uv-torch/src/backend.rs @@ -171,6 +171,8 @@ pub enum TorchMode { #[serde(rename = "rocm4.0.1")] #[cfg_attr(feature = "clap", clap(name = "rocm4.0.1"))] Rocm401, + /// Use the PyTorch index for Intel XPU. + Xpu, } /// The strategy to use when determining the appropriate PyTorch index. @@ -237,6 +239,7 @@ impl TorchStrategy { TorchMode::Rocm42 => Ok(Self::Backend(TorchBackend::Rocm42)), TorchMode::Rocm41 => Ok(Self::Backend(TorchBackend::Rocm41)), TorchMode::Rocm401 => Ok(Self::Backend(TorchBackend::Rocm401)), + TorchMode::Xpu => Ok(Self::Backend(TorchBackend::Xpu)), } } @@ -356,6 +359,7 @@ pub enum TorchBackend { Rocm42, Rocm41, Rocm401, + Xpu, } impl TorchBackend { @@ -403,6 +407,7 @@ impl TorchBackend { Self::Rocm42 => &ROCM42_INDEX_URL, Self::Rocm41 => &ROCM41_INDEX_URL, Self::Rocm401 => &ROCM401_INDEX_URL, + Self::Xpu => &XPU_INDEX_URL, } } @@ -465,6 +470,7 @@ impl TorchBackend { TorchBackend::Rocm42 => None, TorchBackend::Rocm41 => None, TorchBackend::Rocm401 => None, + TorchBackend::Xpu => None, } } @@ -512,6 +518,7 @@ impl TorchBackend { TorchBackend::Rocm42 => Some(Version::new([4, 2])), TorchBackend::Rocm41 => Some(Version::new([4, 1])), TorchBackend::Rocm401 => Some(Version::new([4, 0, 1])), + TorchBackend::Xpu => None, } } } @@ -562,6 +569,7 @@ impl FromStr for TorchBackend { "rocm4.2" => Ok(TorchBackend::Rocm42), "rocm4.1" => Ok(TorchBackend::Rocm41), "rocm4.0.1" => Ok(TorchBackend::Rocm401), + "xpu" => Ok(TorchBackend::Xpu), _ => Err(format!("Unknown PyTorch backend: {s}")), } } @@ -725,3 +733,5 @@ static ROCM41_INDEX_URL: LazyLock = LazyLock::new(|| IndexUrl::from_str("https://download.pytorch.org/whl/rocm4.1").unwrap()); static ROCM401_INDEX_URL: LazyLock = LazyLock::new(|| IndexUrl::from_str("https://download.pytorch.org/whl/rocm4.0.1").unwrap()); +static XPU_INDEX_URL: LazyLock = + LazyLock::new(|| IndexUrl::from_str("https://download.pytorch.org/whl/xpu").unwrap()); diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 48b0351fe..b9490e2ce 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -3451,6 +3451,7 @@ by --python-version.

  • rocm4.2: Use the PyTorch index for ROCm 4.2
  • rocm4.1: Use the PyTorch index for ROCm 4.1
  • rocm4.0.1: Use the PyTorch index for ROCm 4.0.1
  • +
  • xpu: Use the PyTorch index for Intel XPU
  • --universal

    Perform a universal resolution, attempting to generate a single requirements.txt output file that is compatible with all operating systems, architectures, and Python implementations.

    In universal mode, the current Python version (or user-provided --python-version) will be treated as a lower bound. For example, --universal --python-version 3.7 would produce a universal resolution for Python 3.7 and later.

    Implies --no-strip-markers.

    @@ -3708,6 +3709,7 @@ be used with caution, as it can modify the system Python installation.

  • rocm4.2: Use the PyTorch index for ROCm 4.2
  • rocm4.1: Use the PyTorch index for ROCm 4.1
  • rocm4.0.1: Use the PyTorch index for ROCm 4.0.1
  • +
  • xpu: Use the PyTorch index for Intel XPU
  • --verbose, -v

    Use verbose output.

    You can configure fine-grained logging using the RUST_LOG environment variable. (https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives)

    @@ -3998,6 +4000,7 @@ should be used with caution, as it can modify the system Python installation.

    rocm4.2: Use the PyTorch index for ROCm 4.2
  • rocm4.1: Use the PyTorch index for ROCm 4.1
  • rocm4.0.1: Use the PyTorch index for ROCm 4.0.1
  • +
  • xpu: Use the PyTorch index for Intel XPU
  • --upgrade, -U

    Allow package upgrades, ignoring pinned versions in any existing output file. Implies --refresh

    --upgrade-package, -P upgrade-package

    Allow upgrades for a specific package, ignoring pinned versions in any existing output file. Implies --refresh-package

    --user
    --verbose, -v

    Use verbose output.

    diff --git a/uv.schema.json b/uv.schema.json index 0d2b47490..0b9bd6f15 100644 --- a/uv.schema.json +++ b/uv.schema.json @@ -2701,6 +2701,13 @@ "enum": [ "rocm4.0.1" ] + }, + { + "description": "Use the PyTorch index for Intel XPU.", + "type": "string", + "enum": [ + "xpu" + ] } ] }, From 0fef253c4b40c7721d16e52aed86f0ee24d6fa41 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Fri, 20 Jun 2025 20:33:29 -0400 Subject: [PATCH 030/349] Use a dedicated type for form metadata (#14175) --- crates/uv-publish/src/lib.rs | 227 +++++++++++++++++++---------------- 1 file changed, 122 insertions(+), 105 deletions(-) diff --git a/crates/uv-publish/src/lib.rs b/crates/uv-publish/src/lib.rs index ec19713cc..51f9bb472 100644 --- a/crates/uv-publish/src/lib.rs +++ b/crates/uv-publish/src/lib.rs @@ -390,7 +390,7 @@ pub async fn upload( download_concurrency: &Semaphore, reporter: Arc, ) -> Result { - let form_metadata = form_metadata(file, filename) + let form_metadata = FormMetadata::read_from_file(file, filename) .await .map_err(|err| PublishError::PublishPrepare(file.to_path_buf(), Box::new(err)))?; @@ -644,108 +644,118 @@ async fn metadata(file: &Path, filename: &DistFilename) -> Result -async fn form_metadata( - file: &Path, - filename: &DistFilename, -) -> Result, PublishPrepareError> { - let hash_hex = hash_file(file, Hasher::from(HashAlgorithm::Sha256)).await?; +#[derive(Debug, Clone)] +struct FormMetadata(Vec<(&'static str, String)>); - let Metadata23 { - metadata_version, - name, - version, - platforms, - // Not used by PyPI legacy upload - supported_platforms: _, - summary, - description, - description_content_type, - keywords, - home_page, - download_url, - author, - author_email, - maintainer, - maintainer_email, - license, - license_expression, - license_files, - classifiers, - requires_dist, - provides_dist, - obsoletes_dist, - requires_python, - requires_external, - project_urls, - provides_extras, - dynamic, - } = metadata(file, filename).await?; +impl FormMetadata { + /// Collect the non-file fields for the multipart request from the package METADATA. + /// + /// Reference implementation: + async fn read_from_file( + file: &Path, + filename: &DistFilename, + ) -> Result { + let hash_hex = hash_file(file, Hasher::from(HashAlgorithm::Sha256)).await?; - let mut form_metadata = vec![ - (":action", "file_upload".to_string()), - ("sha256_digest", hash_hex.digest.to_string()), - ("protocol_version", "1".to_string()), - ("metadata_version", metadata_version.clone()), - // Twine transforms the name with `re.sub("[^A-Za-z0-9.]+", "-", name)` - // * - // * - // warehouse seems to call `packaging.utils.canonicalize_name` nowadays and has a separate - // `normalized_name`, so we'll start with this and we'll readjust if there are user reports. - ("name", name.clone()), - ("version", version.clone()), - ("filetype", filename.filetype().to_string()), - ]; + let Metadata23 { + metadata_version, + name, + version, + platforms, + // Not used by PyPI legacy upload + supported_platforms: _, + summary, + description, + description_content_type, + keywords, + home_page, + download_url, + author, + author_email, + maintainer, + maintainer_email, + license, + license_expression, + license_files, + classifiers, + requires_dist, + provides_dist, + obsoletes_dist, + requires_python, + requires_external, + project_urls, + provides_extras, + dynamic, + } = metadata(file, filename).await?; - if let DistFilename::WheelFilename(wheel) = filename { - form_metadata.push(("pyversion", wheel.python_tags().iter().join("."))); - } else { - form_metadata.push(("pyversion", "source".to_string())); + let mut form_metadata = vec![ + (":action", "file_upload".to_string()), + ("sha256_digest", hash_hex.digest.to_string()), + ("protocol_version", "1".to_string()), + ("metadata_version", metadata_version.clone()), + // Twine transforms the name with `re.sub("[^A-Za-z0-9.]+", "-", name)` + // * + // * + // warehouse seems to call `packaging.utils.canonicalize_name` nowadays and has a separate + // `normalized_name`, so we'll start with this and we'll readjust if there are user reports. + ("name", name.clone()), + ("version", version.clone()), + ("filetype", filename.filetype().to_string()), + ]; + + if let DistFilename::WheelFilename(wheel) = filename { + form_metadata.push(("pyversion", wheel.python_tags().iter().join("."))); + } else { + form_metadata.push(("pyversion", "source".to_string())); + } + + let mut add_option = |name, value: Option| { + if let Some(some) = value.clone() { + form_metadata.push((name, some)); + } + }; + + add_option("author", author); + add_option("author_email", author_email); + add_option("description", description); + add_option("description_content_type", description_content_type); + add_option("download_url", download_url); + add_option("home_page", home_page); + add_option("keywords", keywords); + add_option("license", license); + add_option("license_expression", license_expression); + add_option("maintainer", maintainer); + add_option("maintainer_email", maintainer_email); + add_option("summary", summary); + + // The GitLab PyPI repository API implementation requires this metadata field and twine always + // includes it in the request, even when it's empty. + form_metadata.push(("requires_python", requires_python.unwrap_or(String::new()))); + + let mut add_vec = |name, values: Vec| { + for i in values { + form_metadata.push((name, i.clone())); + } + }; + + add_vec("classifiers", classifiers); + add_vec("dynamic", dynamic); + add_vec("license_file", license_files); + add_vec("obsoletes_dist", obsoletes_dist); + add_vec("platform", platforms); + add_vec("project_urls", project_urls); + add_vec("provides_dist", provides_dist); + add_vec("provides_extra", provides_extras); + add_vec("requires_dist", requires_dist); + add_vec("requires_external", requires_external); + + Ok(Self(form_metadata)) } - let mut add_option = |name, value: Option| { - if let Some(some) = value.clone() { - form_metadata.push((name, some)); - } - }; - - add_option("author", author); - add_option("author_email", author_email); - add_option("description", description); - add_option("description_content_type", description_content_type); - add_option("download_url", download_url); - add_option("home_page", home_page); - add_option("keywords", keywords); - add_option("license", license); - add_option("license_expression", license_expression); - add_option("maintainer", maintainer); - add_option("maintainer_email", maintainer_email); - add_option("summary", summary); - - // The GitLab PyPI repository API implementation requires this metadata field and twine always - // includes it in the request, even when it's empty. - form_metadata.push(("requires_python", requires_python.unwrap_or(String::new()))); - - let mut add_vec = |name, values: Vec| { - for i in values { - form_metadata.push((name, i.clone())); - } - }; - - add_vec("classifiers", classifiers); - add_vec("dynamic", dynamic); - add_vec("license_file", license_files); - add_vec("obsoletes_dist", obsoletes_dist); - add_vec("platform", platforms); - add_vec("project_urls", project_urls); - add_vec("provides_dist", provides_dist); - add_vec("provides_extra", provides_extras); - add_vec("requires_dist", requires_dist); - add_vec("requires_external", requires_external); - - Ok(form_metadata) + /// Returns an iterator over the metadata fields. + fn iter(&self) -> std::slice::Iter<'_, (&'static str, String)> { + self.0.iter() + } } /// Build the upload request. @@ -758,11 +768,11 @@ async fn build_request<'a>( registry: &DisplaySafeUrl, client: &'a BaseClient, credentials: &Credentials, - form_metadata: &[(&'static str, String)], + form_metadata: &FormMetadata, reporter: Arc, ) -> Result<(RequestBuilder<'a>, usize), PublishPrepareError> { let mut form = reqwest::multipart::Form::new(); - for (key, value) in form_metadata { + for (key, value) in form_metadata.iter() { form = form.text(*key, value.clone()); } @@ -888,16 +898,19 @@ async fn handle_response(registry: &Url, response: Response) -> Result<(), Publi #[cfg(test)] mod tests { - use crate::{Reporter, build_request, form_metadata}; - use insta::{assert_debug_snapshot, assert_snapshot}; - use itertools::Itertools; use std::path::PathBuf; use std::sync::Arc; + + use insta::{assert_debug_snapshot, assert_snapshot}; + use itertools::Itertools; + use uv_auth::Credentials; use uv_client::BaseClientBuilder; use uv_distribution_filename::DistFilename; use uv_redacted::DisplaySafeUrl; + use crate::{FormMetadata, Reporter, build_request}; + struct DummyReporter; impl Reporter for DummyReporter { @@ -916,7 +929,9 @@ mod tests { let file = PathBuf::from("../../scripts/links/").join(raw_filename); let filename = DistFilename::try_from_normalized_filename(raw_filename).unwrap(); - let form_metadata = form_metadata(&file, &filename).await.unwrap(); + let form_metadata = FormMetadata::read_from_file(&file, &filename) + .await + .unwrap(); let formatted_metadata = form_metadata .iter() @@ -1028,7 +1043,9 @@ mod tests { let file = PathBuf::from("../../scripts/links/").join(raw_filename); let filename = DistFilename::try_from_normalized_filename(raw_filename).unwrap(); - let form_metadata = form_metadata(&file, &filename).await.unwrap(); + let form_metadata = FormMetadata::read_from_file(&file, &filename) + .await + .unwrap(); let formatted_metadata = form_metadata .iter() From 8352560b98bdfd830b842cb4e2cc0aa1a8e50357 Mon Sep 17 00:00:00 2001 From: John Mumm Date: Sat, 21 Jun 2025 03:50:41 -0400 Subject: [PATCH 031/349] Only update existing symlink directories on preview uninstall (#14179) On preview uninstall, we should not create a new minor version symlink directory if one doesn't exist. We should only update existing ones. --- crates/uv/src/commands/python/uninstall.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/uv/src/commands/python/uninstall.rs b/crates/uv/src/commands/python/uninstall.rs index 8a63b015c..e79ea9b19 100644 --- a/crates/uv/src/commands/python/uninstall.rs +++ b/crates/uv/src/commands/python/uninstall.rs @@ -240,7 +240,7 @@ async fn do_uninstall( .iter() .filter(|(minor_version, _)| uninstalled_minor_versions.contains(minor_version)) { - installation.ensure_minor_version_link(preview)?; + installation.update_minor_version_link(preview)?; } // For each uninstalled installation, check if there are no remaining installations // for its minor version. If there are none remaining, remove the symlink directory From b18f45db14152e75b85351afd69558e41b674325 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Sat, 21 Jun 2025 09:01:20 -0500 Subject: [PATCH 032/349] Restore Docker image annotations and fix attestations (#14165) More follow-up to #13459 - Depot doesn't support annotations, so we push those manually - Docker push for the re-tag was breaking the manifest, since we need to annotate manually, we just do that instead - We attest after the annotation A bit of an aside - We test building the extra images, it's very fast and I don't see why it's better to gate it I tested this on my fork then cleaned it up a bit for a commit here. You can see the images at - https://github.com/zanieb/uv/pkgs/container/uv - https://hub.docker.com/r/astral/uv/tags --------- Co-authored-by: samypr100 <3933065+samypr100@users.noreply.github.com> --- .github/workflows/build-docker.yml | 126 +++++++++++++++++++++++++---- 1 file changed, 110 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 1f5229aef..1ac48ee0f 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -80,7 +80,9 @@ jobs: name: release outputs: image-tags: ${{ steps.meta.outputs.tags }} + image-annotations: ${{ steps.meta.outputs.annotations }} image-digest: ${{ steps.build.outputs.digest }} + image-version: ${{ steps.meta.outputs.version }} steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: @@ -117,6 +119,8 @@ jobs: - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0 + env: + DOCKER_METADATA_ANNOTATIONS_LEVELS: index with: images: | ${{ env.UV_GHCR_IMAGE }} @@ -137,10 +141,12 @@ jobs: push: ${{ needs.docker-plan.outputs.push }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + # TODO(zanieb): Annotations are not supported by Depot yet and are ignored + annotations: ${{ steps.meta.outputs.annotations }} - name: Generate artifact attestation for base image if: ${{ needs.docker-plan.outputs.push == 'true' }} - uses: actions/attest-build-provenance@c074443f1aee8d4aeeae555aebba3282517141b2 # v2.2.3 + uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0 with: subject-name: ${{ env.UV_GHCR_IMAGE }} subject-digest: ${{ steps.build.outputs.digest }} @@ -153,7 +159,6 @@ jobs: needs: - docker-plan - docker-publish-base - if: ${{ needs.docker-plan.outputs.push == 'true' }} permissions: id-token: write # for Depot OIDC and GHCR signing packages: write # for GHCR image pushes @@ -263,20 +268,66 @@ jobs: context: . project: 7hd4vdzmw5 # astral-sh/uv platforms: linux/amd64,linux/arm64 - push: true + push: ${{ needs.docker-plan.outputs.push }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + # TODO(zanieb): Annotations are not supported by Depot yet and are ignored annotations: ${{ steps.meta.outputs.annotations }} - name: Generate artifact attestation + if: ${{ needs.docker-plan.outputs.push == 'true' }} uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0 with: subject-name: ${{ env.UV_GHCR_IMAGE }} subject-digest: ${{ steps.build-and-push.outputs.digest }} - # Re-tag the base image, to ensure it's shown as the newest on the registry UI - docker-retag-base: - name: retag uv + # Push annotations manually. + # See `docker-annotate-base` for details. + - name: Add annotations to images + if: ${{ needs.docker-plan.outputs.push == 'true' }} + env: + IMAGES: "${{ env.UV_GHCR_IMAGE }} ${{ env.UV_DOCKERHUB_IMAGE }}" + DIGEST: ${{ steps.build-and-push.outputs.digest }} + TAGS: ${{ steps.meta.outputs.tags }} + ANNOTATIONS: ${{ steps.meta.outputs.annotations }} + run: | + set -x + readarray -t lines <<< "$ANNOTATIONS"; annotations=(); for line in "${lines[@]}"; do annotations+=(--annotation "$line"); done + for image in $IMAGES; do + readarray -t lines < <(grep "^${image}:" <<< "$TAGS"); tags=(); for line in "${lines[@]}"; do tags+=(-t "$line"); done + docker buildx imagetools create \ + "${annotations[@]}" \ + "${tags[@]}" \ + "${image}@${DIGEST}" + done + + # See `docker-annotate-base` for details. + - name: Export manifest digest + id: manifest-digest + if: ${{ needs.docker-plan.outputs.push == 'true' }} + env: + IMAGE: ${{ env.UV_GHCR_IMAGE }} + VERSION: ${{ steps.meta.outputs.version }} + run: | + digest="$( + docker buildx imagetools inspect \ + "${IMAGE}:${VERSION}" \ + --format '{{json .Manifest}}' \ + | jq -r '.digest' + )" + echo "digest=${digest}" >> "$GITHUB_OUTPUT" + + # See `docker-annotate-base` for details. + - name: Generate artifact attestation + if: ${{ needs.docker-plan.outputs.push == 'true' }} + uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0 + with: + subject-name: ${{ env.UV_GHCR_IMAGE }} + subject-digest: ${{ steps.manifest-digest.outputs.digest }} + + # Annotate the base image + docker-annotate-base: + name: annotate uv runs-on: ubuntu-latest environment: name: release @@ -286,24 +337,67 @@ jobs: - docker-publish-extra if: ${{ needs.docker-plan.outputs.push == 'true' }} steps: + - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 + with: + username: astral + password: ${{ secrets.DOCKERHUB_TOKEN_RW }} + - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Push tags + # Depot doesn't support annotating images, so we need to do so manually + # afterwards. Mutating the manifest is desirable regardless, because we + # want to bump the base image to appear at the top of the list on GHCR. + # However, once annotation support is added to Depot, this step can be + # minimized to just touch the GHCR manifest. + - name: Add annotations to images env: - IMAGE: ${{ env.UV_GHCR_IMAGE }} + IMAGES: "${{ env.UV_GHCR_IMAGE }} ${{ env.UV_DOCKERHUB_IMAGE }}" DIGEST: ${{ needs.docker-publish-base.outputs.image-digest }} TAGS: ${{ needs.docker-publish-base.outputs.image-tags }} + ANNOTATIONS: ${{ needs.docker-publish-base.outputs.image-annotations }} + # The readarray part is used to make sure the quoting and special characters are preserved on expansion (e.g. spaces) + # The final command becomes `docker buildx imagetools create --annotation 'index:foo=1' --annotation 'index:bar=2' ... -t tag1 -t tag2 ... @sha256:` run: | - docker pull "${IMAGE}@${DIGEST}" - for tag in $TAGS; do - # Skip re-tag for DockerHub - if [[ "$tag" == "${{ env.UV_DOCKERHUB_IMAGE }}"* ]]; then - continue - fi - docker tag "${IMAGE}@${DIGEST}" "${tag}" - docker push "${tag}" + set -x + readarray -t lines <<< "$ANNOTATIONS"; annotations=(); for line in "${lines[@]}"; do annotations+=(--annotation "$line"); done + for image in $IMAGES; do + readarray -t lines < <(grep "^${image}:" <<< "$TAGS"); tags=(); for line in "${lines[@]}"; do tags+=(-t "$line"); done + docker buildx imagetools create \ + "${annotations[@]}" \ + "${tags[@]}" \ + "${image}@${DIGEST}" done + + # Now that we've modified the manifest, we need to attest it again. + # Note we only generate an attestation for GHCR. + - name: Export manifest digest + id: manifest-digest + env: + IMAGE: ${{ env.UV_GHCR_IMAGE }} + VERSION: ${{ needs.docker-publish-base.outputs.image-version }} + # To sign the manifest, we need it's digest. Unfortunately "docker + # buildx imagetools create" does not (yet) have a clean way of sharing + # the digest of the manifest it creates (see docker/buildx#2407), so + # we use a separate command to retrieve it. + # imagetools inspect [TAG] --format '{{json .Manifest}}' gives us + # the machine readable JSON description of the manifest, and the + # jq command extracts the digest from this. The digest is then + # sent to the Github step output file for sharing with other steps. + run: | + digest="$( + docker buildx imagetools inspect \ + "${IMAGE}:${VERSION}" \ + --format '{{json .Manifest}}' \ + | jq -r '.digest' + )" + echo "digest=${digest}" >> "$GITHUB_OUTPUT" + + - name: Generate artifact attestation + uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0 + with: + subject-name: ${{ env.UV_GHCR_IMAGE }} + subject-digest: ${{ steps.manifest-digest.outputs.digest }} From f0407e4b6f29ea053efb7f8eec12435d54f05f76 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Sat, 21 Jun 2025 09:36:07 -0500 Subject: [PATCH 033/349] Only use the `release` environment in Docker builds on push (#14189) This drastically reduces the `release` deployment noise, e.g., as seen in https://github.com/astral-sh/uv/pull/14165 --- .github/workflows/build-docker.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 1ac48ee0f..25e042275 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -77,7 +77,7 @@ jobs: packages: write # for GHCR image pushes attestations: write # for GHCR attestations environment: - name: release + name: ${{ needs.docker-plan.outputs.push == 'true' && 'release' || '' }} outputs: image-tags: ${{ steps.meta.outputs.tags }} image-annotations: ${{ steps.meta.outputs.annotations }} @@ -155,7 +155,7 @@ jobs: name: ${{ needs.docker-plan.outputs.action }} ${{ matrix.image-mapping }} runs-on: ubuntu-latest environment: - name: release + name: ${{ needs.docker-plan.outputs.push == 'true' && 'release' || '' }} needs: - docker-plan - docker-publish-base @@ -330,7 +330,7 @@ jobs: name: annotate uv runs-on: ubuntu-latest environment: - name: release + name: ${{ needs.docker-plan.outputs.push == 'true' && 'release' || '' }} needs: - docker-plan - docker-publish-base From a82c210cabde1bb4422639d93ae2791a827c0bc9 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sat, 21 Jun 2025 11:21:06 -0400 Subject: [PATCH 034/349] Add auto-detection for AMD GPUs (#14176) ## Summary Allows `--torch-backend=auto` to detect AMD GPUs. The approach is fairly well-documented inline, but I opted for `rocm_agent_enumerator` over (e.g.) `rocminfo` since it seems to be the recommended approach for scripting: https://rocm.docs.amd.com/projects/rocminfo/en/latest/how-to/use-rocm-agent-enumerator.html. Closes https://github.com/astral-sh/uv/issues/14086. ## Test Plan ``` root@rocm-jupyter-gpu-mi300x1-192gb-devcloud-atl1:~# ./uv-linux-libc-11fb582c5c046bae09766ceddd276dcc5bb41218/uv pip install torch --torch-backend=auto Resolved 11 packages in 251ms Prepared 2 packages in 6ms Installed 11 packages in 257ms + filelock==3.18.0 + fsspec==2025.5.1 + jinja2==3.1.6 + markupsafe==3.0.2 + mpmath==1.3.0 + networkx==3.5 + pytorch-triton-rocm==3.3.1 + setuptools==80.9.0 + sympy==1.14.0 + torch==2.7.1+rocm6.3 + typing-extensions==4.14.0 ``` --------- Co-authored-by: Zanie Blue --- crates/uv-resolver/src/resolver/mod.rs | 2 +- crates/uv-static/src/env_vars.rs | 6 +- crates/uv-torch/src/accelerator.rs | 110 +++++++++++++++- crates/uv-torch/src/backend.rs | 168 ++++++++++++++++++++----- docs/guides/integration/pytorch.md | 11 +- 5 files changed, 257 insertions(+), 40 deletions(-) diff --git a/crates/uv-resolver/src/resolver/mod.rs b/crates/uv-resolver/src/resolver/mod.rs index 1384ce4f7..ed1cd48af 100644 --- a/crates/uv-resolver/src/resolver/mod.rs +++ b/crates/uv-resolver/src/resolver/mod.rs @@ -1814,7 +1814,7 @@ impl ResolverState std::fmt::Result { match self { Self::Cuda { driver_version } => write!(f, "CUDA {driver_version}"), + Self::Amd { gpu_architecture } => write!(f, "AMD {gpu_architecture}"), } } } @@ -33,9 +46,11 @@ impl Accelerator { /// /// Query, in order: /// 1. The `UV_CUDA_DRIVER_VERSION` environment variable. + /// 2. The `UV_AMD_GPU_ARCHITECTURE` environment variable. /// 2. `/sys/module/nvidia/version`, which contains the driver version (e.g., `550.144.03`). /// 3. `/proc/driver/nvidia/version`, which contains the driver version among other information. /// 4. `nvidia-smi --query-gpu=driver_version --format=csv,noheader`. + /// 5. `rocm_agent_enumerator`, which lists the AMD GPU architectures. pub fn detect() -> Result, AcceleratorError> { // Read from `UV_CUDA_DRIVER_VERSION`. if let Ok(driver_version) = std::env::var(EnvVars::UV_CUDA_DRIVER_VERSION) { @@ -44,6 +59,15 @@ impl Accelerator { return Ok(Some(Self::Cuda { driver_version })); } + // Read from `UV_AMD_GPU_ARCHITECTURE`. + if let Ok(gpu_architecture) = std::env::var(EnvVars::UV_AMD_GPU_ARCHITECTURE) { + let gpu_architecture = AmdGpuArchitecture::from_str(&gpu_architecture)?; + debug!( + "Detected AMD GPU architecture from `UV_AMD_GPU_ARCHITECTURE`: {gpu_architecture}" + ); + return Ok(Some(Self::Amd { gpu_architecture })); + } + // Read from `/sys/module/nvidia/version`. match fs_err::read_to_string("/sys/module/nvidia/version") { Ok(content) => { @@ -100,7 +124,34 @@ impl Accelerator { ); } - debug!("Failed to detect CUDA driver version"); + // Query `rocm_agent_enumerator` to detect the AMD GPU architecture. + // + // See: https://rocm.docs.amd.com/projects/rocminfo/en/latest/how-to/use-rocm-agent-enumerator.html + if let Ok(output) = std::process::Command::new("rocm_agent_enumerator").output() { + if output.status.success() { + let stdout = String::from_utf8(output.stdout)?; + if let Some(gpu_architecture) = stdout + .lines() + .map(str::trim) + .filter_map(|line| AmdGpuArchitecture::from_str(line).ok()) + .min() + { + debug!( + "Detected AMD GPU architecture from `rocm_agent_enumerator`: {gpu_architecture}" + ); + return Ok(Some(Self::Amd { gpu_architecture })); + } + } else { + debug!( + "Failed to query AMD GPU architecture with `rocm_agent_enumerator` with status `{}`: {}", + output.status, + String::from_utf8_lossy(&output.stderr) + ); + } + } + + debug!("Failed to detect GPU driver version"); + Ok(None) } } @@ -129,6 +180,63 @@ fn parse_proc_driver_nvidia_version(content: &str) -> Result, Ac Ok(Some(driver_version)) } +/// A GPU architecture for AMD GPUs. +/// +/// See: +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub enum AmdGpuArchitecture { + Gfx900, + Gfx906, + Gfx908, + Gfx90a, + Gfx942, + Gfx1030, + Gfx1100, + Gfx1101, + Gfx1102, + Gfx1200, + Gfx1201, +} + +impl FromStr for AmdGpuArchitecture { + type Err = AcceleratorError; + + fn from_str(s: &str) -> Result { + match s { + "gfx900" => Ok(Self::Gfx900), + "gfx906" => Ok(Self::Gfx906), + "gfx908" => Ok(Self::Gfx908), + "gfx90a" => Ok(Self::Gfx90a), + "gfx942" => Ok(Self::Gfx942), + "gfx1030" => Ok(Self::Gfx1030), + "gfx1100" => Ok(Self::Gfx1100), + "gfx1101" => Ok(Self::Gfx1101), + "gfx1102" => Ok(Self::Gfx1102), + "gfx1200" => Ok(Self::Gfx1200), + "gfx1201" => Ok(Self::Gfx1201), + _ => Err(AcceleratorError::UnknownAmdGpuArchitecture(s.to_string())), + } + } +} + +impl std::fmt::Display for AmdGpuArchitecture { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Gfx900 => write!(f, "gfx900"), + Self::Gfx906 => write!(f, "gfx906"), + Self::Gfx908 => write!(f, "gfx908"), + Self::Gfx90a => write!(f, "gfx90a"), + Self::Gfx942 => write!(f, "gfx942"), + Self::Gfx1030 => write!(f, "gfx1030"), + Self::Gfx1100 => write!(f, "gfx1100"), + Self::Gfx1101 => write!(f, "gfx1101"), + Self::Gfx1102 => write!(f, "gfx1102"), + Self::Gfx1200 => write!(f, "gfx1200"), + Self::Gfx1201 => write!(f, "gfx1201"), + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/uv-torch/src/backend.rs b/crates/uv-torch/src/backend.rs index 60d43f3d7..0f2b72077 100644 --- a/crates/uv-torch/src/backend.rs +++ b/crates/uv-torch/src/backend.rs @@ -47,7 +47,7 @@ use uv_normalize::PackageName; use uv_pep440::Version; use uv_platform_tags::Os; -use crate::{Accelerator, AcceleratorError}; +use crate::{Accelerator, AcceleratorError, AmdGpuArchitecture}; /// The strategy to use when determining the appropriate PyTorch index. #[derive(Debug, Copy, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] @@ -178,8 +178,13 @@ pub enum TorchMode { /// The strategy to use when determining the appropriate PyTorch index. #[derive(Debug, Clone, Eq, PartialEq)] pub enum TorchStrategy { - /// Select the appropriate PyTorch index based on the operating system and CUDA driver version. - Auto { os: Os, driver_version: Version }, + /// Select the appropriate PyTorch index based on the operating system and CUDA driver version (e.g., `550.144.03`). + Cuda { os: Os, driver_version: Version }, + /// Select the appropriate PyTorch index based on the operating system and AMD GPU architecture (e.g., `gfx1100`). + Amd { + os: Os, + gpu_architecture: AmdGpuArchitecture, + }, /// Use the specified PyTorch index. Backend(TorchBackend), } @@ -188,16 +193,17 @@ impl TorchStrategy { /// Determine the [`TorchStrategy`] from the given [`TorchMode`], [`Os`], and [`Accelerator`]. pub fn from_mode(mode: TorchMode, os: &Os) -> Result { match mode { - TorchMode::Auto => { - if let Some(Accelerator::Cuda { driver_version }) = Accelerator::detect()? { - Ok(Self::Auto { - os: os.clone(), - driver_version: driver_version.clone(), - }) - } else { - Ok(Self::Backend(TorchBackend::Cpu)) - } - } + TorchMode::Auto => match Accelerator::detect()? { + Some(Accelerator::Cuda { driver_version }) => Ok(Self::Cuda { + os: os.clone(), + driver_version: driver_version.clone(), + }), + Some(Accelerator::Amd { gpu_architecture }) => Ok(Self::Amd { + os: os.clone(), + gpu_architecture, + }), + None => Ok(Self::Backend(TorchBackend::Cpu)), + }, TorchMode::Cpu => Ok(Self::Backend(TorchBackend::Cpu)), TorchMode::Cu128 => Ok(Self::Backend(TorchBackend::Cu128)), TorchMode::Cu126 => Ok(Self::Backend(TorchBackend::Cu126)), @@ -267,25 +273,27 @@ impl TorchStrategy { /// Return the appropriate index URLs for the given [`TorchStrategy`]. pub fn index_urls(&self) -> impl Iterator { match self { - TorchStrategy::Auto { os, driver_version } => { + TorchStrategy::Cuda { os, driver_version } => { // If this is a GPU-enabled package, and CUDA drivers are installed, use PyTorch's CUDA // indexes. // // See: https://github.com/pmeier/light-the-torch/blob/33397cbe45d07b51ad8ee76b004571a4c236e37f/light_the_torch/_patch.py#L36-L49 match os { - Os::Manylinux { .. } | Os::Musllinux { .. } => Either::Left(Either::Left( - LINUX_DRIVERS - .iter() - .filter_map(move |(backend, version)| { - if driver_version >= version { - Some(backend.index_url()) - } else { - None - } - }) - .chain(std::iter::once(TorchBackend::Cpu.index_url())), - )), - Os::Windows => Either::Left(Either::Right( + Os::Manylinux { .. } | Os::Musllinux { .. } => { + Either::Left(Either::Left(Either::Left( + LINUX_CUDA_DRIVERS + .iter() + .filter_map(move |(backend, version)| { + if driver_version >= version { + Some(backend.index_url()) + } else { + None + } + }) + .chain(std::iter::once(TorchBackend::Cpu.index_url())), + ))) + } + Os::Windows => Either::Left(Either::Left(Either::Right( WINDOWS_CUDA_VERSIONS .iter() .filter_map(move |(backend, version)| { @@ -296,7 +304,7 @@ impl TorchStrategy { } }) .chain(std::iter::once(TorchBackend::Cpu.index_url())), - )), + ))), Os::Macos { .. } | Os::FreeBsd { .. } | Os::NetBsd { .. } @@ -306,11 +314,42 @@ impl TorchStrategy { | Os::Haiku { .. } | Os::Android { .. } | Os::Pyodide { .. } => { - Either::Right(std::iter::once(TorchBackend::Cpu.index_url())) + Either::Right(Either::Left(std::iter::once(TorchBackend::Cpu.index_url()))) } } } - TorchStrategy::Backend(backend) => Either::Right(std::iter::once(backend.index_url())), + TorchStrategy::Amd { + os, + gpu_architecture, + } => match os { + Os::Manylinux { .. } | Os::Musllinux { .. } => Either::Left(Either::Right( + LINUX_AMD_GPU_DRIVERS + .iter() + .filter_map(move |(backend, architecture)| { + if gpu_architecture == architecture { + Some(backend.index_url()) + } else { + None + } + }) + .chain(std::iter::once(TorchBackend::Cpu.index_url())), + )), + Os::Windows + | Os::Macos { .. } + | Os::FreeBsd { .. } + | Os::NetBsd { .. } + | Os::OpenBsd { .. } + | Os::Dragonfly { .. } + | Os::Illumos { .. } + | Os::Haiku { .. } + | Os::Android { .. } + | Os::Pyodide { .. } => { + Either::Right(Either::Left(std::iter::once(TorchBackend::Cpu.index_url()))) + } + }, + TorchStrategy::Backend(backend) => { + Either::Right(Either::Right(std::iter::once(backend.index_url()))) + } } } } @@ -578,7 +617,7 @@ impl FromStr for TorchBackend { /// Linux CUDA driver versions and the corresponding CUDA versions. /// /// See: -static LINUX_DRIVERS: LazyLock<[(TorchBackend, Version); 24]> = LazyLock::new(|| { +static LINUX_CUDA_DRIVERS: LazyLock<[(TorchBackend, Version); 24]> = LazyLock::new(|| { [ // Table 2 from // https://docs.nvidia.com/cuda/cuda-toolkit-release-notes/index.html @@ -651,6 +690,73 @@ static WINDOWS_CUDA_VERSIONS: LazyLock<[(TorchBackend, Version); 24]> = LazyLock ] }); +/// Linux AMD GPU architectures and the corresponding PyTorch backends. +/// +/// These were inferred by running the following snippet for each ROCm version: +/// +/// ```python +/// import torch +/// +/// print(torch.cuda.get_arch_list()) +/// ``` +/// +/// AMD also provides a compatibility matrix: ; +/// however, this list includes a broader array of GPUs than those in the matrix. +static LINUX_AMD_GPU_DRIVERS: LazyLock<[(TorchBackend, AmdGpuArchitecture); 44]> = + LazyLock::new(|| { + [ + // ROCm 6.3 + (TorchBackend::Rocm63, AmdGpuArchitecture::Gfx900), + (TorchBackend::Rocm63, AmdGpuArchitecture::Gfx906), + (TorchBackend::Rocm63, AmdGpuArchitecture::Gfx908), + (TorchBackend::Rocm63, AmdGpuArchitecture::Gfx90a), + (TorchBackend::Rocm63, AmdGpuArchitecture::Gfx942), + (TorchBackend::Rocm63, AmdGpuArchitecture::Gfx1030), + (TorchBackend::Rocm63, AmdGpuArchitecture::Gfx1100), + (TorchBackend::Rocm63, AmdGpuArchitecture::Gfx1101), + (TorchBackend::Rocm63, AmdGpuArchitecture::Gfx1102), + (TorchBackend::Rocm63, AmdGpuArchitecture::Gfx1200), + (TorchBackend::Rocm63, AmdGpuArchitecture::Gfx1201), + // ROCm 6.2.4 + (TorchBackend::Rocm624, AmdGpuArchitecture::Gfx900), + (TorchBackend::Rocm624, AmdGpuArchitecture::Gfx906), + (TorchBackend::Rocm624, AmdGpuArchitecture::Gfx908), + (TorchBackend::Rocm624, AmdGpuArchitecture::Gfx90a), + (TorchBackend::Rocm624, AmdGpuArchitecture::Gfx942), + (TorchBackend::Rocm624, AmdGpuArchitecture::Gfx1030), + (TorchBackend::Rocm624, AmdGpuArchitecture::Gfx1100), + (TorchBackend::Rocm624, AmdGpuArchitecture::Gfx1101), + (TorchBackend::Rocm624, AmdGpuArchitecture::Gfx1102), + (TorchBackend::Rocm624, AmdGpuArchitecture::Gfx1200), + (TorchBackend::Rocm624, AmdGpuArchitecture::Gfx1201), + // ROCm 6.2 + (TorchBackend::Rocm62, AmdGpuArchitecture::Gfx900), + (TorchBackend::Rocm62, AmdGpuArchitecture::Gfx906), + (TorchBackend::Rocm62, AmdGpuArchitecture::Gfx908), + (TorchBackend::Rocm62, AmdGpuArchitecture::Gfx90a), + (TorchBackend::Rocm62, AmdGpuArchitecture::Gfx1030), + (TorchBackend::Rocm62, AmdGpuArchitecture::Gfx1100), + (TorchBackend::Rocm62, AmdGpuArchitecture::Gfx942), + // ROCm 6.1 + (TorchBackend::Rocm61, AmdGpuArchitecture::Gfx900), + (TorchBackend::Rocm61, AmdGpuArchitecture::Gfx906), + (TorchBackend::Rocm61, AmdGpuArchitecture::Gfx908), + (TorchBackend::Rocm61, AmdGpuArchitecture::Gfx90a), + (TorchBackend::Rocm61, AmdGpuArchitecture::Gfx942), + (TorchBackend::Rocm61, AmdGpuArchitecture::Gfx1030), + (TorchBackend::Rocm61, AmdGpuArchitecture::Gfx1100), + (TorchBackend::Rocm61, AmdGpuArchitecture::Gfx1101), + // ROCm 6.0 + (TorchBackend::Rocm60, AmdGpuArchitecture::Gfx900), + (TorchBackend::Rocm60, AmdGpuArchitecture::Gfx906), + (TorchBackend::Rocm60, AmdGpuArchitecture::Gfx908), + (TorchBackend::Rocm60, AmdGpuArchitecture::Gfx90a), + (TorchBackend::Rocm60, AmdGpuArchitecture::Gfx1030), + (TorchBackend::Rocm60, AmdGpuArchitecture::Gfx1100), + (TorchBackend::Rocm60, AmdGpuArchitecture::Gfx942), + ] + }); + static CPU_INDEX_URL: LazyLock = LazyLock::new(|| IndexUrl::from_str("https://download.pytorch.org/whl/cpu").unwrap()); static CU128_INDEX_URL: LazyLock = diff --git a/docs/guides/integration/pytorch.md b/docs/guides/integration/pytorch.md index 7a2500ec5..a90ebeb6b 100644 --- a/docs/guides/integration/pytorch.md +++ b/docs/guides/integration/pytorch.md @@ -444,10 +444,10 @@ $ # With an environment variable. $ UV_TORCH_BACKEND=auto uv pip install torch ``` -When enabled, uv will query for the installed CUDA driver version and use the most-compatible -PyTorch index for all relevant packages (e.g., `torch`, `torchvision`, etc.). If no such CUDA driver -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. +When enabled, uv will query for the installed CUDA driver and AMD GPU versions then use the +most-compatible PyTorch index for all relevant packages (e.g., `torch`, `torchvision`, 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.6) with `--torch-backend=cu126` (or `UV_TORCH_BACKEND=cu126`): @@ -460,5 +460,4 @@ $ # With an environment variable. $ UV_TORCH_BACKEND=cu126 uv pip install torch torchvision ``` -At present, `--torch-backend` is only available in the `uv pip` interface, and only supports -detection of CUDA drivers (as opposed to other accelerators like ROCm or Intel GPUs). +At present, `--torch-backend` is only available in the `uv pip` interface. From 7fce3a88b8e1a947f3c293703e92723e89625662 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 12:04:10 +0200 Subject: [PATCH 035/349] Update aws-actions/configure-aws-credentials digest to 3bb878b (#14203) 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 | action | digest | `b475783` -> `3bb878b` | --- > [!WARNING] > Some dependencies could not be looked up. Check the Dependency Dashboard for more information. --- ### Configuration 📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC), 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 459495600..9e6e2e51c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1443,7 +1443,7 @@ jobs: run: chmod +x ./uv - name: "Configure AWS credentials" - uses: aws-actions/configure-aws-credentials@b47578312673ae6fa5b5096b330d9fbac3d116df + uses: aws-actions/configure-aws-credentials@3bb878b6ab43ba8717918141cd07a0ea68cfe7ea with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} From a52595b61adfc3d4b202f7c37855b31db37a8e2f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 12:04:50 +0200 Subject: [PATCH 036/349] Update google-github-actions/auth digest to 0920706 (#14204) 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 | |---|---|---|---| | google-github-actions/auth | action | digest | `ba79af0` -> `0920706` | --- > [!WARNING] > Some dependencies could not be looked up. Check the Dependency Dashboard for more information. --- ### Configuration 📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC), 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 9e6e2e51c..dd0882f29 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1462,7 +1462,7 @@ jobs: - name: "Authenticate with GCP" id: "auth" - uses: "google-github-actions/auth@ba79af03959ebeac9769e648f473a284504d9193" + uses: "google-github-actions/auth@0920706a19e9d22c3d0da43d1db5939c6ad837a8" with: credentials_json: "${{ secrets.GCP_SERVICE_ACCOUNT_KEY }}" From 46221b40c3cc4ab1e24d5bea8bedb13809536798 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 12:06:20 +0200 Subject: [PATCH 037/349] Update EmbarkStudios/cargo-deny-action action to v2.0.12 (#14206) 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 | |---|---|---|---| | [EmbarkStudios/cargo-deny-action](https://redirect.github.com/EmbarkStudios/cargo-deny-action) | action | patch | `v2.0.11` -> `v2.0.12` | --- > [!WARNING] > Some dependencies could not be looked up. Check the Dependency Dashboard for more information. --- ### Release Notes
    EmbarkStudios/cargo-deny-action (EmbarkStudios/cargo-deny-action) ### [`v2.0.12`](https://redirect.github.com/EmbarkStudios/cargo-deny-action/releases/tag/v2.0.12): Release 2.0.12 - cargo-deny 0.18.3 [Compare Source](https://redirect.github.com/EmbarkStudios/cargo-deny-action/compare/v2.0.11...v2.0.12) ##### Changed - [PR#773](https://redirect.github.com/EmbarkStudios/cargo-deny/pull/773) changed cargo-deny's duplicate detection to automatically ignore versions whose only dependent is another version of the same crate.
    --- ### Configuration 📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC), 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 dd0882f29..d730f9190 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -130,7 +130,7 @@ jobs: with: save-if: ${{ github.ref == 'refs/heads/main' }} - name: "Check uv_build dependencies" - uses: EmbarkStudios/cargo-deny-action@34899fc7ba81ca6268d5947a7a16b4649013fea1 # v2.0.11 + uses: EmbarkStudios/cargo-deny-action@30f817c6f72275c6d54dc744fbca09ebc958599f # v2.0.12 with: command: check bans manifest-path: crates/uv-build/Cargo.toml From 3e9dbe8b7d14cf143de9d3b5195c357edc8c7b76 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 12:07:09 +0200 Subject: [PATCH 038/349] Update Rust crate mimalloc to v0.1.47 (#14207) 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 | |---|---|---|---| | [mimalloc](https://redirect.github.com/purpleprotocol/mimalloc_rust) | dependencies | patch | `0.1.46` -> `0.1.47` | --- > [!WARNING] > Some dependencies could not be looked up. Check the Dependency Dashboard for more information. --- ### Release Notes
    purpleprotocol/mimalloc_rust (mimalloc) ### [`v0.1.47`](https://redirect.github.com/purpleprotocol/mimalloc_rust/releases/tag/v0.1.47): Version 0.1.47 [Compare Source](https://redirect.github.com/purpleprotocol/mimalloc_rust/compare/v0.1.46...v0.1.47) ##### Changes - Mimalloc `v2.2.4`
    --- ### Configuration 📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC), 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> --- crates/uv-performance-memory-allocator/Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/uv-performance-memory-allocator/Cargo.lock b/crates/uv-performance-memory-allocator/Cargo.lock index 831d5a0f9..e1650c824 100644 --- a/crates/uv-performance-memory-allocator/Cargo.lock +++ b/crates/uv-performance-memory-allocator/Cargo.lock @@ -19,9 +19,9 @@ checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libmimalloc-sys" -version = "0.1.42" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec9d6fac27761dabcd4ee73571cdb06b7022dc99089acbe5435691edffaac0f4" +checksum = "bf88cd67e9de251c1781dbe2f641a1a3ad66eaae831b8a2c38fbdc5ddae16d4d" dependencies = [ "cc", "libc", @@ -29,9 +29,9 @@ dependencies = [ [[package]] name = "mimalloc" -version = "0.1.46" +version = "0.1.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "995942f432bbb4822a7e9c3faa87a695185b0d09273ba85f097b54f4e458f2af" +checksum = "b1791cbe101e95af5764f06f20f6760521f7158f69dbf9d6baf941ee1bf6bc40" dependencies = [ "libmimalloc-sys", ] From ac1405e06c628c72cceecdd0dbe889c7e2b6b472 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 12:07:36 +0200 Subject: [PATCH 039/349] Update Rust crate syn to v2.0.104 (#14208) 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 | |---|---|---|---| | [syn](https://redirect.github.com/dtolnay/syn) | workspace.dependencies | patch | `2.0.103` -> `2.0.104` | --- > [!WARNING] > Some dependencies could not be looked up. Check the Dependency Dashboard for more information. --- ### Release Notes
    dtolnay/syn (syn) ### [`v2.0.104`](https://redirect.github.com/dtolnay/syn/releases/tag/2.0.104) [Compare Source](https://redirect.github.com/dtolnay/syn/compare/2.0.103...2.0.104) - Disallow attributes on range expression ([#​1872](https://redirect.github.com/dtolnay/syn/issues/1872))
    --- ### Configuration 📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC), 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 6c1978cd0..ce32fb893 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3799,9 +3799,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.103" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", From a9a9e714819df970ccaa8af7651fa2d1f97a3672 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 10:20:59 +0000 Subject: [PATCH 040/349] Update google-github-actions/setup-gcloud digest to a8b5801 (#14205) 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 | |---|---|---|---| | google-github-actions/setup-gcloud | action | digest | `77e7a55` -> `a8b5801` | --- > [!WARNING] > Some dependencies could not be looked up. Check the Dependency Dashboard for more information. --- ### Configuration 📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC), 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 d730f9190..b33f96fe2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1467,7 +1467,7 @@ jobs: credentials_json: "${{ secrets.GCP_SERVICE_ACCOUNT_KEY }}" - name: "Set up GCP SDK" - uses: "google-github-actions/setup-gcloud@77e7a554d41e2ee56fc945c52dfd3f33d12def9a" + uses: "google-github-actions/setup-gcloud@a8b58010a5b2a061afd605f50e88629c9ec7536b" - name: "Get GCP Artifact Registry token" id: get_token From 6481aa3e64f1fad2f418502cb6b0103353790ee6 Mon Sep 17 00:00:00 2001 From: John Mumm Date: Mon, 23 Jun 2025 09:12:43 -0400 Subject: [PATCH 041/349] Consolidate logic for checking for a virtual environment (#14214) We were checking whether a path was an executable in a virtual environment or the base directory of a virtual environment in multiple places in the codebase. This PR consolidates this logic into one place. Closes #13947. --- crates/uv-fs/src/lib.rs | 24 ++++++++++++++++++++++++ crates/uv-python/src/discovery.rs | 9 ++------- crates/uv-python/src/interpreter.rs | 7 +------ crates/uv-python/src/virtualenv.rs | 4 ++-- crates/uv-virtualenv/src/virtualenv.rs | 2 +- 5 files changed, 30 insertions(+), 16 deletions(-) diff --git a/crates/uv-fs/src/lib.rs b/crates/uv-fs/src/lib.rs index 0b5055b40..ad4a883ad 100644 --- a/crates/uv-fs/src/lib.rs +++ b/crates/uv-fs/src/lib.rs @@ -575,6 +575,30 @@ pub fn is_temporary(path: impl AsRef) -> bool { .is_some_and(|name| name.starts_with(".tmp")) } +/// Checks if the grandparent directory of the given executable is the base +/// of a virtual environment. +/// +/// The procedure described in PEP 405 includes checking both the parent and +/// grandparent directory of an executable, but in practice we've found this to +/// be unnecessary. +pub fn is_virtualenv_executable(executable: impl AsRef) -> bool { + executable + .as_ref() + .parent() + .and_then(Path::parent) + .is_some_and(is_virtualenv_base) +} + +/// Returns `true` if a path is the base path of a virtual environment, +/// indicated by the presence of a `pyvenv.cfg` file. +/// +/// The procedure described in PEP 405 includes scanning `pyvenv.cfg` +/// for a `home` key, but in practice we've found this to be +/// unnecessary. +pub fn is_virtualenv_base(path: impl AsRef) -> bool { + path.as_ref().join("pyvenv.cfg").is_file() +} + /// A file lock that is automatically released when dropped. #[derive(Debug)] pub struct LockedFile(fs_err::File); diff --git a/crates/uv-python/src/discovery.rs b/crates/uv-python/src/discovery.rs index eaf4b4830..d1f3a690a 100644 --- a/crates/uv-python/src/discovery.rs +++ b/crates/uv-python/src/discovery.rs @@ -888,13 +888,8 @@ impl Error { | InterpreterError::BrokenSymlink(BrokenSymlink { path, .. }) => { // If the interpreter is from an active, valid virtual environment, we should // fail because it's broken - if let Some(Ok(true)) = matches!(source, PythonSource::ActiveEnvironment) - .then(|| { - path.parent() - .and_then(Path::parent) - .map(|path| path.join("pyvenv.cfg").try_exists()) - }) - .flatten() + if matches!(source, PythonSource::ActiveEnvironment) + && uv_fs::is_virtualenv_executable(path) { true } else { diff --git a/crates/uv-python/src/interpreter.rs b/crates/uv-python/src/interpreter.rs index 19e790b7e..b62633283 100644 --- a/crates/uv-python/src/interpreter.rs +++ b/crates/uv-python/src/interpreter.rs @@ -993,14 +993,9 @@ impl InterpreterInfo { .symlink_metadata() .is_ok_and(|metadata| metadata.is_symlink()) { - let venv = executable - .parent() - .and_then(Path::parent) - .map(|path| path.join("pyvenv.cfg").is_file()) - .unwrap_or(false); Error::BrokenSymlink(BrokenSymlink { path: executable.to_path_buf(), - venv, + venv: uv_fs::is_virtualenv_executable(executable), }) } else { Error::NotFound(executable.to_path_buf()) diff --git a/crates/uv-python/src/virtualenv.rs b/crates/uv-python/src/virtualenv.rs index ea578fff3..8b51a5e1b 100644 --- a/crates/uv-python/src/virtualenv.rs +++ b/crates/uv-python/src/virtualenv.rs @@ -130,14 +130,14 @@ pub(crate) fn virtualenv_from_working_dir() -> Result, Error> { for dir in current_dir.ancestors() { // If we're _within_ a virtualenv, return it. - if dir.join("pyvenv.cfg").is_file() { + if uv_fs::is_virtualenv_base(dir) { return Ok(Some(dir.to_path_buf())); } // Otherwise, search for a `.venv` directory. let dot_venv = dir.join(".venv"); if dot_venv.is_dir() { - if !dot_venv.join("pyvenv.cfg").is_file() { + if !uv_fs::is_virtualenv_base(&dot_venv) { return Err(Error::MissingPyVenvCfg(dot_venv)); } return Ok(Some(dot_venv)); diff --git a/crates/uv-virtualenv/src/virtualenv.rs b/crates/uv-virtualenv/src/virtualenv.rs index bb6db01a3..f466233c0 100644 --- a/crates/uv-virtualenv/src/virtualenv.rs +++ b/crates/uv-virtualenv/src/virtualenv.rs @@ -85,7 +85,7 @@ pub(crate) fn create( } else if metadata.is_dir() { if allow_existing { debug!("Allowing existing directory"); - } else if location.join("pyvenv.cfg").is_file() { + } else if uv_fs::is_virtualenv_base(location) { debug!("Removing existing directory"); // On Windows, if the current executable is in the directory, guard against From b06dec8398af0ef5e5189ac2c4085cfb9f9531f7 Mon Sep 17 00:00:00 2001 From: John Mumm Date: Mon, 23 Jun 2025 09:51:44 -0400 Subject: [PATCH 042/349] Improve Python uninstall perf by removing unnecessary call to `installations.find_all()` (#14180) #13954 introduced an unnecessary slow-down to Python uninstall by calling `installations.find_all()` to discover remaining installations after an uninstall. Instead, we can filter all initial installations against those in `uninstalled`. As part of this change, I've updated `uninstalled` from a `Vec` to an `IndexSet` in order to do efficient lookups in the filter. This required a change I call out below to how we were retrieving them for messaging. --- crates/uv/src/commands/python/uninstall.rs | 30 +++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/crates/uv/src/commands/python/uninstall.rs b/crates/uv/src/commands/python/uninstall.rs index e79ea9b19..642942d07 100644 --- a/crates/uv/src/commands/python/uninstall.rs +++ b/crates/uv/src/commands/python/uninstall.rs @@ -200,13 +200,13 @@ async fn do_uninstall( }); } - let mut uninstalled = vec![]; + let mut uninstalled = IndexSet::::default(); let mut errors = vec![]; while let Some((key, result)) = tasks.next().await { if let Err(err) = result { errors.push((key.clone(), anyhow::Error::new(err))); } else { - uninstalled.push(key.clone()); + uninstalled.insert(key.clone()); } } @@ -223,14 +223,15 @@ async fn do_uninstall( // Read all existing managed installations and find the highest installed patch // for each installed minor version. Ensure the minor version link directory // is still valid. - let uninstalled_minor_versions = &uninstalled.iter().fold( - IndexSet::<&PythonInstallationMinorVersionKey>::default(), - |mut minor_versions, key| { - minor_versions.insert(PythonInstallationMinorVersionKey::ref_cast(key)); - minor_versions - }, - ); - let remaining_installations: Vec<_> = installations.find_all()?.collect(); + let uninstalled_minor_versions: IndexSet<_> = uninstalled + .iter() + .map(PythonInstallationMinorVersionKey::ref_cast) + .collect(); + let remaining_installations: Vec<_> = installed_installations + .into_iter() + .filter(|installation| !uninstalled.contains(installation.key())) + .collect(); + let remaining_minor_versions = PythonInstallationMinorVersionKey::highest_installations_by_minor_version_key( remaining_installations.iter(), @@ -278,28 +279,27 @@ async fn do_uninstall( } // Report on any uninstalled installations. - if !uninstalled.is_empty() { - if let [uninstalled] = uninstalled.as_slice() { + if let Some(first_uninstalled) = uninstalled.first() { + if uninstalled.len() == 1 { // Ex) "Uninstalled Python 3.9.7 in 1.68s" writeln!( printer.stderr(), "{}", format!( "Uninstalled {} {}", - format!("Python {}", uninstalled.version()).bold(), + format!("Python {}", first_uninstalled.version()).bold(), format!("in {}", elapsed(start.elapsed())).dimmed() ) .dimmed() )?; } else { // Ex) "Uninstalled 2 versions in 1.68s" - let s = if uninstalled.len() == 1 { "" } else { "s" }; writeln!( printer.stderr(), "{}", format!( "Uninstalled {} {}", - format!("{} version{s}", uninstalled.len()).bold(), + format!("{} versions", uninstalled.len()).bold(), format!("in {}", elapsed(start.elapsed())).dimmed() ) .dimmed() From 2d2dd0c1a38b81f14484dd5bc37d2eb64d928948 Mon Sep 17 00:00:00 2001 From: John Mumm Date: Mon, 23 Jun 2025 12:19:36 -0400 Subject: [PATCH 043/349] Update upgrade tests to use 3.10.17 instead of 3.10.8 (#14219) @oconnor663 discovered that executing `3.10.8` on Arch Linux ran into an error loading `libcrypt.so.1`. This caused uv to install the latest patch version on `uv venv` operations during upgrade tests, which undermined their purpose (since they are checking that if you first install `3.10.8` and then upgrade, virtual environments are transparently upgraded). This PR updates the test to use `3.10.17` instead to avoid this issue. --- crates/uv/tests/it/python_install.rs | 4 +- crates/uv/tests/it/python_upgrade.rs | 82 ++++++++++++++-------------- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/crates/uv/tests/it/python_install.rs b/crates/uv/tests/it/python_install.rs index d5dec1977..2b6f03d4b 100644 --- a/crates/uv/tests/it/python_install.rs +++ b/crates/uv/tests/it/python_install.rs @@ -1742,14 +1742,14 @@ fn install_multiple_patches() { fs_err::remove_dir_all(&context.venv).unwrap(); // Install 3.10 patches in descending order list - uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.10.17").arg("3.10.8"), @r" + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.10.17").arg("3.10.16"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Installed 2 versions in [TIME] - + cpython-3.10.8-[PLATFORM] + + cpython-3.10.16-[PLATFORM] + cpython-3.10.17-[PLATFORM] (python3.10) " ); diff --git a/crates/uv/tests/it/python_upgrade.rs b/crates/uv/tests/it/python_upgrade.rs index cbea1d404..bf6d45e08 100644 --- a/crates/uv/tests/it/python_upgrade.rs +++ b/crates/uv/tests/it/python_upgrade.rs @@ -13,18 +13,18 @@ fn python_upgrade() { .with_managed_python_dirs(); // Install an earlier patch version - uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.10.8"), @r" + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.10.17"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - Installed Python 3.10.8 in [TIME] - + cpython-3.10.8-[PLATFORM] (python3.10) + Installed Python 3.10.17 in [TIME] + + cpython-3.10.17-[PLATFORM] (python3.10) "); // Don't accept patch version as argument to upgrade command - uv_snapshot!(context.filters(), context.python_upgrade().arg("--preview").arg("3.10.8"), @r" + uv_snapshot!(context.filters(), context.python_upgrade().arg("--preview").arg("3.10.17"), @r" success: false exit_code: 1 ----- stdout ----- @@ -130,14 +130,14 @@ fn python_upgrade_transparent_from_venv() { .with_managed_python_dirs(); // Install an earlier patch version - uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.10.8"), @r" + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.10.17"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - Installed Python 3.10.8 in [TIME] - + cpython-3.10.8-[PLATFORM] (python3.10) + Installed Python 3.10.17 in [TIME] + + cpython-3.10.17-[PLATFORM] (python3.10) "); // Create a virtual environment @@ -147,7 +147,7 @@ fn python_upgrade_transparent_from_venv() { ----- stdout ----- ----- stderr ----- - Using CPython 3.10.8 + Using CPython 3.10.17 Creating virtual environment at: .venv Activate with: source .venv/[BIN]/activate "); @@ -156,7 +156,7 @@ fn python_upgrade_transparent_from_venv() { success: true exit_code: 0 ----- stdout ----- - Python 3.10.8 + Python 3.10.17 ----- stderr ----- " @@ -171,7 +171,7 @@ fn python_upgrade_transparent_from_venv() { ----- stdout ----- ----- stderr ----- - Using CPython 3.10.8 + Using CPython 3.10.17 Creating virtual environment at: .venv2 Activate with: source .venv2/[BIN]/activate "); @@ -181,7 +181,7 @@ fn python_upgrade_transparent_from_venv() { success: true exit_code: 0 ----- stdout ----- - Python 3.10.8 + Python 3.10.17 ----- stderr ----- " @@ -232,14 +232,14 @@ fn python_upgrade_transparent_from_venv_preview() { .with_managed_python_dirs(); // Install an earlier patch version using `--preview` - uv_snapshot!(context.filters(), context.python_install().arg("3.10.8").arg("--preview"), @r" + uv_snapshot!(context.filters(), context.python_install().arg("3.10.17").arg("--preview"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - Installed Python 3.10.8 in [TIME] - + cpython-3.10.8-[PLATFORM] (python3.10) + Installed Python 3.10.17 in [TIME] + + cpython-3.10.17-[PLATFORM] (python3.10) "); // Create a virtual environment @@ -249,7 +249,7 @@ fn python_upgrade_transparent_from_venv_preview() { ----- stdout ----- ----- stderr ----- - Using CPython 3.10.8 + Using CPython 3.10.17 Creating virtual environment at: .venv Activate with: source .venv/[BIN]/activate "); @@ -258,7 +258,7 @@ fn python_upgrade_transparent_from_venv_preview() { success: true exit_code: 0 ----- stdout ----- - Python 3.10.8 + Python 3.10.17 ----- stderr ----- " @@ -295,14 +295,14 @@ fn python_upgrade_ignored_with_python_pin() { .with_managed_python_dirs(); // Install an earlier patch version - uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.10.8"), @r" + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.10.17"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - Installed Python 3.10.8 in [TIME] - + cpython-3.10.8-[PLATFORM] (python3.10) + Installed Python 3.10.17 in [TIME] + + cpython-3.10.17-[PLATFORM] (python3.10) "); // Create a virtual environment @@ -312,17 +312,17 @@ fn python_upgrade_ignored_with_python_pin() { ----- stdout ----- ----- stderr ----- - Using CPython 3.10.8 + Using CPython 3.10.17 Creating virtual environment at: .venv Activate with: source .venv/[BIN]/activate "); // Pin to older patch version - uv_snapshot!(context.filters(), context.python_pin().arg("3.10.8"), @r" + uv_snapshot!(context.filters(), context.python_pin().arg("3.10.17"), @r" success: true exit_code: 0 ----- stdout ----- - Pinned `.python-version` to `3.10.8` + Pinned `.python-version` to `3.10.17` ----- stderr ----- "); @@ -343,7 +343,7 @@ fn python_upgrade_ignored_with_python_pin() { success: true exit_code: 0 ----- stdout ----- - Python 3.10.8 + Python 3.10.17 ----- stderr ----- " @@ -360,24 +360,24 @@ fn python_no_transparent_upgrade_with_venv_patch_specification() { .with_managed_python_dirs(); // Install an earlier patch version - uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.10.8"), @r" + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.10.17"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - Installed Python 3.10.8 in [TIME] - + cpython-3.10.8-[PLATFORM] (python3.10) + Installed Python 3.10.17 in [TIME] + + cpython-3.10.17-[PLATFORM] (python3.10) "); // Create a virtual environment with a patch version - uv_snapshot!(context.filters(), context.venv().arg("-p").arg("3.10.8"), @r" + uv_snapshot!(context.filters(), context.venv().arg("-p").arg("3.10.17"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - Using CPython 3.10.8 + Using CPython 3.10.17 Creating virtual environment at: .venv Activate with: source .venv/[BIN]/activate "); @@ -386,7 +386,7 @@ fn python_no_transparent_upgrade_with_venv_patch_specification() { success: true exit_code: 0 ----- stdout ----- - Python 3.10.8 + Python 3.10.17 ----- stderr ----- " @@ -408,7 +408,7 @@ fn python_no_transparent_upgrade_with_venv_patch_specification() { success: true exit_code: 0 ----- stdout ----- - Python 3.10.8 + Python 3.10.17 ----- stderr ----- " @@ -426,14 +426,14 @@ fn python_transparent_upgrade_venv_venv() { .with_managed_python_dirs(); // Install an earlier patch version - uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.10.8"), @r" + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.10.17"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - Installed Python 3.10.8 in [TIME] - + cpython-3.10.8-[PLATFORM] (python3.10) + Installed Python 3.10.17 in [TIME] + + cpython-3.10.17-[PLATFORM] (python3.10) "); // Create an initial virtual environment @@ -443,7 +443,7 @@ fn python_transparent_upgrade_venv_venv() { ----- stdout ----- ----- stderr ----- - Using CPython 3.10.8 + Using CPython 3.10.17 Creating virtual environment at: .venv Activate with: source .venv/[BIN]/activate "); @@ -465,7 +465,7 @@ fn python_transparent_upgrade_venv_venv() { ----- stdout ----- ----- stderr ----- - Using CPython 3.10.8 interpreter at: .venv/[BIN]/python + Using CPython 3.10.17 interpreter at: .venv/[BIN]/python Creating virtual environment at: .venv2 Activate with: source .venv2/[BIN]/activate "); @@ -477,7 +477,7 @@ fn python_transparent_upgrade_venv_venv() { success: true exit_code: 0 ----- stdout ----- - Python 3.10.8 + Python 3.10.17 ----- stderr ----- " @@ -588,14 +588,14 @@ fn python_upgrade_transparent_from_venv_module_in_venv() { let bin_dir = context.temp_dir.child("bin"); // Install earlier patch version - uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.10.8"), @r" + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.10.17"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - Installed Python 3.10.8 in [TIME] - + cpython-3.10.8-[PLATFORM] (python3.10) + Installed Python 3.10.17 in [TIME] + + cpython-3.10.17-[PLATFORM] (python3.10) "); // Create first virtual environment @@ -605,7 +605,7 @@ fn python_upgrade_transparent_from_venv_module_in_venv() { ----- stdout ----- ----- stderr ----- - Using CPython 3.10.8 + Using CPython 3.10.17 Creating virtual environment at: .venv Activate with: source .venv/[BIN]/activate "); @@ -630,7 +630,7 @@ fn python_upgrade_transparent_from_venv_module_in_venv() { success: true exit_code: 0 ----- stdout ----- - Python 3.10.8 + Python 3.10.17 ----- stderr ----- " From 92de53f4eb9c3869bf36c91b2dcd9f3556eee2bc Mon Sep 17 00:00:00 2001 From: Aria Desires Date: Mon, 23 Jun 2025 12:48:51 -0400 Subject: [PATCH 044/349] Bump version to 0.7.14 (#14218) --- CHANGELOG.md | 38 ++++++++++++++++++++++++++- Cargo.lock | 6 ++--- crates/uv-build/Cargo.toml | 2 +- crates/uv-build/pyproject.toml | 2 +- crates/uv-version/Cargo.toml | 2 +- crates/uv/Cargo.toml | 2 +- docs/concepts/build-backend.md | 2 +- 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/pre-commit.md | 10 +++---- pyproject.toml | 2 +- 13 files changed, 61 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f62cedf62..159c39331 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,44 @@ -## 0.7.13 +## 0.7.14 +### Enhancements + +- Add XPU to `--torch-backend` ([#14172](https://github.com/astral-sh/uv/pull/14172)) +- Add ROCm backends to `--torch-backend` ([#14120](https://github.com/astral-sh/uv/pull/14120)) +- Remove preview label from `--torch-backend` ([#14119](https://github.com/astral-sh/uv/pull/14119)) +- Add `[tool.uv.dependency-groups].mygroup.requires-python` ([#13735](https://github.com/astral-sh/uv/pull/13735)) +- Add auto-detection for AMD GPUs ([#14176](https://github.com/astral-sh/uv/pull/14176)) +- Show retries for HTTP status code errors ([#13897](https://github.com/astral-sh/uv/pull/13897)) +- Support transparent Python patch version upgrades ([#13954](https://github.com/astral-sh/uv/pull/13954)) +- Warn on empty index directory ([#13940](https://github.com/astral-sh/uv/pull/13940)) +- Publish to DockerHub ([#14088](https://github.com/astral-sh/uv/pull/14088)) + +### Performance + +- Make cold resolves about 10% faster ([#14035](https://github.com/astral-sh/uv/pull/14035)) + +### Bug fixes + +- Don't use walrus operator in interpreter query script ([#14108](https://github.com/astral-sh/uv/pull/14108)) +- Fix handling of changes to `requires-python` ([#14076](https://github.com/astral-sh/uv/pull/14076)) +- Fix implied `platform_machine` marker for `win_amd64` platform tag ([#14041](https://github.com/astral-sh/uv/pull/14041)) +- Only update existing symlink directories on preview uninstall ([#14179](https://github.com/astral-sh/uv/pull/14179)) +- Serialize Python requests for tools as canonicalized strings ([#14109](https://github.com/astral-sh/uv/pull/14109)) +- Support netrc and same-origin credential propagation on index redirects ([#14126](https://github.com/astral-sh/uv/pull/14126)) +- Support reading `dependency-groups` from pyproject.tomls with no `[project]` ([#13742](https://github.com/astral-sh/uv/pull/13742)) +- Handle an existing shebang in `uv init --script` ([#14141](https://github.com/astral-sh/uv/pull/14141)) +- Prevent concurrent updates of the environment in `uv run` ([#14153](https://github.com/astral-sh/uv/pull/14153)) +- Filter managed Python distributions by platform before querying when included in request ([#13936](https://github.com/astral-sh/uv/pull/13936)) + +### Documentation + +- Replace cuda124 with cuda128 ([#14168](https://github.com/astral-sh/uv/pull/14168)) +- Document the way member sources shadow workspace sources ([#14136](https://github.com/astral-sh/uv/pull/14136)) +- Sync documented PyTorch integration index for CUDA and ROCm versions from PyTorch website ([#14100](https://github.com/astral-sh/uv/pull/14100)) + +## 0.7.13 ### Python diff --git a/Cargo.lock b/Cargo.lock index ce32fb893..95e9308b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4569,7 +4569,7 @@ dependencies = [ [[package]] name = "uv" -version = "0.7.13" +version = "0.7.14" dependencies = [ "anstream", "anyhow", @@ -4735,7 +4735,7 @@ dependencies = [ [[package]] name = "uv-build" -version = "0.7.13" +version = "0.7.14" dependencies = [ "anyhow", "uv-build-backend", @@ -5923,7 +5923,7 @@ dependencies = [ [[package]] name = "uv-version" -version = "0.7.13" +version = "0.7.14" [[package]] name = "uv-virtualenv" diff --git a/crates/uv-build/Cargo.toml b/crates/uv-build/Cargo.toml index 01f6f7cbe..26c046b2b 100644 --- a/crates/uv-build/Cargo.toml +++ b/crates/uv-build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-build" -version = "0.7.13" +version = "0.7.14" edition.workspace = true rust-version.workspace = true homepage.workspace = true diff --git a/crates/uv-build/pyproject.toml b/crates/uv-build/pyproject.toml index 7e556e6bf..4dab6a520 100644 --- a/crates/uv-build/pyproject.toml +++ b/crates/uv-build/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uv-build" -version = "0.7.13" +version = "0.7.14" description = "The uv build backend" authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }] requires-python = ">=3.8" diff --git a/crates/uv-version/Cargo.toml b/crates/uv-version/Cargo.toml index a029b8196..b92014be4 100644 --- a/crates/uv-version/Cargo.toml +++ b/crates/uv-version/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-version" -version = "0.7.13" +version = "0.7.14" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml index de4fa3c38..e63cbfe40 100644 --- a/crates/uv/Cargo.toml +++ b/crates/uv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv" -version = "0.7.13" +version = "0.7.14" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } diff --git a/docs/concepts/build-backend.md b/docs/concepts/build-backend.md index 8d0dfa533..2162aaaa5 100644 --- a/docs/concepts/build-backend.md +++ b/docs/concepts/build-backend.md @@ -19,7 +19,7 @@ existing project, add it to the `[build-system]` section in your `pyproject.toml ```toml [build-system] -requires = ["uv_build>=0.7.13,<0.8.0"] +requires = ["uv_build>=0.7.14,<0.8.0"] build-backend = "uv_build" ``` diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 6b6b3e872..a87ce695a 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.7.13/install.sh | sh + $ curl -LsSf https://astral.sh/uv/0.7.14/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.7.13/install.ps1 | iex" + PS> powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/0.7.14/install.ps1 | iex" ``` !!! tip diff --git a/docs/guides/integration/aws-lambda.md b/docs/guides/integration/aws-lambda.md index 700011297..467088736 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.7.13 AS uv +FROM ghcr.io/astral-sh/uv:0.7.14 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.7.13 AS uv +FROM ghcr.io/astral-sh/uv:0.7.14 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 55f86fad6..01e66f775 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.7.13` +- `ghcr.io/astral-sh/uv:{major}.{minor}.{patch}`, e.g., `ghcr.io/astral-sh/uv:0.7.14` - `ghcr.io/astral-sh/uv:{major}.{minor}`, e.g., `ghcr.io/astral-sh/uv:0.7` (the latest patch version) @@ -75,7 +75,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.7.13-alpine`. +`ghcr.io/astral-sh/uv:{major}.{minor}-{base}`, e.g., `ghcr.io/astral-sh/uv:0.7.14-alpine`. For more details, see the [GitHub Container](https://github.com/astral-sh/uv/pkgs/container/uv) page. @@ -113,7 +113,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.7.13 /uv /uvx /bin/ +COPY --from=ghcr.io/astral-sh/uv:0.7.14 /uv /uvx /bin/ ``` !!! tip @@ -131,7 +131,7 @@ COPY --from=ghcr.io/astral-sh/uv:0.7.13 /uv /uvx /bin/ Or, with the installer: ```dockerfile -ADD https://astral.sh/uv/0.7.13/install.sh /uv-installer.sh +ADD https://astral.sh/uv/0.7.14/install.sh /uv-installer.sh ``` ### Installing a project @@ -557,5 +557,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.7.13`, or (even better) the specific image digest, + version tag, e.g., `ghcr.io/astral-sh/uv:0.7.14`, 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 49eb57a80..0bd1cc1b1 100644 --- a/docs/guides/integration/github.md +++ b/docs/guides/integration/github.md @@ -47,7 +47,7 @@ jobs: uses: astral-sh/setup-uv@v5 with: # Install a specific version of uv. - version: "0.7.13" + version: "0.7.14" ``` ## Setting up Python diff --git a/docs/guides/integration/pre-commit.md b/docs/guides/integration/pre-commit.md index 87c384da0..326832e7e 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.7.13 + rev: 0.7.14 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.7.13 + rev: 0.7.14 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.7.13 + rev: 0.7.14 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.7.13 + rev: 0.7.14 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.7.13 + rev: 0.7.14 hooks: # Compile requirements - id: pip-compile diff --git a/pyproject.toml b/pyproject.toml index d7bc4d805..86dd0a1e2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "uv" -version = "0.7.13" +version = "0.7.14" 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" From e7f596711114528d98af1d20c91ce05f8462c703 Mon Sep 17 00:00:00 2001 From: Aria Desires Date: Mon, 23 Jun 2025 13:30:49 -0400 Subject: [PATCH 045/349] Don't block docker logins on it being a pull-request (#14222) --- .github/workflows/build-docker.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 25e042275..9a2e5ba1d 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -90,7 +90,6 @@ jobs: # Login to DockerHub (when not pushing, it's to avoid rate-limiting) - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 - if: ${{ github.event.pull_request.head.repo.full_name == 'astral-sh/uv' }} with: username: ${{ needs.docker-plan.outputs.push == 'true' && 'astral' || 'astralshbot' }} password: ${{ needs.docker-plan.outputs.push == 'true' && secrets.DOCKERHUB_TOKEN_RW || secrets.DOCKERHUB_TOKEN_RO }} @@ -196,7 +195,6 @@ jobs: steps: # Login to DockerHub (when not pushing, it's to avoid rate-limiting) - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 - if: ${{ github.event.pull_request.head.repo.full_name == 'astral-sh/uv' }} with: username: ${{ needs.docker-plan.outputs.push == 'true' && 'astral' || 'astralshbot' }} password: ${{ needs.docker-plan.outputs.push == 'true' && secrets.DOCKERHUB_TOKEN_RW || secrets.DOCKERHUB_TOKEN_RO }} From d9351d52fc7acd355a9577355fea19df3df79626 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 23 Jun 2025 14:26:14 -0400 Subject: [PATCH 046/349] Remove wheel filename-from URL conversion (#14223) ## Summary This appears to be unused. --- Cargo.lock | 1 - crates/uv-distribution-filename/Cargo.toml | 1 - crates/uv-distribution-filename/src/wheel.rs | 24 -------------------- 3 files changed, 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 95e9308b4..6a717094c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5135,7 +5135,6 @@ dependencies = [ "serde", "smallvec", "thiserror 2.0.12", - "url", "uv-cache-key", "uv-normalize", "uv-pep440", diff --git a/crates/uv-distribution-filename/Cargo.toml b/crates/uv-distribution-filename/Cargo.toml index f30e79b3b..0dfdd623e 100644 --- a/crates/uv-distribution-filename/Cargo.toml +++ b/crates/uv-distribution-filename/Cargo.toml @@ -27,7 +27,6 @@ rkyv = { workspace = true, features = ["smallvec-1"] } serde = { workspace = true } smallvec = { workspace = true } thiserror = { workspace = true } -url = { workspace = true } [dev-dependencies] insta = { version = "1.40.0" } diff --git a/crates/uv-distribution-filename/src/wheel.rs b/crates/uv-distribution-filename/src/wheel.rs index d7dc7dfca..2ac0ef7d9 100644 --- a/crates/uv-distribution-filename/src/wheel.rs +++ b/crates/uv-distribution-filename/src/wheel.rs @@ -5,7 +5,6 @@ use std::str::FromStr; use memchr::memchr; use serde::{Deserialize, Deserializer, Serialize, Serializer, de}; use thiserror::Error; -use url::Url; use uv_cache_key::cache_digest; use uv_normalize::{InvalidNameError, PackageName}; @@ -300,29 +299,6 @@ impl WheelFilename { } } -impl TryFrom<&Url> for WheelFilename { - type Error = WheelFilenameError; - - fn try_from(url: &Url) -> Result { - let filename = url - .path_segments() - .ok_or_else(|| { - WheelFilenameError::InvalidWheelFileName( - url.to_string(), - "URL must have a path".to_string(), - ) - })? - .next_back() - .ok_or_else(|| { - WheelFilenameError::InvalidWheelFileName( - url.to_string(), - "URL must contain a filename".to_string(), - ) - })?; - Self::from_str(filename) - } -} - impl<'de> Deserialize<'de> for WheelFilename { fn deserialize(deserializer: D) -> Result where From aa2448ef836af43f4acd7ac7271c680174a76c76 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 23 Jun 2025 14:52:07 -0400 Subject: [PATCH 047/349] Strip query parameters when parsing source URL (#14224) ## Summary Closes https://github.com/astral-sh/uv/issues/14217. --- crates/uv-resolver/src/lock/mod.rs | 40 +++++++++++++++++++++++++----- crates/uv/tests/it/sync.rs | 29 ++++++++++++++++++++++ 2 files changed, 63 insertions(+), 6 deletions(-) diff --git a/crates/uv-resolver/src/lock/mod.rs b/crates/uv-resolver/src/lock/mod.rs index 5af4377b9..0b72014a8 100644 --- a/crates/uv-resolver/src/lock/mod.rs +++ b/crates/uv-resolver/src/lock/mod.rs @@ -2371,7 +2371,13 @@ impl Package { let sdist = match &self.id.source { Source::Path(path) => { // A direct path source can also be a wheel, so validate the extension. - let DistExtension::Source(ext) = DistExtension::from_path(path)? else { + let DistExtension::Source(ext) = DistExtension::from_path(path).map_err(|err| { + LockErrorKind::MissingExtension { + id: self.id.clone(), + err, + } + })? + else { return Ok(None); }; let install_path = absolute_path(workspace_root, path)?; @@ -2444,7 +2450,14 @@ impl Package { } Source::Direct(url, direct) => { // A direct URL source can also be a wheel, so validate the extension. - let DistExtension::Source(ext) = DistExtension::from_path(url.as_ref())? else { + let DistExtension::Source(ext) = + DistExtension::from_path(url.base_str()).map_err(|err| { + LockErrorKind::MissingExtension { + id: self.id.clone(), + err, + } + })? + else { return Ok(None); }; let location = url.to_url().map_err(LockErrorKind::InvalidUrl)?; @@ -2483,7 +2496,12 @@ impl Package { .ok_or_else(|| LockErrorKind::MissingFilename { id: self.id.clone(), })?; - let ext = SourceDistExtension::from_path(filename.as_ref())?; + let ext = SourceDistExtension::from_path(filename.as_ref()).map_err(|err| { + LockErrorKind::MissingExtension { + id: self.id.clone(), + err, + } + })?; let file = Box::new(uv_distribution_types::File { dist_info_metadata: false, filename: SmallString::from(filename), @@ -2535,7 +2553,12 @@ impl Package { .ok_or_else(|| LockErrorKind::MissingFilename { id: self.id.clone(), })?; - let ext = SourceDistExtension::from_path(filename.as_ref())?; + let ext = SourceDistExtension::from_path(filename.as_ref()).map_err(|err| { + LockErrorKind::MissingExtension { + id: self.id.clone(), + err, + } + })?; let file = Box::new(uv_distribution_types::File { dist_info_metadata: false, filename: SmallString::from(filename), @@ -5222,8 +5245,13 @@ enum LockErrorKind { ), /// An error that occurs when the extension can't be determined /// for a given wheel or source distribution. - #[error("Failed to parse file extension; expected one of: {0}")] - MissingExtension(#[from] ExtensionError), + #[error("Failed to parse file extension for `{id}`; expected one of: {err}", id = id.cyan())] + MissingExtension { + /// The filename that was expected to have an extension. + id: PackageId, + /// The list of valid extensions that were expected. + err: ExtensionError, + }, /// Failed to parse a Git source URL. #[error("Failed to parse Git URL")] InvalidGitSourceUrl( diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index e6fe831d3..9da2fb62a 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -9943,3 +9943,32 @@ fn sync_required_environment_hint() -> Result<()> { Ok(()) } + +#[test] +fn sync_url_with_query_parameters() -> Result<()> { + let context = TestContext::new("3.13").with_exclude_newer("2025-03-24T19:00:00Z"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str(r#" + [project] + name = "example" + version = "0.1.0" + requires-python = ">=3.13.2" + dependencies = ["source-distribution @ https://files.pythonhosted.org/packages/1f/e5/5b016c945d745f8b108e759d428341488a6aee8f51f07c6c4e33498bb91f/source_distribution-0.0.3.tar.gz?foo=bar"] + "# + )?; + + uv_snapshot!(context.filters(), context.sync(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + source-distribution==0.0.3 (from https://files.pythonhosted.org/packages/1f/e5/5b016c945d745f8b108e759d428341488a6aee8f51f07c6c4e33498bb91f/source_distribution-0.0.3.tar.gz?foo=bar) + "); + + Ok(()) +} From 19c58c7fbbe7f7fbf5a4c1db806ed55fcc3a9072 Mon Sep 17 00:00:00 2001 From: Ben Beasley Date: Tue, 24 Jun 2025 09:04:55 -0400 Subject: [PATCH 048/349] Update wiremock to 0.6.4 (#14238) ## Summary In e10881d49c8649a66f301af9052fd2bb17eb68d4, `uv` started using a fork of the `wiremock` crate, https://github.com/astral-sh/wiremock-rs, linking companion PR https://github.com/LukeMathWalker/wiremock-rs/pull/159. That PR was merged in `wiremock` 0.6.4, so this PR switches back to the crates.io version of `wiremock`, with a minimum version of 0.6.4. ## Test Plan ``` $ cargo run python install $ cargo test ```` --- Cargo.lock | 5 +++-- Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6a717094c..0680e0c28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6700,8 +6700,9 @@ checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" [[package]] name = "wiremock" -version = "0.6.3" -source = "git+https://github.com/astral-sh/wiremock-rs?rev=b79b69f62521df9f83a54e866432397562eae789#b79b69f62521df9f83a54e866432397562eae789" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b8b99d4cdbf36b239a9532e31fe4fb8acc38d1897c1761e161550a7dc78e6a" dependencies = [ "assert-json-diff", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 4269c6cfa..b2d9c0f8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -189,7 +189,7 @@ windows-core = { version = "0.59.0" } windows-registry = { version = "0.5.0" } windows-result = { version = "0.3.0" } windows-sys = { version = "0.59.0", features = ["Win32_Foundation", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Ioctl", "Win32_System_IO", "Win32_System_Registry"] } -wiremock = { git = "https://github.com/astral-sh/wiremock-rs", rev = "b79b69f62521df9f83a54e866432397562eae789" } +wiremock = { version = "0.6.4" } xz2 = { version = "0.1.7" } zip = { version = "2.2.3", default-features = false, features = ["deflate", "zstd", "bzip2", "lzma", "xz"] } From 093e9d6ff0eef171f59f5a00a14baef6f8ed545c Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 24 Jun 2025 11:15:18 -0400 Subject: [PATCH 049/349] Add `"python-eol"` feature to Sphinx tests (#14241) ## Summary Closes https://github.com/astral-sh/uv/issues/14228. --- crates/uv/tests/it/sync.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index 9da2fb62a..1ce892050 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -355,6 +355,7 @@ fn mixed_requires_python() -> Result<()> { /// Ensure that group requires-python solves an actual problem #[test] #[cfg(not(windows))] +#[cfg(feature = "python-eol")] fn group_requires_python_useful_defaults() -> Result<()> { let context = TestContext::new_with_versions(&["3.8", "3.9"]); @@ -499,6 +500,7 @@ fn group_requires_python_useful_defaults() -> Result<()> { /// Ensure that group requires-python solves an actual problem #[test] #[cfg(not(windows))] +#[cfg(feature = "python-eol")] fn group_requires_python_useful_non_defaults() -> Result<()> { let context = TestContext::new_with_versions(&["3.8", "3.9"]); From f20659e1cededf019777b5aa76ccea908c060abe Mon Sep 17 00:00:00 2001 From: konsti Date: Tue, 24 Jun 2025 17:53:10 +0200 Subject: [PATCH 050/349] Don't log GitHub fast path usage if it's cached (#14235) Don't log that we resolved a reference through the GitHub fast path if we didn't use GitHub at all but used the cached revision. This avoids stating that the fast path works when it's blocked due to unrelated reasons (e.g. rate limits). --- crates/uv-distribution/src/source/mod.rs | 6 ++++ crates/uv-git/src/resolver.rs | 36 +++++++++++++++--------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/crates/uv-distribution/src/source/mod.rs b/crates/uv-distribution/src/source/mod.rs index 90d77bd90..7be26fece 100644 --- a/crates/uv-distribution/src/source/mod.rs +++ b/crates/uv-distribution/src/source/mod.rs @@ -1860,6 +1860,12 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { } }; + // If the URL is already precise, return it. + if self.build_context.git().get_precise(git).is_some() { + debug!("Precise commit already known: {source}"); + return Ok(()); + } + // If this is GitHub URL, attempt to resolve to a precise commit using the GitHub API. if self .build_context diff --git a/crates/uv-git/src/resolver.rs b/crates/uv-git/src/resolver.rs index d404390f3..0881910b2 100644 --- a/crates/uv-git/src/resolver.rs +++ b/crates/uv-git/src/resolver.rs @@ -46,6 +46,21 @@ impl GitResolver { self.0.get(reference) } + pub fn get_precise(&self, url: &GitUrl) -> Option { + // If the URL is already precise, return it. + if let Some(precise) = url.precise() { + return Some(precise); + } + + // If we know the precise commit already, return it. + let reference = RepositoryReference::from(url); + if let Some(precise) = self.get(&reference) { + return Some(*precise); + } + + None + } + /// Resolve a Git URL to a specific commit without performing any Git operations. /// /// Returns a [`GitOid`] if the URL has already been resolved (i.e., is available in the cache), @@ -59,18 +74,11 @@ impl GitResolver { return Ok(None); } - let reference = RepositoryReference::from(url); - - // If the URL is already precise, return it. - if let Some(precise) = url.precise() { + // If the URL is already precise or we know the precise commit, return it. + if let Some(precise) = self.get_precise(url) { return Ok(Some(precise)); } - // If we know the precise commit already, return it. - if let Some(precise) = self.get(&reference) { - return Ok(Some(*precise)); - } - // If the URL is a GitHub URL, attempt to resolve it via the GitHub API. let Some(GitHubRepository { owner, repo }) = GitHubRepository::parse(url.repository()) else { @@ -80,10 +88,10 @@ impl GitResolver { // Determine the Git reference. let rev = url.reference().as_rev(); - let url = format!("https://api.github.com/repos/{owner}/{repo}/commits/{rev}"); + let github_api_url = format!("https://api.github.com/repos/{owner}/{repo}/commits/{rev}"); - debug!("Querying GitHub for commit at: {url}"); - let mut request = client.get(&url); + debug!("Querying GitHub for commit at: {github_api_url}"); + let mut request = client.get(&github_api_url); request = request.header("Accept", "application/vnd.github.3.sha"); request = request.header( "User-Agent", @@ -95,7 +103,7 @@ impl GitResolver { // Returns a 404 if the repository does not exist, and a 422 if GitHub is unable to // resolve the requested rev. debug!( - "GitHub API request failed for: {url} ({})", + "GitHub API request failed for: {github_api_url} ({})", response.status() ); return Ok(None); @@ -108,7 +116,7 @@ impl GitResolver { // Insert the resolved URL into the in-memory cache. This ensures that subsequent fetches // resolve to the same precise commit. - self.insert(reference, precise); + self.insert(RepositoryReference::from(url), precise); Ok(Some(precise)) } From 606633d35f4167bc1904de5186db196a343432bb Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 24 Jun 2025 11:54:12 -0400 Subject: [PATCH 051/349] Remove wheel filename benchmark (#14240) ## Summary This flakes often and we don't really need it to be monitored continuously. We can always revive it from Git later. Closes https://github.com/astral-sh/uv/issues/13952. --- Cargo.lock | 1 - crates/uv-bench/Cargo.toml | 6 - .../uv-bench/benches/distribution_filename.rs | 168 ------------------ 3 files changed, 175 deletions(-) delete mode 100644 crates/uv-bench/benches/distribution_filename.rs diff --git a/Cargo.lock b/Cargo.lock index 0680e0c28..ce0325b9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4719,7 +4719,6 @@ dependencies = [ "uv-configuration", "uv-dispatch", "uv-distribution", - "uv-distribution-filename", "uv-distribution-types", "uv-extract", "uv-install-wheel", diff --git a/crates/uv-bench/Cargo.toml b/crates/uv-bench/Cargo.toml index 65ce78731..5d55fafd7 100644 --- a/crates/uv-bench/Cargo.toml +++ b/crates/uv-bench/Cargo.toml @@ -18,11 +18,6 @@ workspace = true doctest = false bench = false -[[bench]] -name = "distribution-filename" -path = "benches/distribution_filename.rs" -harness = false - [[bench]] name = "uv" path = "benches/uv.rs" @@ -34,7 +29,6 @@ uv-client = { workspace = true } uv-configuration = { workspace = true } uv-dispatch = { workspace = true } uv-distribution = { workspace = true } -uv-distribution-filename = { workspace = true } uv-distribution-types = { workspace = true } uv-extract = { workspace = true, optional = true } uv-install-wheel = { workspace = true } diff --git a/crates/uv-bench/benches/distribution_filename.rs b/crates/uv-bench/benches/distribution_filename.rs deleted file mode 100644 index 99d72cf05..000000000 --- a/crates/uv-bench/benches/distribution_filename.rs +++ /dev/null @@ -1,168 +0,0 @@ -use std::str::FromStr; - -use uv_bench::criterion::{ - BenchmarkId, Criterion, Throughput, criterion_group, criterion_main, measurement::WallTime, -}; -use uv_distribution_filename::WheelFilename; -use uv_platform_tags::{AbiTag, LanguageTag, PlatformTag, Tags}; - -/// A set of platform tags extracted from burntsushi's Archlinux workstation. -/// We could just re-create these via `Tags::from_env`, but those might differ -/// depending on the platform. This way, we always use the same data. It also -/// lets us assert tag compatibility regardless of where the benchmarks run. -const PLATFORM_TAGS: &[(&str, &str, &str)] = include!("../inputs/platform_tags.rs"); - -/// A set of wheel names used in the benchmarks below. We pick short and long -/// names, as well as compatible and not-compatibles (with `PLATFORM_TAGS`) -/// names. -/// -/// The tuple is (name, filename, compatible) where `name` is a descriptive -/// name for humans used in the benchmark definition. And `filename` is the -/// actual wheel filename we want to benchmark operation on. And `compatible` -/// indicates whether the tags in the wheel filename are expected to be -/// compatible with the tags in `PLATFORM_TAGS`. -const WHEEL_NAMES: &[(&str, &str, bool)] = &[ - // This tests a case with a very short name that *is* compatible with - // PLATFORM_TAGS. It only uses one tag for each component (one Python - // version, one ABI and one platform). - ( - "flyte-short-compatible", - "ipython-2.1.0-py3-none-any.whl", - true, - ), - // This tests a case with a long name that is *not* compatible. That - // is, all platform tags need to be checked against the tags in the - // wheel filename. This is essentially the worst possible practical - // case. - ( - "flyte-long-incompatible", - "protobuf-3.5.2.post1-cp36-cp36m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", - false, - ), - // This tests a case with a long name that *is* compatible. We - // expect this to be (on average) quicker because the compatibility - // check stops as soon as a positive match is found. (Where as the - // incompatible case needs to check all tags.) - ( - "flyte-long-compatible", - "coverage-6.6.0b1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", - true, - ), -]; - -/// A list of names that are candidates for wheel filenames but will ultimately -/// fail to parse. -const INVALID_WHEEL_NAMES: &[(&str, &str)] = &[ - ("flyte-short-extension", "mock-5.1.0.tar.gz"), - ( - "flyte-long-extension", - "Pillow-5.4.0.dev0-py3.7-macosx-10.13-x86_64.egg", - ), -]; - -/// Benchmarks the construction of platform tags. -/// -/// This only happens ~once per program startup. Originally, construction was -/// trivial. But to speed up `WheelFilename::is_compatible`, we added some -/// extra processing. We thus expect construction to become slower, but we -/// write a benchmark to ensure it is still "reasonable." -fn benchmark_build_platform_tags(c: &mut Criterion) { - let tags: Vec<(LanguageTag, AbiTag, PlatformTag)> = PLATFORM_TAGS - .iter() - .map(|&(py, abi, plat)| { - ( - LanguageTag::from_str(py).unwrap(), - AbiTag::from_str(abi).unwrap(), - PlatformTag::from_str(plat).unwrap(), - ) - }) - .collect(); - - let mut group = c.benchmark_group("build_platform_tags"); - group.bench_function(BenchmarkId::from_parameter("burntsushi-archlinux"), |b| { - b.iter(|| std::hint::black_box(Tags::new(tags.clone()))); - }); - group.finish(); -} - -/// Benchmarks `WheelFilename::from_str`. This has been observed to take some -/// non-trivial time in profiling (although, at time of writing, not as much -/// as tag compatibility). In the process of optimizing tag compatibility, -/// we tweaked wheel filename parsing. This benchmark was therefore added to -/// ensure we didn't regress here. -fn benchmark_wheelname_parsing(c: &mut Criterion) { - let mut group = c.benchmark_group("wheelname_parsing"); - for (name, filename, _) in WHEEL_NAMES.iter().copied() { - let len = u64::try_from(filename.len()).expect("length fits in u64"); - group.throughput(Throughput::Bytes(len)); - group.bench_function(BenchmarkId::from_parameter(name), |b| { - b.iter(|| { - filename - .parse::() - .expect("valid wheel filename"); - }); - }); - } - group.finish(); -} - -/// Benchmarks `WheelFilename::from_str` when it fails. This routine is called -/// on every filename in a package's metadata. A non-trivial portion of which -/// are not wheel filenames. Ensuring that the error path is fast is thus -/// probably a good idea. -fn benchmark_wheelname_parsing_failure(c: &mut Criterion) { - let mut group = c.benchmark_group("wheelname_parsing_failure"); - for (name, filename) in INVALID_WHEEL_NAMES.iter().copied() { - let len = u64::try_from(filename.len()).expect("length fits in u64"); - group.throughput(Throughput::Bytes(len)); - group.bench_function(BenchmarkId::from_parameter(name), |b| { - b.iter(|| { - filename - .parse::() - .expect_err("invalid wheel filename"); - }); - }); - } - group.finish(); -} - -/// Benchmarks the `WheelFilename::is_compatible` routine. This was revealed -/// to be the #1 bottleneck in the resolver. The main issue was that the -/// set of platform tags (generated once) is quite large, and the original -/// implementation did an exhaustive search over each of them for each tag in -/// the wheel filename. -fn benchmark_wheelname_tag_compatibility(c: &mut Criterion) { - let tags: Vec<(LanguageTag, AbiTag, PlatformTag)> = PLATFORM_TAGS - .iter() - .map(|&(py, abi, plat)| { - ( - LanguageTag::from_str(py).unwrap(), - AbiTag::from_str(abi).unwrap(), - PlatformTag::from_str(plat).unwrap(), - ) - }) - .collect(); - let tags = Tags::new(tags); - - let mut group = c.benchmark_group("wheelname_tag_compatibility"); - for (name, filename, expected) in WHEEL_NAMES.iter().copied() { - let wheelname: WheelFilename = filename.parse().expect("valid wheel filename"); - let len = u64::try_from(filename.len()).expect("length fits in u64"); - group.throughput(Throughput::Bytes(len)); - group.bench_function(BenchmarkId::from_parameter(name), |b| { - b.iter(|| { - assert_eq!(expected, wheelname.is_compatible(&tags)); - }); - }); - } - group.finish(); -} - -criterion_group!( - uv_distribution_filename, - benchmark_build_platform_tags, - benchmark_wheelname_parsing, - benchmark_wheelname_parsing_failure, - benchmark_wheelname_tag_compatibility, -); -criterion_main!(uv_distribution_filename); From 61265b0c1401bd483ea3c04b9304ec36d98c29cd Mon Sep 17 00:00:00 2001 From: dmitry-bychkov Date: Tue, 24 Jun 2025 18:56:36 +0300 Subject: [PATCH 052/349] Add a link to PyPI FAQ to clarify what per-project token is. (#14242) ## Summary This change adds a link to PyPI FAQ about API tokens on the package publishing guide page. To me it wasn't clear what are meant in this section of the docs and it required a little bit of research. Adding explicit link might help beginners. Co-authored-by: Dmitry Bychkov --- docs/guides/package.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/package.md b/docs/guides/package.md index 0914d5750..ce5bae7f9 100644 --- a/docs/guides/package.md +++ b/docs/guides/package.md @@ -31,8 +31,8 @@ the effect of declaring a build system in the This setting makes PyPI reject your uploaded package from publishing. It does not affect security or privacy settings on alternative registries. - We also recommend only generating per-project tokens: Without a PyPI token matching the project, - it can't be accidentally published. + We also recommend only generating [per-project PyPI API tokens](https://pypi.org/help/#apitoken): + Without a PyPI token matching the project, it can't be accidentally published. ## Building your package From fe11ceedfa716bdd1f93fd1886318d4cf798e311 Mon Sep 17 00:00:00 2001 From: Christopher Tee Date: Tue, 24 Jun 2025 15:11:41 -0400 Subject: [PATCH 053/349] Skip GitHub fast path when rate-limited (#13033) --- crates/uv-git/src/git.rs | 17 ++++++- crates/uv-git/src/lib.rs | 1 + crates/uv-git/src/rate_limit.rs | 37 ++++++++++++++ crates/uv-git/src/resolver.rs | 24 +++++++-- crates/uv-static/src/env_vars.rs | 4 ++ crates/uv/tests/it/edit.rs | 82 +++++++++++++++++++++++++++++++ crates/uv/tests/it/pip_install.rs | 58 ++++++++++++++++++++++ 7 files changed, 219 insertions(+), 4 deletions(-) create mode 100644 crates/uv-git/src/rate_limit.rs diff --git a/crates/uv-git/src/git.rs b/crates/uv-git/src/git.rs index 298c205ba..4ee4c2670 100644 --- a/crates/uv-git/src/git.rs +++ b/crates/uv-git/src/git.rs @@ -20,6 +20,8 @@ use uv_redacted::DisplaySafeUrl; use uv_static::EnvVars; use uv_version::version; +use crate::rate_limit::{GITHUB_RATE_LIMIT_STATUS, is_github_rate_limited}; + /// A file indicates that if present, `git reset` has been done and a repo /// checkout is ready to go. See [`GitCheckout::reset`] for why we need this. const CHECKOUT_READY_LOCK: &str = ".ok"; @@ -787,7 +789,15 @@ fn github_fast_path( } }; - let url = format!("https://api.github.com/repos/{owner}/{repo}/commits/{github_branch_name}"); + // Check if we're rate-limited by GitHub before determining the FastPathRev + if GITHUB_RATE_LIMIT_STATUS.is_active() { + debug!("Skipping GitHub fast path attempt for: {url} (rate-limited)"); + return Ok(FastPathRev::Indeterminate); + } + + let base_url = std::env::var(EnvVars::UV_GITHUB_FAST_PATH_URL) + .unwrap_or("https://api.github.com/repos".to_owned()); + let url = format!("{base_url}/{owner}/{repo}/commits/{github_branch_name}"); let runtime = tokio::runtime::Builder::new_current_thread() .enable_all() @@ -807,6 +817,11 @@ fn github_fast_path( let response = request.send().await?; + if is_github_rate_limited(&response) { + // Mark that we are being rate-limited by GitHub + GITHUB_RATE_LIMIT_STATUS.activate(); + } + // GitHub returns a 404 if the repository does not exist, and a 422 if it exists but GitHub // is unable to resolve the requested revision. response.error_for_status_ref()?; diff --git a/crates/uv-git/src/lib.rs b/crates/uv-git/src/lib.rs index ef23e58c2..716eb7538 100644 --- a/crates/uv-git/src/lib.rs +++ b/crates/uv-git/src/lib.rs @@ -7,5 +7,6 @@ pub use crate::source::{Fetch, GitSource, Reporter}; mod credentials; mod git; +mod rate_limit; mod resolver; mod source; diff --git a/crates/uv-git/src/rate_limit.rs b/crates/uv-git/src/rate_limit.rs new file mode 100644 index 000000000..4d277e652 --- /dev/null +++ b/crates/uv-git/src/rate_limit.rs @@ -0,0 +1,37 @@ +use reqwest::{Response, StatusCode}; +use std::sync::atomic::{AtomicBool, Ordering}; + +/// A global state on whether we are being rate-limited by GitHub's REST API. +/// If we are, avoid "fast-path" attempts. +pub(crate) static GITHUB_RATE_LIMIT_STATUS: GitHubRateLimitStatus = GitHubRateLimitStatus::new(); + +/// GitHub REST API rate limit status tracker. +/// +/// ## Assumptions +/// +/// The rate limit timeout duration is much longer than the runtime of a `uv` command. +/// And so we do not need to invalidate this state based on `x-ratelimit-reset`. +#[derive(Debug)] +pub(crate) struct GitHubRateLimitStatus(AtomicBool); + +impl GitHubRateLimitStatus { + const fn new() -> Self { + Self(AtomicBool::new(false)) + } + + pub(crate) fn activate(&self) { + self.0.store(true, Ordering::Relaxed); + } + + pub(crate) fn is_active(&self) -> bool { + self.0.load(Ordering::Relaxed) + } +} + +/// Determine if GitHub is applying rate-limiting based on the response +pub(crate) fn is_github_rate_limited(response: &Response) -> bool { + // HTTP 403 and 429 are possible status codes in the event of a primary or secondary rate limit. + // Source: https://docs.github.com/en/rest/using-the-rest-api/troubleshooting-the-rest-api?apiVersion=2022-11-28#rate-limit-errors + let status_code = response.status(); + status_code == StatusCode::FORBIDDEN || status_code == StatusCode::TOO_MANY_REQUESTS +} diff --git a/crates/uv-git/src/resolver.rs b/crates/uv-git/src/resolver.rs index 0881910b2..3c12fc589 100644 --- a/crates/uv-git/src/resolver.rs +++ b/crates/uv-git/src/resolver.rs @@ -15,7 +15,10 @@ use uv_git_types::{GitHubRepository, GitOid, GitReference, GitUrl}; use uv_static::EnvVars; use uv_version::version; -use crate::{Fetch, GitSource, Reporter}; +use crate::{ + Fetch, GitSource, Reporter, + rate_limit::{GITHUB_RATE_LIMIT_STATUS, is_github_rate_limited}, +}; #[derive(Debug, thiserror::Error)] pub enum GitResolverError { @@ -85,10 +88,18 @@ impl GitResolver { return Ok(None); }; + // Check if we're rate-limited by GitHub, before determining the Git reference + if GITHUB_RATE_LIMIT_STATUS.is_active() { + debug!("Rate-limited by GitHub. Skipping GitHub fast path attempt for: {url}"); + return Ok(None); + } + // Determine the Git reference. let rev = url.reference().as_rev(); - let github_api_url = format!("https://api.github.com/repos/{owner}/{repo}/commits/{rev}"); + let github_api_base_url = std::env::var(EnvVars::UV_GITHUB_FAST_PATH_URL) + .unwrap_or("https://api.github.com/repos".to_owned()); + let github_api_url = format!("{github_api_base_url}/{owner}/{repo}/commits/{rev}"); debug!("Querying GitHub for commit at: {github_api_url}"); let mut request = client.get(&github_api_url); @@ -99,13 +110,20 @@ impl GitResolver { ); let response = request.send().await?; - if !response.status().is_success() { + let status = response.status(); + if !status.is_success() { // Returns a 404 if the repository does not exist, and a 422 if GitHub is unable to // resolve the requested rev. debug!( "GitHub API request failed for: {github_api_url} ({})", response.status() ); + + if is_github_rate_limited(&response) { + // Mark that we are being rate-limited by GitHub + GITHUB_RATE_LIMIT_STATUS.activate(); + } + return Ok(None); } diff --git a/crates/uv-static/src/env_vars.rs b/crates/uv-static/src/env_vars.rs index 8193b6aec..4a44579d7 100644 --- a/crates/uv-static/src/env_vars.rs +++ b/crates/uv-static/src/env_vars.rs @@ -667,6 +667,10 @@ impl EnvVars { #[attr_hidden] pub const UV_TEST_INDEX_URL: &'static str = "UV_TEST_INDEX_URL"; + /// Used to set the GitHub fast-path url for tests. + #[attr_hidden] + pub const UV_GITHUB_FAST_PATH_URL: &'static str = "UV_GITHUB_FAST_PATH_URL"; + /// Hide progress messages with non-deterministic order in tests. #[attr_hidden] pub const UV_TEST_NO_CLI_PROGRESS: &'static str = "UV_TEST_NO_CLI_PROGRESS"; diff --git a/crates/uv/tests/it/edit.rs b/crates/uv/tests/it/edit.rs index d117b1e8c..6bdaec17b 100644 --- a/crates/uv/tests/it/edit.rs +++ b/crates/uv/tests/it/edit.rs @@ -494,6 +494,88 @@ fn add_git_private_raw() -> Result<()> { Ok(()) } +#[tokio::test] +#[cfg(feature = "git")] +async fn add_git_private_rate_limited_by_github_rest_api_403_response() -> Result<()> { + let context = TestContext::new("3.12"); + let token = decode_token(READ_ONLY_GITHUB_TOKEN); + + let server = MockServer::start().await; + Mock::given(method("GET")) + .respond_with(ResponseTemplate::new(403)) + .expect(1) + .mount(&server) + .await; + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + "#})?; + + uv_snapshot!(context.filters(), context + .add() + .arg(format!("uv-private-pypackage @ git+https://{token}@github.com/astral-test/uv-private-pypackage")) + .env("UV_GITHUB_FAST_PATH_URL", server.uri()), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + uv-private-pypackage==0.1.0 (from git+https://github.com/astral-test/uv-private-pypackage@d780faf0ac91257d4d5a4f0c5a0e4509608c0071) + "); + + Ok(()) +} + +#[tokio::test] +#[cfg(feature = "git")] +async fn add_git_private_rate_limited_by_github_rest_api_429_response() -> Result<()> { + use uv_client::DEFAULT_RETRIES; + + let context = TestContext::new("3.12"); + let token = decode_token(READ_ONLY_GITHUB_TOKEN); + + let server = MockServer::start().await; + Mock::given(method("GET")) + .respond_with(ResponseTemplate::new(429)) + .expect(1 + u64::from(DEFAULT_RETRIES)) // Middleware retries on 429 by default + .mount(&server) + .await; + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + "#})?; + + uv_snapshot!(context.filters(), context + .add() + .arg(format!("uv-private-pypackage @ git+https://{token}@github.com/astral-test/uv-private-pypackage")) + .env("UV_GITHUB_FAST_PATH_URL", server.uri()), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + uv-private-pypackage==0.1.0 (from git+https://github.com/astral-test/uv-private-pypackage@d780faf0ac91257d4d5a4f0c5a0e4509608c0071) + "); + + Ok(()) +} + #[test] #[cfg(feature = "git")] fn add_git_error() -> Result<()> { diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index e0876b23c..604b8db15 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -2066,6 +2066,64 @@ fn install_git_public_https_missing_branch_or_tag() { "###); } +#[tokio::test] +#[cfg(feature = "git")] +async fn install_git_public_rate_limited_by_github_rest_api_403_response() { + let context = TestContext::new("3.12"); + + let server = MockServer::start().await; + Mock::given(method("GET")) + .respond_with(ResponseTemplate::new(403)) + .expect(1) + .mount(&server) + .await; + + uv_snapshot!(context.filters(), context + .pip_install() + .arg("uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage") + .env("UV_GITHUB_FAST_PATH_URL", server.uri()), @r" + 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) + "); +} + +#[tokio::test] +#[cfg(feature = "git")] +async fn install_git_public_rate_limited_by_github_rest_api_429_response() { + use uv_client::DEFAULT_RETRIES; + + let context = TestContext::new("3.12"); + + let server = MockServer::start().await; + Mock::given(method("GET")) + .respond_with(ResponseTemplate::new(429)) + .expect(1 + u64::from(DEFAULT_RETRIES)) // Middleware retries on 429 by default + .mount(&server) + .await; + + uv_snapshot!(context.filters(), context + .pip_install() + .arg("uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage") + .env("UV_GITHUB_FAST_PATH_URL", server.uri()), @r" + 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) + "); +} + /// Install a package from a public GitHub repository at a ref that does not exist #[test] #[cfg(feature = "git")] From 9fba7a4768a5931df97af158cc6e81cf3d06ed3a Mon Sep 17 00:00:00 2001 From: Christopher Tee Date: Tue, 24 Jun 2025 15:30:26 -0400 Subject: [PATCH 054/349] Consistently use `Ordering::Relaxed` for standalone atomic use cases (#14190) --- crates/uv-configuration/src/threading.rs | 2 +- crates/uv-warnings/src/lib.rs | 8 ++++---- crates/uv/src/lib.rs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/uv-configuration/src/threading.rs b/crates/uv-configuration/src/threading.rs index 58b6190a6..2f70b5d81 100644 --- a/crates/uv-configuration/src/threading.rs +++ b/crates/uv-configuration/src/threading.rs @@ -62,7 +62,7 @@ pub static RAYON_PARALLELISM: AtomicUsize = AtomicUsize::new(0); /// `LazyLock::force(&RAYON_INITIALIZE)`. pub static RAYON_INITIALIZE: LazyLock<()> = LazyLock::new(|| { rayon::ThreadPoolBuilder::new() - .num_threads(RAYON_PARALLELISM.load(Ordering::SeqCst)) + .num_threads(RAYON_PARALLELISM.load(Ordering::Relaxed)) .stack_size(min_stack_size()) .build_global() .expect("failed to initialize global rayon pool"); diff --git a/crates/uv-warnings/src/lib.rs b/crates/uv-warnings/src/lib.rs index 5f2287cac..2b664be8d 100644 --- a/crates/uv-warnings/src/lib.rs +++ b/crates/uv-warnings/src/lib.rs @@ -13,12 +13,12 @@ pub static ENABLED: AtomicBool = AtomicBool::new(false); /// Enable user-facing warnings. pub fn enable() { - ENABLED.store(true, std::sync::atomic::Ordering::SeqCst); + ENABLED.store(true, std::sync::atomic::Ordering::Relaxed); } /// Disable user-facing warnings. pub fn disable() { - ENABLED.store(false, std::sync::atomic::Ordering::SeqCst); + ENABLED.store(false, std::sync::atomic::Ordering::Relaxed); } /// Warn a user, if warnings are enabled. @@ -28,7 +28,7 @@ macro_rules! warn_user { use $crate::anstream::eprintln; use $crate::owo_colors::OwoColorize; - if $crate::ENABLED.load(std::sync::atomic::Ordering::SeqCst) { + if $crate::ENABLED.load(std::sync::atomic::Ordering::Relaxed) { let message = format!("{}", format_args!($($arg)*)); let formatted = message.bold(); eprintln!("{}{} {formatted}", "warning".yellow().bold(), ":".bold()); @@ -46,7 +46,7 @@ macro_rules! warn_user_once { use $crate::anstream::eprintln; use $crate::owo_colors::OwoColorize; - if $crate::ENABLED.load(std::sync::atomic::Ordering::SeqCst) { + if $crate::ENABLED.load(std::sync::atomic::Ordering::Relaxed) { if let Ok(mut states) = $crate::WARNINGS.lock() { let message = format!("{}", format_args!($($arg)*)); if states.insert(message.clone()) { diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index fd2e28fae..ab4aee9e9 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -400,7 +400,7 @@ async fn run(mut cli: Cli) -> Result { }))?; // Don't initialize the rayon threadpool yet, this is too costly when we're doing a noop sync. - uv_configuration::RAYON_PARALLELISM.store(globals.concurrency.installs, Ordering::SeqCst); + uv_configuration::RAYON_PARALLELISM.store(globals.concurrency.installs, Ordering::Relaxed); debug!("uv {}", uv_cli::version::uv_self_version()); From ac788d7cdeaf8b6de9d06971d19ae95fc74e0b5d Mon Sep 17 00:00:00 2001 From: ya7010 <47286750+ya7010@users.noreply.github.com> Date: Wed, 25 Jun 2025 04:43:31 +0900 Subject: [PATCH 055/349] Update schemars 1.0.0 (#13693) ## Summary Update [schemars 0.9.0](https://github.com/GREsau/schemars/releases/tag/v0.9.0) There are differences in the generated JSON Schema and I will [contact the author](https://github.com/GREsau/schemars/issues/407). ## Test Plan --------- Co-authored-by: konstin --- Cargo.lock | 9 +- Cargo.toml | 2 +- .../uv-configuration/src/name_specifiers.rs | 30 +- .../uv-configuration/src/required_version.rs | 21 +- crates/uv-configuration/src/trusted_host.rs | 21 +- crates/uv-dev/src/generate_json_schema.rs | 7 +- crates/uv-distribution-types/src/index_url.rs | 19 +- crates/uv-distribution-types/src/pip_index.rs | 10 +- .../src/status_code_strategy.rs | 20 +- crates/uv-fs/src/path.rs | 6 +- crates/uv-pep508/Cargo.toml | 2 +- crates/uv-pep508/src/lib.rs | 22 +- crates/uv-pep508/src/marker/tree.rs | 24 +- crates/uv-pypi-types/src/conflicts.rs | 10 +- crates/uv-pypi-types/src/identifier.rs | 26 +- crates/uv-python/src/python_version.rs | 27 +- crates/uv-resolver/src/exclude_newer.rs | 28 +- crates/uv-settings/src/settings.rs | 1 + crates/uv-small-str/src/lib.rs | 10 +- crates/uv-workspace/src/pyproject.rs | 9 +- uv.schema.json | 1033 +++++++---------- 21 files changed, 524 insertions(+), 813 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ce0325b9e..2e1957e9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3405,11 +3405,12 @@ dependencies = [ [[package]] name = "schemars" -version = "0.8.22" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +checksum = "febc07c7e70b5db4f023485653c754d76e1bbe8d9dbfa20193ce13da9f9633f4" dependencies = [ "dyn-clone", + "ref-cast", "schemars_derive", "serde", "serde_json", @@ -3418,9 +3419,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.22" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +checksum = "c1eeedaab7b1e1d09b5b4661121f4d27f9e7487089b0117833ccd7a882ee1ecc" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index b2d9c0f8a..5f3cdf40c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -151,7 +151,7 @@ rust-netrc = { version = "0.1.2" } rustc-hash = { version = "2.0.0" } rustix = { version = "1.0.0", default-features = false, features = ["fs", "std"] } same-file = { version = "1.0.6" } -schemars = { version = "0.8.21", features = ["url"] } +schemars = { version = "1.0.0", features = ["url2"] } seahash = { version = "4.1.0" } self-replace = { version = "1.5.0" } serde = { version = "1.0.210", features = ["derive", "rc"] } diff --git a/crates/uv-configuration/src/name_specifiers.rs b/crates/uv-configuration/src/name_specifiers.rs index 5ff209948..1fd25ea0b 100644 --- a/crates/uv-configuration/src/name_specifiers.rs +++ b/crates/uv-configuration/src/name_specifiers.rs @@ -1,4 +1,4 @@ -use std::str::FromStr; +use std::{borrow::Cow, str::FromStr}; use uv_pep508::PackageName; @@ -63,28 +63,16 @@ impl<'de> serde::Deserialize<'de> for PackageNameSpecifier { #[cfg(feature = "schemars")] impl schemars::JsonSchema for PackageNameSpecifier { - fn schema_name() -> String { - "PackageNameSpecifier".to_string() + fn schema_name() -> Cow<'static, str> { + Cow::Borrowed("PackageNameSpecifier") } - fn json_schema(_gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema { - schemars::schema::SchemaObject { - instance_type: Some(schemars::schema::InstanceType::String.into()), - string: Some(Box::new(schemars::schema::StringValidation { - // See: https://packaging.python.org/en/latest/specifications/name-normalization/#name-format - pattern: Some( - r"^(:none:|:all:|([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9]))$" - .to_string(), - ), - ..schemars::schema::StringValidation::default() - })), - metadata: Some(Box::new(schemars::schema::Metadata { - description: Some("The name of a package, or `:all:` or `:none:` to select or omit all packages, respectively.".to_string()), - ..schemars::schema::Metadata::default() - })), - ..schemars::schema::SchemaObject::default() - } - .into() + fn json_schema(_gen: &mut schemars::generate::SchemaGenerator) -> schemars::Schema { + schemars::json_schema!({ + "type": "string", + "pattern": r"^(:none:|:all:|([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9]))$", + "description": "The name of a package, or `:all:` or `:none:` to select or omit all packages, respectively.", + }) } } diff --git a/crates/uv-configuration/src/required_version.rs b/crates/uv-configuration/src/required_version.rs index a0138a46e..7135dfdab 100644 --- a/crates/uv-configuration/src/required_version.rs +++ b/crates/uv-configuration/src/required_version.rs @@ -1,5 +1,5 @@ -use std::fmt::Formatter; use std::str::FromStr; +use std::{borrow::Cow, fmt::Formatter}; use uv_pep440::{Version, VersionSpecifier, VersionSpecifiers, VersionSpecifiersParseError}; @@ -36,20 +36,15 @@ impl FromStr for RequiredVersion { #[cfg(feature = "schemars")] impl schemars::JsonSchema for RequiredVersion { - fn schema_name() -> String { - String::from("RequiredVersion") + fn schema_name() -> Cow<'static, str> { + Cow::Borrowed("RequiredVersion") } - fn json_schema(_gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema { - schemars::schema::SchemaObject { - instance_type: Some(schemars::schema::InstanceType::String.into()), - metadata: Some(Box::new(schemars::schema::Metadata { - description: Some("A version specifier, e.g. `>=0.5.0` or `==0.5.0`.".to_string()), - ..schemars::schema::Metadata::default() - })), - ..schemars::schema::SchemaObject::default() - } - .into() + fn json_schema(_generator: &mut schemars::generate::SchemaGenerator) -> schemars::Schema { + schemars::json_schema!({ + "type": "string", + "description": "A version specifier, e.g. `>=0.5.0` or `==0.5.0`." + }) } } diff --git a/crates/uv-configuration/src/trusted_host.rs b/crates/uv-configuration/src/trusted_host.rs index 64fb14169..9f3efb6fc 100644 --- a/crates/uv-configuration/src/trusted_host.rs +++ b/crates/uv-configuration/src/trusted_host.rs @@ -1,5 +1,5 @@ use serde::{Deserialize, Deserializer}; -use std::str::FromStr; +use std::{borrow::Cow, str::FromStr}; use url::Url; /// A host specification (wildcard, or host, with optional scheme and/or port) for which @@ -143,20 +143,15 @@ impl std::fmt::Display for TrustedHost { #[cfg(feature = "schemars")] impl schemars::JsonSchema for TrustedHost { - fn schema_name() -> String { - "TrustedHost".to_string() + fn schema_name() -> Cow<'static, str> { + Cow::Borrowed("TrustedHost") } - fn json_schema(_gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema { - schemars::schema::SchemaObject { - instance_type: Some(schemars::schema::InstanceType::String.into()), - metadata: Some(Box::new(schemars::schema::Metadata { - description: Some("A host or host-port pair.".to_string()), - ..schemars::schema::Metadata::default() - })), - ..schemars::schema::SchemaObject::default() - } - .into() + fn json_schema(_generator: &mut schemars::generate::SchemaGenerator) -> schemars::Schema { + schemars::json_schema!({ + "type": "string", + "description": "A host or host-port pair." + }) } } diff --git a/crates/uv-dev/src/generate_json_schema.rs b/crates/uv-dev/src/generate_json_schema.rs index 75465f429..8a4ff47d5 100644 --- a/crates/uv-dev/src/generate_json_schema.rs +++ b/crates/uv-dev/src/generate_json_schema.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use anstream::println; use anyhow::{Result, bail}; use pretty_assertions::StrComparison; -use schemars::{JsonSchema, schema_for}; +use schemars::JsonSchema; use serde::Deserialize; use uv_settings::Options as SettingsOptions; @@ -91,7 +91,10 @@ const REPLACEMENTS: &[(&str, &str)] = &[ /// Generate the JSON schema for the combined options as a string. fn generate() -> String { - let schema = schema_for!(CombinedOptions); + let settings = schemars::generate::SchemaSettings::draft07(); + let generator = schemars::SchemaGenerator::new(settings); + let schema = generator.into_root_schema_for::(); + let mut output = serde_json::to_string_pretty(&schema).unwrap(); for (value, replacement) in REPLACEMENTS { diff --git a/crates/uv-distribution-types/src/index_url.rs b/crates/uv-distribution-types/src/index_url.rs index a523b4811..9d07c9cbe 100644 --- a/crates/uv-distribution-types/src/index_url.rs +++ b/crates/uv-distribution-types/src/index_url.rs @@ -92,20 +92,15 @@ impl IndexUrl { #[cfg(feature = "schemars")] impl schemars::JsonSchema for IndexUrl { - fn schema_name() -> String { - "IndexUrl".to_string() + fn schema_name() -> Cow<'static, str> { + Cow::Borrowed("IndexUrl") } - fn json_schema(_gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema { - schemars::schema::SchemaObject { - instance_type: Some(schemars::schema::InstanceType::String.into()), - metadata: Some(Box::new(schemars::schema::Metadata { - description: Some("The URL of an index to use for fetching packages (e.g., `https://pypi.org/simple`), or a local path.".to_string()), - ..schemars::schema::Metadata::default() - })), - ..schemars::schema::SchemaObject::default() - } - .into() + fn json_schema(_generator: &mut schemars::generate::SchemaGenerator) -> schemars::Schema { + schemars::json_schema!({ + "type": "string", + "description": "The URL of an index to use for fetching packages (e.g., `https://pypi.org/simple`), or a local path." + }) } } diff --git a/crates/uv-distribution-types/src/pip_index.rs b/crates/uv-distribution-types/src/pip_index.rs index 6ce22abd2..888c0df0d 100644 --- a/crates/uv-distribution-types/src/pip_index.rs +++ b/crates/uv-distribution-types/src/pip_index.rs @@ -3,7 +3,7 @@ //! flags set. use serde::{Deserialize, Deserializer, Serialize}; -use std::path::Path; +use std::{borrow::Cow, path::Path}; use crate::{Index, IndexUrl}; @@ -50,14 +50,14 @@ macro_rules! impl_index { #[cfg(feature = "schemars")] impl schemars::JsonSchema for $name { - fn schema_name() -> String { + fn schema_name() -> Cow<'static, str> { IndexUrl::schema_name() } fn json_schema( - r#gen: &mut schemars::r#gen::SchemaGenerator, - ) -> schemars::schema::Schema { - IndexUrl::json_schema(r#gen) + generator: &mut schemars::generate::SchemaGenerator, + ) -> schemars::Schema { + IndexUrl::json_schema(generator) } } }; diff --git a/crates/uv-distribution-types/src/status_code_strategy.rs b/crates/uv-distribution-types/src/status_code_strategy.rs index a2940a23a..709f68be1 100644 --- a/crates/uv-distribution-types/src/status_code_strategy.rs +++ b/crates/uv-distribution-types/src/status_code_strategy.rs @@ -1,4 +1,4 @@ -use std::ops::Deref; +use std::{borrow::Cow, ops::Deref}; use http::StatusCode; use rustc_hash::FxHashSet; @@ -136,17 +136,17 @@ impl<'de> Deserialize<'de> for SerializableStatusCode { #[cfg(feature = "schemars")] impl schemars::JsonSchema for SerializableStatusCode { - fn schema_name() -> String { - "StatusCode".to_string() + fn schema_name() -> Cow<'static, str> { + Cow::Borrowed("StatusCode") } - fn json_schema(r#gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema { - let mut schema = r#gen.subschema_for::().into_object(); - schema.metadata().description = Some("HTTP status code (100-599)".to_string()); - schema.number().minimum = Some(100.0); - schema.number().maximum = Some(599.0); - - schema.into() + fn json_schema(_generator: &mut schemars::generate::SchemaGenerator) -> schemars::Schema { + schemars::json_schema!({ + "type": "number", + "minimum": 100, + "maximum": 599, + "description": "HTTP status code (100-599)" + }) } } diff --git a/crates/uv-fs/src/path.rs b/crates/uv-fs/src/path.rs index 90d0ce80a..40e579f8e 100644 --- a/crates/uv-fs/src/path.rs +++ b/crates/uv-fs/src/path.rs @@ -330,11 +330,11 @@ pub struct PortablePathBuf(Box); #[cfg(feature = "schemars")] impl schemars::JsonSchema for PortablePathBuf { - fn schema_name() -> String { - PathBuf::schema_name() + fn schema_name() -> Cow<'static, str> { + Cow::Borrowed("PortablePathBuf") } - fn json_schema(_gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema { + fn json_schema(_gen: &mut schemars::generate::SchemaGenerator) -> schemars::Schema { PathBuf::json_schema(_gen) } } diff --git a/crates/uv-pep508/Cargo.toml b/crates/uv-pep508/Cargo.toml index 7494a722d..e9306da00 100644 --- a/crates/uv-pep508/Cargo.toml +++ b/crates/uv-pep508/Cargo.toml @@ -41,7 +41,7 @@ version-ranges = { workspace = true } [dev-dependencies] insta = { version = "1.40.0" } -serde_json = { version = "1.0.128" } +serde_json = { workspace = true } tracing-test = { version = "0.2.5" } [features] diff --git a/crates/uv-pep508/src/lib.rs b/crates/uv-pep508/src/lib.rs index e313db86d..46e2f3039 100644 --- a/crates/uv-pep508/src/lib.rs +++ b/crates/uv-pep508/src/lib.rs @@ -16,6 +16,7 @@ #![warn(missing_docs)] +use std::borrow::Cow; use std::error::Error; use std::fmt::{Debug, Display, Formatter}; use std::path::Path; @@ -334,22 +335,15 @@ impl Reporter for TracingReporter { #[cfg(feature = "schemars")] impl schemars::JsonSchema for Requirement { - fn schema_name() -> String { - "Requirement".to_string() + fn schema_name() -> Cow<'static, str> { + Cow::Borrowed("Requirement") } - fn json_schema(_gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema { - schemars::schema::SchemaObject { - instance_type: Some(schemars::schema::InstanceType::String.into()), - metadata: Some(Box::new(schemars::schema::Metadata { - description: Some( - "A PEP 508 dependency specifier, e.g., `ruff >= 0.6.0`".to_string(), - ), - ..schemars::schema::Metadata::default() - })), - ..schemars::schema::SchemaObject::default() - } - .into() + fn json_schema(_gen: &mut schemars::generate::SchemaGenerator) -> schemars::Schema { + schemars::json_schema!({ + "type": "string", + "description": "A PEP 508 dependency specifier, e.g., `ruff >= 0.6.0`" + }) } } diff --git a/crates/uv-pep508/src/marker/tree.rs b/crates/uv-pep508/src/marker/tree.rs index 070a24b26..975d4ff10 100644 --- a/crates/uv-pep508/src/marker/tree.rs +++ b/crates/uv-pep508/src/marker/tree.rs @@ -1707,23 +1707,15 @@ impl Display for MarkerTreeContents { #[cfg(feature = "schemars")] impl schemars::JsonSchema for MarkerTree { - fn schema_name() -> String { - "MarkerTree".to_string() + fn schema_name() -> Cow<'static, str> { + Cow::Borrowed("MarkerTree") } - fn json_schema(_gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema { - schemars::schema::SchemaObject { - instance_type: Some(schemars::schema::InstanceType::String.into()), - metadata: Some(Box::new(schemars::schema::Metadata { - description: Some( - "A PEP 508-compliant marker expression, e.g., `sys_platform == 'Darwin'`" - .to_string(), - ), - ..schemars::schema::Metadata::default() - })), - ..schemars::schema::SchemaObject::default() - } - .into() + fn json_schema(_generator: &mut schemars::generate::SchemaGenerator) -> schemars::Schema { + schemars::json_schema!({ + "type": "string", + "description": "A PEP 508-compliant marker expression, e.g., `sys_platform == 'Darwin'`" + }) } } @@ -2515,7 +2507,7 @@ mod test { #[test] fn test_simplification_extra_versus_other() { // Here, the `extra != 'foo'` cannot be simplified out, because - // `extra == 'foo'` can be true even when `extra == 'bar`' is true. + // `extra == 'foo'` can be true even when `extra == 'bar'`' is true. assert_simplifies( r#"extra != "foo" and (extra == "bar" or extra == "baz")"#, "(extra == 'bar' and extra != 'foo') or (extra == 'baz' and extra != 'foo')", diff --git a/crates/uv-pypi-types/src/conflicts.rs b/crates/uv-pypi-types/src/conflicts.rs index 94366bfd2..dd1e96b77 100644 --- a/crates/uv-pypi-types/src/conflicts.rs +++ b/crates/uv-pypi-types/src/conflicts.rs @@ -3,7 +3,7 @@ use petgraph::{ graph::{DiGraph, NodeIndex}, }; use rustc_hash::{FxHashMap, FxHashSet}; -use std::{collections::BTreeSet, hash::Hash, rc::Rc}; +use std::{borrow::Cow, collections::BTreeSet, hash::Hash, rc::Rc}; use uv_normalize::{ExtraName, GroupName, PackageName}; use crate::dependency_groups::{DependencyGroupSpecifier, DependencyGroups}; @@ -638,12 +638,12 @@ pub struct SchemaConflictItem { #[cfg(feature = "schemars")] impl schemars::JsonSchema for SchemaConflictItem { - fn schema_name() -> String { - "SchemaConflictItem".to_string() + fn schema_name() -> Cow<'static, str> { + Cow::Borrowed("SchemaConflictItem") } - fn json_schema(r#gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema { - ::json_schema(r#gen) + fn json_schema(generator: &mut schemars::generate::SchemaGenerator) -> schemars::Schema { + ::json_schema(generator) } } diff --git a/crates/uv-pypi-types/src/identifier.rs b/crates/uv-pypi-types/src/identifier.rs index b0c78d5b2..972a327ae 100644 --- a/crates/uv-pypi-types/src/identifier.rs +++ b/crates/uv-pypi-types/src/identifier.rs @@ -1,4 +1,5 @@ use serde::{Serialize, Serializer}; +use std::borrow::Cow; use std::fmt::Display; use std::str::FromStr; use thiserror::Error; @@ -99,25 +100,16 @@ impl Serialize for Identifier { #[cfg(feature = "schemars")] impl schemars::JsonSchema for Identifier { - fn schema_name() -> String { - "Identifier".to_string() + fn schema_name() -> Cow<'static, str> { + Cow::Borrowed("Identifier") } - fn json_schema(_gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema { - schemars::schema::SchemaObject { - instance_type: Some(schemars::schema::InstanceType::String.into()), - string: Some(Box::new(schemars::schema::StringValidation { - // Best-effort Unicode support (https://stackoverflow.com/a/68844380/3549270) - pattern: Some(r"^[_\p{Alphabetic}][_0-9\p{Alphabetic}]*$".to_string()), - ..schemars::schema::StringValidation::default() - })), - metadata: Some(Box::new(schemars::schema::Metadata { - description: Some("An identifier in Python".to_string()), - ..schemars::schema::Metadata::default() - })), - ..schemars::schema::SchemaObject::default() - } - .into() + fn json_schema(_generator: &mut schemars::generate::SchemaGenerator) -> schemars::Schema { + schemars::json_schema!({ + "type": "string", + "pattern": r"^[_\p{Alphabetic}][_0-9\p{Alphabetic}]*$", + "description": "An identifier in Python" + }) } } diff --git a/crates/uv-python/src/python_version.rs b/crates/uv-python/src/python_version.rs index 39e8e3429..63f50f226 100644 --- a/crates/uv-python/src/python_version.rs +++ b/crates/uv-python/src/python_version.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::fmt::{Display, Formatter}; use std::ops::Deref; use std::str::FromStr; @@ -65,26 +66,16 @@ impl FromStr for PythonVersion { #[cfg(feature = "schemars")] impl schemars::JsonSchema for PythonVersion { - fn schema_name() -> String { - String::from("PythonVersion") + fn schema_name() -> Cow<'static, str> { + Cow::Borrowed("PythonVersion") } - fn json_schema(_gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema { - schemars::schema::SchemaObject { - instance_type: Some(schemars::schema::InstanceType::String.into()), - string: Some(Box::new(schemars::schema::StringValidation { - pattern: Some(r"^3\.\d+(\.\d+)?$".to_string()), - ..schemars::schema::StringValidation::default() - })), - metadata: Some(Box::new(schemars::schema::Metadata { - description: Some( - "A Python version specifier, e.g. `3.11` or `3.12.4`.".to_string(), - ), - ..schemars::schema::Metadata::default() - })), - ..schemars::schema::SchemaObject::default() - } - .into() + fn json_schema(_generator: &mut schemars::generate::SchemaGenerator) -> schemars::Schema { + schemars::json_schema!({ + "type": "string", + "pattern": r"^3\.\d+(\.\d+)?$", + "description": "A Python version specifier, e.g. `3.11` or `3.12.4`." + }) } } diff --git a/crates/uv-resolver/src/exclude_newer.rs b/crates/uv-resolver/src/exclude_newer.rs index 40ec009f8..c1ac6adb8 100644 --- a/crates/uv-resolver/src/exclude_newer.rs +++ b/crates/uv-resolver/src/exclude_newer.rs @@ -1,4 +1,4 @@ -use std::str::FromStr; +use std::{borrow::Cow, str::FromStr}; use jiff::{Timestamp, ToSpan, tz::TimeZone}; @@ -67,25 +67,15 @@ impl std::fmt::Display for ExcludeNewer { #[cfg(feature = "schemars")] impl schemars::JsonSchema for ExcludeNewer { - fn schema_name() -> String { - "ExcludeNewer".to_string() + fn schema_name() -> Cow<'static, str> { + Cow::Borrowed("ExcludeNewer") } - fn json_schema(_gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema { - schemars::schema::SchemaObject { - instance_type: Some(schemars::schema::InstanceType::String.into()), - string: Some(Box::new(schemars::schema::StringValidation { - pattern: Some( - r"^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(Z|[+-]\d{2}:\d{2}))?$".to_string(), - ), - ..schemars::schema::StringValidation::default() - })), - metadata: Some(Box::new(schemars::schema::Metadata { - description: Some("Exclude distributions uploaded after the given timestamp.\n\nAccepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and local dates in the same format (e.g., `2006-12-02`).".to_string()), - ..schemars::schema::Metadata::default() - })), - ..schemars::schema::SchemaObject::default() - } - .into() + fn json_schema(_generator: &mut schemars::generate::SchemaGenerator) -> schemars::Schema { + schemars::json_schema!({ + "type": "string", + "pattern": r"^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(Z|[+-]\d{2}:\d{2}))?$", + "description": "Exclude distributions uploaded after the given timestamp.\n\nAccepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and local dates in the same format (e.g., `2006-12-02`).", + }) } } diff --git a/crates/uv-settings/src/settings.rs b/crates/uv-settings/src/settings.rs index 2c18fb40a..d80ccce2f 100644 --- a/crates/uv-settings/src/settings.rs +++ b/crates/uv-settings/src/settings.rs @@ -41,6 +41,7 @@ pub(crate) struct Tools { #[derive(Debug, Clone, Default, Deserialize, CombineOptions, OptionsMetadata)] #[serde(from = "OptionsWire", rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[cfg_attr(feature = "schemars", schemars(!from))] pub struct Options { #[serde(flatten)] pub globals: GlobalOptions, diff --git a/crates/uv-small-str/src/lib.rs b/crates/uv-small-str/src/lib.rs index 7395c090a..1524f1b99 100644 --- a/crates/uv-small-str/src/lib.rs +++ b/crates/uv-small-str/src/lib.rs @@ -147,15 +147,15 @@ impl PartialOrd for rkyv::string::ArchivedString { /// An [`schemars::JsonSchema`] implementation for [`SmallString`]. #[cfg(feature = "schemars")] impl schemars::JsonSchema for SmallString { - fn is_referenceable() -> bool { - String::is_referenceable() + fn inline_schema() -> bool { + true } - fn schema_name() -> String { + fn schema_name() -> Cow<'static, str> { String::schema_name() } - fn json_schema(_gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema { - String::json_schema(_gen) + fn json_schema(generator: &mut schemars::generate::SchemaGenerator) -> schemars::Schema { + String::json_schema(generator) } } diff --git a/crates/uv-workspace/src/pyproject.rs b/crates/uv-workspace/src/pyproject.rs index 6499aad5d..dabbe33fb 100644 --- a/crates/uv-workspace/src/pyproject.rs +++ b/crates/uv-workspace/src/pyproject.rs @@ -6,6 +6,7 @@ //! //! Then lowers them into a dependency specification. +use std::borrow::Cow; use std::collections::BTreeMap; use std::fmt::Formatter; use std::ops::Deref; @@ -813,12 +814,12 @@ impl<'de> serde::Deserialize<'de> for SerdePattern { #[cfg(feature = "schemars")] impl schemars::JsonSchema for SerdePattern { - fn schema_name() -> String { - ::schema_name() + fn schema_name() -> Cow<'static, str> { + Cow::Borrowed("SerdePattern") } - fn json_schema(r#gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema { - ::json_schema(r#gen) + fn json_schema(generator: &mut schemars::generate::SchemaGenerator) -> schemars::Schema { + ::json_schema(generator) } } diff --git a/uv.schema.json b/uv.schema.json index 0b9bd6f15..b00463ee9 100644 --- a/uv.schema.json +++ b/uv.schema.json @@ -5,7 +5,7 @@ "type": "object", "properties": { "add-bounds": { - "description": "The default version specifier when adding a dependency.\n\nWhen adding a dependency to the project, if no constraint or URL is provided, a constraint is added based on the latest compatible version of the package. By default, a lower bound constraint is used, e.g., `>=1.2.3`.\n\nWhen `--frozen` is provided, no resolution is performed, and dependencies are always added without constraints.\n\nThis option is in preview and may change in any future release.", + "description": "The default version specifier when adding a dependency.\n\nWhen adding a dependency to the project, if no constraint or URL is provided, a constraint\nis added based on the latest compatible version of the package. By default, a lower bound\nconstraint is used, e.g., `>=1.2.3`.\n\nWhen `--frozen` is provided, no resolution is performed, and dependencies are always added\nwithout constraints.\n\nThis option is in preview and may change in any future release.", "anyOf": [ { "$ref": "#/definitions/AddBoundsKind" @@ -16,7 +16,7 @@ ] }, "allow-insecure-host": { - "description": "Allow insecure connections to host.\n\nExpects to receive either a hostname (e.g., `localhost`), a host-port pair (e.g., `localhost:8080`), or a URL (e.g., `https://localhost`).\n\nWARNING: Hosts included in this list will not be verified against the system's certificate store. Only use `--allow-insecure-host` in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.", + "description": "Allow insecure connections to host.\n\nExpects to receive either a hostname (e.g., `localhost`), a host-port pair (e.g.,\n`localhost:8080`), or a URL (e.g., `https://localhost`).\n\nWARNING: Hosts included in this list will not be verified against the system's certificate\nstore. Only use `--allow-insecure-host` in a secure network with verified sources, as it\nbypasses SSL verification and could expose you to MITM attacks.", "type": [ "array", "null" @@ -26,7 +26,7 @@ } }, "build-backend": { - "description": "Configuration for the uv build backend.\n\nNote that those settings only apply when using the `uv_build` backend, other build backends (such as hatchling) have their own configuration.", + "description": "Configuration for the uv build backend.\n\nNote that those settings only apply when using the `uv_build` backend, other build backends\n(such as hatchling) have their own configuration.", "anyOf": [ { "$ref": "#/definitions/BuildBackendSettings" @@ -47,14 +47,14 @@ } }, "cache-dir": { - "description": "Path to the cache directory.\n\nDefaults to `$XDG_CACHE_HOME/uv` or `$HOME/.cache/uv` on Linux and macOS, and `%LOCALAPPDATA%\\uv\\cache` on Windows.", + "description": "Path to the cache directory.\n\nDefaults to `$XDG_CACHE_HOME/uv` or `$HOME/.cache/uv` on Linux and macOS, and\n`%LOCALAPPDATA%\\uv\\cache` on Windows.", "type": [ "string", "null" ] }, "cache-keys": { - "description": "The keys to consider when caching builds for the project.\n\nCache keys enable you to specify the files or directories that should trigger a rebuild when modified. By default, uv will rebuild a project whenever the `pyproject.toml`, `setup.py`, or `setup.cfg` files in the project directory are modified, or if a `src` directory is added or removed, i.e.:\n\n```toml cache-keys = [{ file = \"pyproject.toml\" }, { file = \"setup.py\" }, { file = \"setup.cfg\" }, { dir = \"src\" }] ```\n\nAs an example: if a project uses dynamic metadata to read its dependencies from a `requirements.txt` file, you can specify `cache-keys = [{ file = \"requirements.txt\" }, { file = \"pyproject.toml\" }]` to ensure that the project is rebuilt whenever the `requirements.txt` file is modified (in addition to watching the `pyproject.toml`).\n\nGlobs are supported, following the syntax of the [`glob`](https://docs.rs/glob/0.3.1/glob/struct.Pattern.html) crate. For example, to invalidate the cache whenever a `.toml` file in the project directory or any of its subdirectories is modified, you can specify `cache-keys = [{ file = \"**/*.toml\" }]`. Note that the use of globs can be expensive, as uv may need to walk the filesystem to determine whether any files have changed.\n\nCache keys can also include version control information. For example, if a project uses `setuptools_scm` to read its version from a Git commit, you can specify `cache-keys = [{ git = { commit = true }, { file = \"pyproject.toml\" }]` to include the current Git commit hash in the cache key (in addition to the `pyproject.toml`). Git tags are also supported via `cache-keys = [{ git = { commit = true, tags = true } }]`.\n\nCache keys can also include environment variables. For example, if a project relies on `MACOSX_DEPLOYMENT_TARGET` or other environment variables to determine its behavior, you can specify `cache-keys = [{ env = \"MACOSX_DEPLOYMENT_TARGET\" }]` to invalidate the cache whenever the environment variable changes.\n\nCache keys only affect the project defined by the `pyproject.toml` in which they're specified (as opposed to, e.g., affecting all members in a workspace), and all paths and globs are interpreted as relative to the project directory.", + "description": "The keys to consider when caching builds for the project.\n\nCache keys enable you to specify the files or directories that should trigger a rebuild when\nmodified. By default, uv will rebuild a project whenever the `pyproject.toml`, `setup.py`,\nor `setup.cfg` files in the project directory are modified, or if a `src` directory is\nadded or removed, i.e.:\n\n```toml\ncache-keys = [{ file = \"pyproject.toml\" }, { file = \"setup.py\" }, { file = \"setup.cfg\" }, { dir = \"src\" }]\n```\n\nAs an example: if a project uses dynamic metadata to read its dependencies from a\n`requirements.txt` file, you can specify `cache-keys = [{ file = \"requirements.txt\" }, { file = \"pyproject.toml\" }]`\nto ensure that the project is rebuilt whenever the `requirements.txt` file is modified (in\naddition to watching the `pyproject.toml`).\n\nGlobs are supported, following the syntax of the [`glob`](https://docs.rs/glob/0.3.1/glob/struct.Pattern.html)\ncrate. For example, to invalidate the cache whenever a `.toml` file in the project directory\nor any of its subdirectories is modified, you can specify `cache-keys = [{ file = \"**/*.toml\" }]`.\nNote that the use of globs can be expensive, as uv may need to walk the filesystem to\ndetermine whether any files have changed.\n\nCache keys can also include version control information. For example, if a project uses\n`setuptools_scm` to read its version from a Git commit, you can specify `cache-keys = [{ git = { commit = true }, { file = \"pyproject.toml\" }]`\nto include the current Git commit hash in the cache key (in addition to the\n`pyproject.toml`). Git tags are also supported via `cache-keys = [{ git = { commit = true, tags = true } }]`.\n\nCache keys can also include environment variables. For example, if a project relies on\n`MACOSX_DEPLOYMENT_TARGET` or other environment variables to determine its behavior, you can\nspecify `cache-keys = [{ env = \"MACOSX_DEPLOYMENT_TARGET\" }]` to invalidate the cache\nwhenever the environment variable changes.\n\nCache keys only affect the project defined by the `pyproject.toml` in which they're\nspecified (as opposed to, e.g., affecting all members in a workspace), and all paths and\nglobs are interpreted as relative to the project directory.", "type": [ "array", "null" @@ -64,7 +64,7 @@ } }, "check-url": { - "description": "Check an index URL for existing files to skip duplicate uploads.\n\nThis option allows retrying publishing that failed after only some, but not all files have been uploaded, and handles error due to parallel uploads of the same file.\n\nBefore uploading, the index is checked. If the exact same file already exists in the index, the file will not be uploaded. If an error occurred during the upload, the index is checked again, to handle cases where the identical file was uploaded twice in parallel.\n\nThe exact behavior will vary based on the index. When uploading to PyPI, uploading the same file succeeds even without `--check-url`, while most other indexes error.\n\nThe index must provide one of the supported hashes (SHA-256, SHA-384, or SHA-512).", + "description": "Check an index URL for existing files to skip duplicate uploads.\n\nThis option allows retrying publishing that failed after only some, but not all files have\nbeen uploaded, and handles error due to parallel uploads of the same file.\n\nBefore uploading, the index is checked. If the exact same file already exists in the index,\nthe file will not be uploaded. If an error occurred during the upload, the index is checked\nagain, to handle cases where the identical file was uploaded twice in parallel.\n\nThe exact behavior will vary based on the index. When uploading to PyPI, uploading the same\nfile succeeds even without `--check-url`, while most other indexes error.\n\nThe index must provide one of the supported hashes (SHA-256, SHA-384, or SHA-512).", "anyOf": [ { "$ref": "#/definitions/IndexUrl" @@ -75,29 +75,29 @@ ] }, "compile-bytecode": { - "description": "Compile Python files to bytecode after installation.\n\nBy default, uv does not compile Python (`.py`) files to bytecode (`__pycache__/*.pyc`); instead, compilation is performed lazily the first time a module is imported. For use-cases in which start time is critical, such as CLI applications and Docker containers, this option can be enabled to trade longer installation times for faster start times.\n\nWhen enabled, uv will process the entire site-packages directory (including packages that are not being modified by the current operation) for consistency. Like pip, it will also ignore errors.", + "description": "Compile Python files to bytecode after installation.\n\nBy default, uv does not compile Python (`.py`) files to bytecode (`__pycache__/*.pyc`);\ninstead, compilation is performed lazily the first time a module is imported. For use-cases\nin which start time is critical, such as CLI applications and Docker containers, this option\ncan be enabled to trade longer installation times for faster start times.\n\nWhen enabled, uv will process the entire site-packages directory (including packages that\nare not being modified by the current operation) for consistency. Like pip, it will also\nignore errors.", "type": [ "boolean", "null" ] }, "concurrent-builds": { - "description": "The maximum number of source distributions that uv will build concurrently at any given time.\n\nDefaults to the number of available CPU cores.", + "description": "The maximum number of source distributions that uv will build concurrently at any given\ntime.\n\nDefaults to the number of available CPU cores.", "type": [ "integer", "null" ], "format": "uint", - "minimum": 1.0 + "minimum": 1 }, "concurrent-downloads": { - "description": "The maximum number of in-flight concurrent downloads that uv will perform at any given time.", + "description": "The maximum number of in-flight concurrent downloads that uv will perform at any given\ntime.", "type": [ "integer", "null" ], "format": "uint", - "minimum": 1.0 + "minimum": 1 }, "concurrent-installs": { "description": "The number of threads used when installing and unzipping packages.\n\nDefaults to the number of available CPU cores.", @@ -106,10 +106,10 @@ "null" ], "format": "uint", - "minimum": 1.0 + "minimum": 1 }, "config-settings": { - "description": "Settings to pass to the [PEP 517](https://peps.python.org/pep-0517/) build backend, specified as `KEY=VALUE` pairs.", + "description": "Settings to pass to the [PEP 517](https://peps.python.org/pep-0517/) build backend,\nspecified as `KEY=VALUE` pairs.", "anyOf": [ { "$ref": "#/definitions/ConfigSettings" @@ -152,7 +152,7 @@ ] }, "dependency-groups": { - "description": "Additional settings for `dependency-groups`.\n\nCurrently this can only be used to add `requires-python` constraints to dependency groups (typically to inform uv that your dev tooling has a higher python requirement than your actual project).\n\nThis cannot be used to define dependency groups, use the top-level `[dependency-groups]` table for that.", + "description": "Additional settings for `dependency-groups`.\n\nCurrently this can only be used to add `requires-python` constraints\nto dependency groups (typically to inform uv that your dev tooling\nhas a higher python requirement than your actual project).\n\nThis cannot be used to define dependency groups, use the top-level\n`[dependency-groups]` table for that.", "anyOf": [ { "$ref": "#/definitions/ToolUvDependencyGroups" @@ -163,7 +163,7 @@ ] }, "dependency-metadata": { - "description": "Pre-defined static metadata for dependencies of the project (direct or transitive). When provided, enables the resolver to use the specified metadata instead of querying the registry or building the relevant package from source.\n\nMetadata should be provided in adherence with the [Metadata 2.3](https://packaging.python.org/en/latest/specifications/core-metadata/) standard, though only the following fields are respected:\n\n- `name`: The name of the package. - (Optional) `version`: The version of the package. If omitted, the metadata will be applied to all versions of the package. - (Optional) `requires-dist`: The dependencies of the package (e.g., `werkzeug>=0.14`). - (Optional) `requires-python`: The Python version required by the package (e.g., `>=3.10`). - (Optional) `provides-extras`: The extras provided by the package.", + "description": "Pre-defined static metadata for dependencies of the project (direct or transitive). When\nprovided, enables the resolver to use the specified metadata instead of querying the\nregistry or building the relevant package from source.\n\nMetadata should be provided in adherence with the [Metadata 2.3](https://packaging.python.org/en/latest/specifications/core-metadata/)\nstandard, though only the following fields are respected:\n\n- `name`: The name of the package.\n- (Optional) `version`: The version of the package. If omitted, the metadata will be applied\n to all versions of the package.\n- (Optional) `requires-dist`: The dependencies of the package (e.g., `werkzeug>=0.14`).\n- (Optional) `requires-python`: The Python version required by the package (e.g., `>=3.10`).\n- (Optional) `provides-extras`: The extras provided by the package.", "type": [ "array", "null" @@ -193,7 +193,7 @@ } }, "exclude-newer": { - "description": "Limit candidate packages to those that were uploaded prior to a given point in time.\n\nAccepts a superset of [RFC 3339](https://www.rfc-editor.org/rfc/rfc3339.html) (e.g., `2006-12-02T02:07:43Z`). A full timestamp is required to ensure that the resolver will behave consistently across timezones.", + "description": "Limit candidate packages to those that were uploaded prior to a given point in time.\n\nAccepts a superset of [RFC 3339](https://www.rfc-editor.org/rfc/rfc3339.html) (e.g.,\n`2006-12-02T02:07:43Z`). A full timestamp is required to ensure that the resolver will\nbehave consistently across timezones.", "anyOf": [ { "$ref": "#/definitions/ExcludeNewer" @@ -204,7 +204,7 @@ ] }, "extra-index-url": { - "description": "Extra URLs of package indexes to use, in addition to `--index-url`.\n\nAccepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/) (the simple repository API), or a local directory laid out in the same format.\n\nAll indexes provided via this flag take priority over the index specified by [`index_url`](#index-url) or [`index`](#index) with `default = true`. When multiple indexes are provided, earlier values take priority.\n\nTo control uv's resolution strategy when multiple indexes are present, see [`index_strategy`](#index-strategy).\n\n(Deprecated: use `index` instead.)", + "description": "Extra URLs of package indexes to use, in addition to `--index-url`.\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\nAll indexes provided via this flag take priority over the index specified by\n[`index_url`](#index-url) or [`index`](#index) with `default = true`. When multiple indexes\nare provided, earlier values take priority.\n\nTo control uv's resolution strategy when multiple indexes are present, see\n[`index_strategy`](#index-strategy).\n\n(Deprecated: use `index` instead.)", "type": [ "array", "null" @@ -214,7 +214,7 @@ } }, "find-links": { - "description": "Locations to search for candidate distributions, in addition to those found in the registry indexes.\n\nIf a path, the target must be a directory that contains packages as wheel files (`.whl`) or source distributions (e.g., `.tar.gz` or `.zip`) at the top level.\n\nIf a URL, the page must contain a flat list of links to package files adhering to the formats described above.", + "description": "Locations to search for candidate distributions, in addition to those found in the registry\nindexes.\n\nIf a path, the target must be a directory that contains packages as wheel files (`.whl`) or\nsource distributions (e.g., `.tar.gz` or `.zip`) at the top level.\n\nIf a URL, the page must contain a flat list of links to package files adhering to the\nformats described above.", "type": [ "array", "null" @@ -224,7 +224,7 @@ } }, "fork-strategy": { - "description": "The strategy to use when selecting multiple versions of a given package across Python versions and platforms.\n\nBy default, uv will optimize for selecting the latest version of each package for each supported Python version (`requires-python`), while minimizing the number of selected versions across platforms.\n\nUnder `fewest`, uv will minimize the number of selected versions for each package, preferring older versions that are compatible with a wider range of supported Python versions or platforms.", + "description": "The strategy to use when selecting multiple versions of a given package across Python\nversions and platforms.\n\nBy default, uv will optimize for selecting the latest version of each package for each\nsupported Python version (`requires-python`), while minimizing the number of selected\nversions across platforms.\n\nUnder `fewest`, uv will minimize the number of selected versions for each package,\npreferring older versions that are compatible with a wider range of supported Python\nversions or platforms.", "anyOf": [ { "$ref": "#/definitions/ForkStrategy" @@ -235,18 +235,18 @@ ] }, "index": { - "description": "The indexes to use when resolving dependencies.\n\nAccepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/) (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 index has the highest priority. Further, the indexes provided by this setting are given higher priority than any indexes specified via [`index_url`](#index-url) or [`extra_index_url`](#extra-index-url). uv will only consider the first index that contains a 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 dependencies that select it explicitly via `[tool.uv.sources]`, as in:\n\n```toml [[tool.uv.index]] name = \"pytorch\" url = \"https://download.pytorch.org/whl/cu121\" explicit = true\n\n[tool.uv.sources] torch = { index = \"pytorch\" } ```\n\nIf an index is marked as `default = true`, it will be moved to the end of the prioritized list, such that it is given the lowest priority when resolving packages. Additionally, marking an index as default will disable the PyPI default index.", - "default": null, + "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.", "type": [ "array", "null" ], + "default": null, "items": { "$ref": "#/definitions/Index" } }, "index-strategy": { - "description": "The strategy to use when resolving against multiple index URLs.\n\nBy default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (`first-index`). This prevents \"dependency confusion\" attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.", + "description": "The strategy to use when resolving against multiple index URLs.\n\nBy default, uv will stop at the first index on which a given package is available, and\nlimit resolutions to those present on that first index (`first-index`). This prevents\n\"dependency confusion\" attacks, whereby an attacker can upload a malicious package under the\nsame name to an alternate index.", "anyOf": [ { "$ref": "#/definitions/IndexStrategy" @@ -257,7 +257,7 @@ ] }, "index-url": { - "description": "The URL of the Python package index (by default: ).\n\nAccepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/) (the simple repository API), or a local directory laid out in the same format.\n\nThe index provided by this setting is given lower priority than any indexes specified via [`extra_index_url`](#extra-index-url) or [`index`](#index).\n\n(Deprecated: use `index` instead.)", + "description": "The URL of the Python package index (by default: ).\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\nThe index provided by this setting is given lower priority than any indexes specified via\n[`extra_index_url`](#extra-index-url) or [`index`](#index).\n\n(Deprecated: use `index` instead.)", "anyOf": [ { "$ref": "#/definitions/IndexUrl" @@ -268,7 +268,7 @@ ] }, "keyring-provider": { - "description": "Attempt to use `keyring` for authentication for index URLs.\n\nAt present, only `--keyring-provider subprocess` is supported, which configures uv to use the `keyring` CLI to handle authentication.", + "description": "Attempt to use `keyring` for authentication for index URLs.\n\nAt present, only `--keyring-provider subprocess` is supported, which configures uv to\nuse the `keyring` CLI to handle authentication.", "anyOf": [ { "$ref": "#/definitions/KeyringProviderType" @@ -279,7 +279,7 @@ ] }, "link-mode": { - "description": "The method to use when installing packages from the global cache.\n\nDefaults to `clone` (also known as Copy-on-Write) on macOS, and `hardlink` on Linux and Windows.", + "description": "The method to use when installing packages from the global cache.\n\nDefaults to `clone` (also known as Copy-on-Write) on macOS, and `hardlink` on Linux and\nWindows.", "anyOf": [ { "$ref": "#/definitions/LinkMode" @@ -290,21 +290,21 @@ ] }, "managed": { - "description": "Whether the project is managed by uv. If `false`, uv will ignore the project when `uv run` is invoked.", + "description": "Whether the project is managed by uv. If `false`, uv will ignore the project when\n`uv run` is invoked.", "type": [ "boolean", "null" ] }, "native-tls": { - "description": "Whether to load TLS certificates from the platform's native certificate store.\n\nBy default, uv loads certificates from the bundled `webpki-roots` crate. The `webpki-roots` are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).\n\nHowever, in some cases, you may want to use the platform's native certificate store, especially if you're relying on a corporate trust root (e.g., for a mandatory proxy) that's included in your system's certificate store.", + "description": "Whether to load TLS certificates from the platform's native certificate store.\n\nBy default, uv loads certificates from the bundled `webpki-roots` crate. The\n`webpki-roots` are a reliable set of trust roots from Mozilla, and including them in uv\nimproves portability and performance (especially on macOS).\n\nHowever, in some cases, you may want to use the platform's native certificate store,\nespecially if you're relying on a corporate trust root (e.g., for a mandatory proxy) that's\nincluded in your system's certificate store.", "type": [ "boolean", "null" ] }, "no-binary": { - "description": "Don't install pre-built wheels.\n\nThe given packages will be built and installed from source. The resolver will still use pre-built wheels to extract package metadata, if available.", + "description": "Don't install pre-built wheels.\n\nThe given packages will be built and installed from source. The resolver will still use\npre-built wheels to extract package metadata, if available.", "type": [ "boolean", "null" @@ -321,21 +321,21 @@ } }, "no-build": { - "description": "Don't build source distributions.\n\nWhen enabled, resolving will not run arbitrary Python code. The cached wheels of already-built source distributions will be reused, but operations that require building distributions will exit with an error.", + "description": "Don't build source distributions.\n\nWhen enabled, resolving will not run arbitrary Python code. The cached wheels of\nalready-built source distributions will be reused, but operations that require building\ndistributions will exit with an error.", "type": [ "boolean", "null" ] }, "no-build-isolation": { - "description": "Disable isolation when building source distributions.\n\nAssumes that build dependencies specified by [PEP 518](https://peps.python.org/pep-0518/) are already installed.", + "description": "Disable isolation when building source distributions.\n\nAssumes that build dependencies specified by [PEP 518](https://peps.python.org/pep-0518/)\nare already installed.", "type": [ "boolean", "null" ] }, "no-build-isolation-package": { - "description": "Disable isolation when building source distributions for a specific package.\n\nAssumes that the packages' build dependencies specified by [PEP 518](https://peps.python.org/pep-0518/) are already installed.", + "description": "Disable isolation when building source distributions for a specific package.\n\nAssumes that the packages' build dependencies specified by [PEP 518](https://peps.python.org/pep-0518/)\nare already installed.", "type": [ "array", "null" @@ -355,21 +355,21 @@ } }, "no-cache": { - "description": "Avoid reading from or writing to the cache, instead using a temporary directory for the duration of the operation.", + "description": "Avoid reading from or writing to the cache, instead using a temporary directory for the\nduration of the operation.", "type": [ "boolean", "null" ] }, "no-index": { - "description": "Ignore all registry indexes (e.g., PyPI), instead relying on direct URL dependencies and those provided via `--find-links`.", + "description": "Ignore all registry indexes (e.g., PyPI), instead relying on direct URL dependencies and\nthose provided via `--find-links`.", "type": [ "boolean", "null" ] }, "no-sources": { - "description": "Ignore the `tool.uv.sources` table when resolving dependencies. Used to lock against the standards-compliant, publishable package metadata, as opposed to using any local or Git sources.", + "description": "Ignore the `tool.uv.sources` table when resolving dependencies. Used to lock against the\nstandards-compliant, publishable package metadata, as opposed to using any local or Git\nsources.", "type": [ "boolean", "null" @@ -393,7 +393,7 @@ } }, "package": { - "description": "Whether the project should be considered a Python package, or a non-package (\"virtual\") project.\n\nPackages are built and installed into the virtual environment in editable mode and thus require a build backend, while virtual projects are _not_ built or installed; instead, only their dependencies are included in the virtual environment.\n\nCreating a package requires that a `build-system` is present in the `pyproject.toml`, and that the project adheres to a structure that adheres to the build backend's expectations (e.g., a `src` layout).", + "description": "Whether the project should be considered a Python package, or a non-package (\"virtual\")\nproject.\n\nPackages are built and installed into the virtual environment in editable mode and thus\nrequire a build backend, while virtual projects are _not_ built or installed; instead, only\ntheir dependencies are included in the virtual environment.\n\nCreating a package requires that a `build-system` is present in the `pyproject.toml`, and\nthat the project adheres to a structure that adheres to the build backend's expectations\n(e.g., a `src` layout).", "type": [ "boolean", "null" @@ -410,7 +410,7 @@ ] }, "prerelease": { - "description": "The strategy to use when considering pre-release versions.\n\nBy default, uv will accept pre-releases for packages that _only_ publish pre-releases, along with first-party requirements that contain an explicit pre-release marker in the declared specifiers (`if-necessary-or-explicit`).", + "description": "The strategy to use when considering pre-release versions.\n\nBy default, uv will accept pre-releases for packages that _only_ publish pre-releases,\nalong with first-party requirements that contain an explicit pre-release marker in the\ndeclared specifiers (`if-necessary-or-explicit`).", "anyOf": [ { "$ref": "#/definitions/PrereleaseMode" @@ -428,15 +428,18 @@ ] }, "publish-url": { - "description": "The URL for publishing packages to the Python package index (by default: ).", - "type": [ - "string", - "null" - ], - "format": "uri" + "description": "The URL for publishing packages to the Python package index (by default:\n).", + "anyOf": [ + { + "$ref": "#/definitions/DisplaySafeUrl" + }, + { + "type": "null" + } + ] }, "pypy-install-mirror": { - "description": "Mirror URL to use for downloading managed PyPy installations.\n\nBy default, managed PyPy installations are downloaded from [downloads.python.org](https://downloads.python.org/). This variable can be set to a mirror URL to use a different source for PyPy installations. The provided URL will replace `https://downloads.python.org/pypy` in, e.g., `https://downloads.python.org/pypy/pypy3.8-v7.3.7-osx64.tar.bz2`.\n\nDistributions can be read from a local directory by using the `file://` URL scheme.", + "description": "Mirror URL to use for downloading managed PyPy installations.\n\nBy default, managed PyPy installations are downloaded from [downloads.python.org](https://downloads.python.org/).\nThis variable can be set to a mirror URL to use a different source for PyPy installations.\nThe provided URL will replace `https://downloads.python.org/pypy` in, e.g., `https://downloads.python.org/pypy/pypy3.8-v7.3.7-osx64.tar.bz2`.\n\nDistributions can be read from a\nlocal directory by using the `file://` URL scheme.", "type": [ "string", "null" @@ -461,14 +464,14 @@ ] }, "python-install-mirror": { - "description": "Mirror URL for downloading managed Python installations.\n\nBy default, managed Python installations are downloaded from [`python-build-standalone`](https://github.com/astral-sh/python-build-standalone). This variable can be set to a mirror URL to use a different source for Python installations. The provided URL will replace `https://github.com/astral-sh/python-build-standalone/releases/download` in, e.g., `https://github.com/astral-sh/python-build-standalone/releases/download/20240713/cpython-3.12.4%2B20240713-aarch64-apple-darwin-install_only.tar.gz`.\n\nDistributions can be read from a local directory by using the `file://` URL scheme.", + "description": "Mirror URL for downloading managed Python installations.\n\nBy default, managed Python installations are downloaded from [`python-build-standalone`](https://github.com/astral-sh/python-build-standalone).\nThis variable can be set to a mirror URL to use a different source for Python installations.\nThe provided URL will replace `https://github.com/astral-sh/python-build-standalone/releases/download` in, e.g., `https://github.com/astral-sh/python-build-standalone/releases/download/20240713/cpython-3.12.4%2B20240713-aarch64-apple-darwin-install_only.tar.gz`.\n\nDistributions can be read from a local directory by using the `file://` URL scheme.", "type": [ "string", "null" ] }, "python-preference": { - "description": "Whether to prefer using Python installations that are already present on the system, or those that are downloaded and installed by uv.", + "description": "Whether to prefer using Python installations that are already present on the system, or\nthose that are downloaded and installed by uv.", "anyOf": [ { "$ref": "#/definitions/PythonPreference" @@ -486,7 +489,7 @@ ] }, "reinstall-package": { - "description": "Reinstall a specific package, regardless of whether it's already installed. Implies `refresh-package`.", + "description": "Reinstall a specific package, regardless of whether it's already installed. Implies\n`refresh-package`.", "type": [ "array", "null" @@ -506,7 +509,7 @@ } }, "required-version": { - "description": "Enforce a requirement on the version of uv.\n\nIf the version of uv does not meet the requirement at runtime, uv will exit with an error.\n\nAccepts a [PEP 440](https://peps.python.org/pep-0440/) specifier, like `==0.5.0` or `>=0.5.0`.", + "description": "Enforce a requirement on the version of uv.\n\nIf the version of uv does not meet the requirement at runtime, uv will exit\nwith an error.\n\nAccepts a [PEP 440](https://peps.python.org/pep-0440/) specifier, like `==0.5.0` or `>=0.5.0`.", "anyOf": [ { "$ref": "#/definitions/RequiredVersion" @@ -517,7 +520,7 @@ ] }, "resolution": { - "description": "The strategy to use when selecting between the different compatible versions for a given package requirement.\n\nBy default, uv will use the latest compatible version of each package (`highest`).", + "description": "The strategy to use when selecting between the different compatible versions for a given\npackage requirement.\n\nBy default, uv will use the latest compatible version of each package (`highest`).", "anyOf": [ { "$ref": "#/definitions/ResolutionMode" @@ -528,7 +531,7 @@ ] }, "sources": { - "description": "The sources to use when resolving dependencies.\n\n`tool.uv.sources` enriches the dependency metadata with additional sources, incorporated during development. A dependency source can be a Git repository, a URL, a local path, or an alternative registry.\n\nSee [Dependencies](https://docs.astral.sh/uv/concepts/projects/dependencies/) for more.", + "description": "The sources to use when resolving dependencies.\n\n`tool.uv.sources` enriches the dependency metadata with additional sources, incorporated\nduring development. A dependency source can be a Git repository, a URL, a local path, or an\nalternative registry.\n\nSee [Dependencies](https://docs.astral.sh/uv/concepts/projects/dependencies/) for more.", "anyOf": [ { "$ref": "#/definitions/ToolUvSources" @@ -539,7 +542,7 @@ ] }, "trusted-publishing": { - "description": "Configure trusted publishing via GitHub Actions.\n\nBy default, uv checks for trusted publishing when running in GitHub Actions, but ignores it if it isn't configured or the workflow doesn't have enough permissions (e.g., a pull request from a fork).", + "description": "Configure trusted publishing via GitHub Actions.\n\nBy default, uv checks for trusted publishing when running in GitHub Actions, but ignores it\nif it isn't configured or the workflow doesn't have enough permissions (e.g., a pull request\nfrom a fork).", "anyOf": [ { "$ref": "#/definitions/TrustedPublishing" @@ -557,7 +560,7 @@ ] }, "upgrade-package": { - "description": "Allow upgrades for a specific package, ignoring pinned versions in any existing output file.\n\nAccepts both standalone package names (`ruff`) and version specifiers (`ruff<0.5.0`).", + "description": "Allow upgrades for a specific package, ignoring pinned versions in any existing output\nfile.\n\nAccepts both standalone package names (`ruff`) and version specifiers (`ruff<0.5.0`).", "type": [ "array", "null" @@ -578,6 +581,7 @@ ] } }, + "additionalProperties": false, "definitions": { "AddBoundsKind": { "description": "The default version specifier when adding a dependency.", @@ -585,49 +589,37 @@ { "description": "Only a lower bound, e.g., `>=1.2.3`.", "type": "string", - "enum": [ - "lower" - ] + "const": "lower" }, { "description": "Allow the same major version, similar to the semver caret, e.g., `>=1.2.3, <2.0.0`.\n\nLeading zeroes are skipped, e.g. `>=0.1.2, <0.2.0`.", "type": "string", - "enum": [ - "major" - ] + "const": "major" }, { "description": "Allow the same minor version, similar to the semver tilde, e.g., `>=1.2.3, <1.3.0`.\n\nLeading zeroes are skipped, e.g. `>=0.1.2, <0.1.3`.", "type": "string", - "enum": [ - "minor" - ] + "const": "minor" }, { "description": "Pin the exact version, e.g., `==1.2.3`.\n\nThis option is not recommended, as versions are already pinned in the uv lockfile.", "type": "string", - "enum": [ - "exact" - ] + "const": "exact" } ] }, "AnnotationStyle": { - "description": "Indicate the style of annotation comments, used to indicate the dependencies that requested each package.", + "description": "Indicate the style of annotation comments, used to indicate the dependencies that requested each\npackage.", "oneOf": [ { "description": "Render the annotations on a single, comma-separated line.", "type": "string", - "enum": [ - "line" - ] + "const": "line" }, { "description": "Render each annotation on its own line.", "type": "string", - "enum": [ - "split" - ] + "const": "split" } ] }, @@ -635,90 +627,84 @@ "description": "When to use authentication.", "oneOf": [ { - "description": "Authenticate when necessary.\n\nIf credentials are provided, they will be used. Otherwise, an unauthenticated request will be attempted first. If the request fails, uv will search for credentials. If credentials are found, an authenticated request will be attempted.", + "description": "Authenticate when necessary.\n\nIf credentials are provided, they will be used. Otherwise, an unauthenticated request will\nbe attempted first. If the request fails, uv will search for credentials. If credentials are\nfound, an authenticated request will be attempted.", "type": "string", - "enum": [ - "auto" - ] + "const": "auto" }, { - "description": "Always authenticate.\n\nIf credentials are not provided, uv will eagerly search for credentials. If credentials cannot be found, uv will error instead of attempting an unauthenticated request.", + "description": "Always authenticate.\n\nIf credentials are not provided, uv will eagerly search for credentials. If credentials\ncannot be found, uv will error instead of attempting an unauthenticated request.", "type": "string", - "enum": [ - "always" - ] + "const": "always" }, { "description": "Never authenticate.\n\nIf credentials are provided, uv will error. uv will not search for credentials.", "type": "string", - "enum": [ - "never" - ] + "const": "never" } ] }, "BuildBackendSettings": { - "description": "Settings for the uv build backend (`uv_build`).\n\n!!! note\n\nThe uv build backend is currently in preview and may change in any future release.\n\nNote that those settings only apply when using the `uv_build` backend, other build backends (such as hatchling) have their own configuration.\n\nAll options that accept globs use the portable glob patterns from [PEP 639](https://packaging.python.org/en/latest/specifications/glob-patterns/).", + "description": "Settings for the uv build backend (`uv_build`).\n\n!!! note\n\n The uv build backend is currently in preview and may change in any future release.\n\nNote that those settings only apply when using the `uv_build` backend, other build backends\n(such as hatchling) have their own configuration.\n\nAll options that accept globs use the portable glob patterns from\n[PEP 639](https://packaging.python.org/en/latest/specifications/glob-patterns/).", "type": "object", "properties": { "data": { - "description": "Data includes for wheels.\n\nEach entry is a directory, whose contents are copied to the matching directory in the wheel in `-.data/(purelib|platlib|headers|scripts|data)`. Upon installation, this data is moved to its target location, as defined by . Usually, small data files are included by placing them in the Python module instead of using data includes.\n\n- `scripts`: Installed to the directory for executables, `/bin` on Unix or `\\Scripts` on Windows. This directory is added to `PATH` when the virtual environment is activated or when using `uv run`, so this data type can be used to install additional binaries. Consider using `project.scripts` instead for Python entrypoints. - `data`: Installed over the virtualenv environment root.\n\nWarning: This may override existing files!\n\n- `headers`: Installed to the include directory. Compilers building Python packages with this package as build requirement use the include directory to find additional header files. - `purelib` and `platlib`: Installed to the `site-packages` directory. It is not recommended to uses these two options.", + "description": "Data includes for wheels.\n\nEach entry is a directory, whose contents are copied to the matching directory in the wheel\nin `-.data/(purelib|platlib|headers|scripts|data)`. Upon installation, this\ndata is moved to its target location, as defined by\n. Usually, small\ndata files are included by placing them in the Python module instead of using data includes.\n\n- `scripts`: Installed to the directory for executables, `/bin` on Unix or\n `\\Scripts` on Windows. This directory is added to `PATH` when the virtual\n environment is activated or when using `uv run`, so this data type can be used to install\n additional binaries. Consider using `project.scripts` instead for Python entrypoints.\n- `data`: Installed over the virtualenv environment root.\n\n Warning: This may override existing files!\n\n- `headers`: Installed to the include directory. Compilers building Python packages\n with this package as build requirement use the include directory to find additional header\n files.\n- `purelib` and `platlib`: Installed to the `site-packages` directory. It is not recommended\n to uses these two options.", + "allOf": [ + { + "$ref": "#/definitions/WheelDataIncludes" + } + ], "default": { "data": null, "headers": null, "platlib": null, "purelib": null, "scripts": null - }, - "allOf": [ - { - "$ref": "#/definitions/WheelDataIncludes" - } - ] + } }, "default-excludes": { "description": "If set to `false`, the default excludes aren't applied.\n\nDefault excludes: `__pycache__`, `*.pyc`, and `*.pyo`.", - "default": true, - "type": "boolean" + "type": "boolean", + "default": true }, "module-name": { - "description": "The name of the module directory inside `module-root`.\n\nThe default module name is the package name with dots and dashes replaced by underscores.\n\nPackage names need to be valid Python identifiers, and the directory needs to contain a `__init__.py`. An exception are stubs packages, whose name ends with `-stubs`, with the stem being the module name, and which contain a `__init__.pyi` file.\n\nFor namespace packages with a single module, the path can be dotted, e.g., `foo.bar` or `foo-stubs.bar`.\n\nNote that using this option runs the risk of creating two packages with different names but the same module names. Installing such packages together leads to unspecified behavior, often with corrupted files or directory trees.", - "default": null, + "description": "The name of the module directory inside `module-root`.\n\nThe default module name is the package name with dots and dashes replaced by underscores.\n\nPackage names need to be valid Python identifiers, and the directory needs to contain a\n`__init__.py`. An exception are stubs packages, whose name ends with `-stubs`, with the stem\nbeing the module name, and which contain a `__init__.pyi` file.\n\nFor namespace packages with a single module, the path can be dotted, e.g., `foo.bar` or\n`foo-stubs.bar`.\n\nNote that using this option runs the risk of creating two packages with different names but\nthe same module names. Installing such packages together leads to unspecified behavior,\noften with corrupted files or directory trees.", "type": [ "string", "null" - ] + ], + "default": null }, "module-root": { "description": "The directory that contains the module directory.\n\nCommon values are `src` (src layout, the default) or an empty path (flat layout).", - "default": "src", - "type": "string" + "type": "string", + "default": "src" }, "namespace": { - "description": "Build a namespace package.\n\nBuild a PEP 420 implicit namespace package, allowing more than one root `__init__.py`.\n\nUse this option when the namespace package contains multiple root `__init__.py`, for namespace packages with a single root `__init__.py` use a dotted `module-name` instead.\n\nTo compare dotted `module-name` and `namespace = true`, the first example below can be expressed with `module-name = \"cloud.database\"`: There is one root `__init__.py` `database`. In the second example, we have three roots (`cloud.database`, `cloud.database_pro`, `billing.modules.database_pro`), so `namespace = true` is required.\n\n```text src └── cloud └── database ├── __init__.py ├── query_builder │ └── __init__.py └── sql ├── parser.py └── __init__.py ```\n\n```text src ├── cloud │ ├── database │ │ ├── __init__.py │ │ ├── query_builder │ │ │ └── __init__.py │ │ └── sql │ │ ├── __init__.py │ │ └── parser.py │ └── database_pro │ ├── __init__.py │ └── query_builder.py └── billing └── modules └── database_pro ├── __init__.py └── sql.py ```", - "default": false, - "type": "boolean" + "description": "Build a namespace package.\n\nBuild a PEP 420 implicit namespace package, allowing more than one root `__init__.py`.\n\nUse this option when the namespace package contains multiple root `__init__.py`, for\nnamespace packages with a single root `__init__.py` use a dotted `module-name` instead.\n\nTo compare dotted `module-name` and `namespace = true`, the first example below can be\nexpressed with `module-name = \"cloud.database\"`: There is one root `__init__.py` `database`.\nIn the second example, we have three roots (`cloud.database`, `cloud.database_pro`,\n`billing.modules.database_pro`), so `namespace = true` is required.\n\n```text\nsrc\n└── cloud\n └── database\n ├── __init__.py\n ├── query_builder\n │ └── __init__.py\n └── sql\n ├── parser.py\n └── __init__.py\n```\n\n```text\nsrc\n├── cloud\n│ ├── database\n│ │ ├── __init__.py\n│ │ ├── query_builder\n│ │ │ └── __init__.py\n│ │ └── sql\n│ │ ├── __init__.py\n│ │ └── parser.py\n│ └── database_pro\n│ ├── __init__.py\n│ └── query_builder.py\n└── billing\n └── modules\n └── database_pro\n ├── __init__.py\n └── sql.py\n```", + "type": "boolean", + "default": false }, "source-exclude": { "description": "Glob expressions which files and directories to exclude from the source distribution.", - "default": [], "type": "array", + "default": [], "items": { "type": "string" } }, "source-include": { - "description": "Glob expressions which files and directories to additionally include in the source distribution.\n\n`pyproject.toml` and the contents of the module directory are always included.", - "default": [], + "description": "Glob expressions which files and directories to additionally include in the source\ndistribution.\n\n`pyproject.toml` and the contents of the module directory are always included.", "type": "array", + "default": [], "items": { "type": "string" } }, "wheel-exclude": { "description": "Glob expressions which files and directories to exclude from the wheel.", - "default": [], "type": "array", + "default": [], "items": { "type": "string" } @@ -734,54 +720,54 @@ { "description": "Ex) `{ file = \"Cargo.lock\" }` or `{ file = \"**/*.toml\" }`", "type": "object", - "required": [ - "file" - ], "properties": { "file": { "type": "string" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "file" + ] }, { "description": "Ex) `{ dir = \"src\" }`", "type": "object", - "required": [ - "dir" - ], "properties": { "dir": { "type": "string" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "dir" + ] }, { "description": "Ex) `{ git = true }` or `{ git = { commit = true, tags = false } }`", "type": "object", - "required": [ - "git" - ], "properties": { "git": { "$ref": "#/definitions/GitPattern" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "git" + ] }, { "description": "Ex) `{ env = \"UV_CACHE_INFO\" }`", "type": "object", - "required": [ - "env" - ], "properties": { "env": { "type": "string" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "env" + ] } ] }, @@ -801,7 +787,7 @@ ] }, "ConfigSettings": { - "description": "Settings to pass to a PEP 517 build backend, structured as a map from (string) key to string or list of strings.\n\nSee: ", + "description": "Settings to pass to a PEP 517 build backend, structured as a map from (string) key to string or\nlist of strings.\n\nSee: ", "type": "object", "additionalProperties": { "$ref": "#/definitions/ConfigSettingValue" @@ -813,16 +799,11 @@ { "description": "All groups are defaulted", "type": "string", - "enum": [ - "All" - ] + "const": "All" }, { "description": "A list of groups", "type": "object", - "required": [ - "List" - ], "properties": { "List": { "type": "array", @@ -831,7 +812,10 @@ } } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "List" + ] } ] }, @@ -847,30 +831,31 @@ } } }, + "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```", + "type": "string", + "format": "uri" + }, "ExcludeNewer": { "description": "Exclude distributions uploaded after the given timestamp.\n\nAccepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and local dates in the same format (e.g., `2006-12-02`).", "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(Z|[+-]\\d{2}:\\d{2}))?$" }, "ExtraName": { - "description": "The normalized name of an extra dependency.\n\nConverts the name to lowercase and collapses runs of `-`, `_`, and `.` down to a single `-`. For example, `---`, `.`, and `__` are all converted to a single `-`.\n\nSee: - - ", + "description": "The normalized name of an extra dependency.\n\nConverts the name to lowercase and collapses runs of `-`, `_`, and `.` down to a single `-`.\nFor example, `---`, `.`, and `__` are all converted to a single `-`.\n\nSee:\n- \n- ", "type": "string" }, "ForkStrategy": { "oneOf": [ { - "description": "Optimize for selecting the fewest number of versions for each package. Older versions may be preferred if they are compatible with a wider range of supported Python versions or platforms.", + "description": "Optimize for selecting the fewest number of versions for each package. Older versions may\nbe preferred if they are compatible with a wider range of supported Python versions or\nplatforms.", "type": "string", - "enum": [ - "fewest" - ] + "const": "fewest" }, { - "description": "Optimize for selecting latest supported version of each package, for each supported Python version.", + "description": "Optimize for selecting latest supported version of each package, for each supported Python\nversion.", "type": "string", - "enum": [ - "requires-python" - ] + "const": "requires-python" } ] }, @@ -903,56 +888,53 @@ "additionalProperties": false }, "GroupName": { - "description": "The normalized name of a dependency group.\n\nSee: - - ", + "description": "The normalized name of a dependency group.\n\nSee:\n- \n- ", "type": "string" }, "Index": { "type": "object", - "required": [ - "url" - ], "properties": { - "authenticate": { - "description": "When uv should use authentication for requests to the index.\n\n```toml [[tool.uv.index]] name = \"my-index\" url = \"https:///simple\" authenticate = \"always\" ```", - "default": "auto", - "allOf": [ - { - "$ref": "#/definitions/AuthPolicy" - } - ] - }, - "default": { - "description": "Mark the index as the default index.\n\nBy default, uv uses PyPI as the default index, such that even if additional indexes are defined via `[[tool.uv.index]]`, PyPI will still be used as a fallback for packages that aren't found elsewhere. To disable the PyPI default, set `default = true` on at least one other index.\n\nMarking an index as default will move it to the front of the list of indexes, such that it is given the highest priority when resolving packages.", - "default": false, - "type": "boolean" - }, - "explicit": { - "description": "Mark the index as explicit.\n\nExplicit indexes will _only_ be used when explicitly requested via a `[tool.uv.sources]` definition, as in:\n\n```toml [[tool.uv.index]] name = \"pytorch\" url = \"https://download.pytorch.org/whl/cu121\" explicit = true\n\n[tool.uv.sources] torch = { index = \"pytorch\" } ```", - "default": false, - "type": "boolean" - }, "format": { - "description": "The format used by the index.\n\nIndexes can either be PEP 503-compliant (i.e., a PyPI-style registry implementing the Simple API) or structured as a flat list of distributions (e.g., `--find-links`). In both cases, indexes can point to either local or remote resources.", - "default": "simple", + "description": "The format used by the index.\n\nIndexes can either be PEP 503-compliant (i.e., a PyPI-style registry implementing the Simple\nAPI) or structured as a flat list of distributions (e.g., `--find-links`). In both cases,\nindexes can point to either local or remote resources.", "allOf": [ { "$ref": "#/definitions/IndexFormat" } - ] + ], + "default": "simple" + }, + "authenticate": { + "description": "When uv should use authentication for requests to the index.\n\n```toml\n[[tool.uv.index]]\nname = \"my-index\"\nurl = \"https:///simple\"\nauthenticate = \"always\"\n```", + "allOf": [ + { + "$ref": "#/definitions/AuthPolicy" + } + ], + "default": "auto" + }, + "default": { + "description": "Mark the index as the default index.\n\nBy default, uv uses PyPI as the default index, such that even if additional indexes are\ndefined via `[[tool.uv.index]]`, PyPI will still be used as a fallback for packages that\naren't found elsewhere. To disable the PyPI default, set `default = true` on at least one\nother index.\n\nMarking an index as default will move it to the front of the list of indexes, such that it\nis given the highest priority when resolving packages.", + "type": "boolean", + "default": false + }, + "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```", + "type": "boolean", + "default": false }, "ignore-error-codes": { - "description": "Status codes that uv should ignore when deciding whether to continue searching in the next index after a failure.\n\n```toml [[tool.uv.index]] name = \"my-index\" url = \"https:///simple\" ignore-error-codes = [401, 403] ```", - "default": null, + "description": "Status codes that uv should ignore when deciding whether\nto continue searching in the next index after a failure.\n\n```toml\n[[tool.uv.index]]\nname = \"my-index\"\nurl = \"https:///simple\"\nignore-error-codes = [401, 403]\n```", "type": [ "array", "null" ], + "default": null, "items": { "$ref": "#/definitions/StatusCode" } }, "name": { - "description": "The name of the index.\n\nIndex names can be used to reference indexes elsewhere in the configuration. For example, you can pin a package to a specific index by name:\n\n```toml [[tool.uv.index]] name = \"pytorch\" url = \"https://download.pytorch.org/whl/cu121\"\n\n[tool.uv.sources] torch = { index = \"pytorch\" } ```", + "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```", "anyOf": [ { "$ref": "#/definitions/IndexName" @@ -963,12 +945,15 @@ ] }, "publish-url": { - "description": "The URL of the upload endpoint.\n\nWhen using `uv publish --index `, this URL is used for publishing.\n\nA configuration for the default index PyPI would look as follows:\n\n```toml [[tool.uv.index]] name = \"pypi\" url = \"https://pypi.org/simple\" publish-url = \"https://upload.pypi.org/legacy/\" ```", - "type": [ - "string", - "null" - ], - "format": "uri" + "description": "The URL of the upload endpoint.\n\nWhen using `uv publish --index `, this URL is used for publishing.\n\nA configuration for the default index PyPI would look as follows:\n\n```toml\n[[tool.uv.index]]\nname = \"pypi\"\nurl = \"https://pypi.org/simple\"\npublish-url = \"https://upload.pypi.org/legacy/\"\n```", + "anyOf": [ + { + "$ref": "#/definitions/DisplaySafeUrl" + }, + { + "type": "null" + } + ] }, "url": { "description": "The URL of the index.\n\nExpects to receive a URL (e.g., `https://pypi.org/simple`) or a local path.", @@ -978,23 +963,22 @@ } ] } - } + }, + "required": [ + "url" + ] }, "IndexFormat": { "oneOf": [ { "description": "A PyPI-style index implementing the Simple Repository API.", "type": "string", - "enum": [ - "simple" - ] + "const": "simple" }, { "description": "A `--find-links`-style index containing a flat list of wheels and source distributions.", "type": "string", - "enum": [ - "flat" - ] + "const": "flat" } ] }, @@ -1005,25 +989,19 @@ "IndexStrategy": { "oneOf": [ { - "description": "Only use results from the first index that returns a match for a given package name.\n\nWhile this differs from pip's behavior, it's the default index strategy as it's the most secure.", + "description": "Only use results from the first index that returns a match for a given package name.\n\nWhile this differs from pip's behavior, it's the default index strategy as it's the most\nsecure.", "type": "string", - "enum": [ - "first-index" - ] + "const": "first-index" }, { - "description": "Search for every package name across all indexes, exhausting the versions from the first index before moving on to the next.\n\nIn this strategy, we look for every package across all indexes. When resolving, we attempt to use versions from the indexes in order, such that we exhaust all available versions from the first index before moving on to the next. Further, if a version is found to be incompatible in the first index, we do not reconsider that version in subsequent indexes, even if the secondary index might contain compatible versions (e.g., variants of the same versions with different ABI tags or Python version constraints).\n\nSee: ", + "description": "Search for every package name across all indexes, exhausting the versions from the first\nindex before moving on to the next.\n\nIn this strategy, we look for every package across all indexes. When resolving, we attempt\nto use versions from the indexes in order, such that we exhaust all available versions from\nthe first index before moving on to the next. Further, if a version is found to be\nincompatible in the first index, we do not reconsider that version in subsequent indexes,\neven if the secondary index might contain compatible versions (e.g., variants of the same\nversions with different ABI tags or Python version constraints).\n\nSee: ", "type": "string", - "enum": [ - "unsafe-first-match" - ] + "const": "unsafe-first-match" }, { - "description": "Search for every package name across all indexes, preferring the \"best\" version found. If a package version is in multiple indexes, only look at the entry for the first index.\n\nIn this strategy, we look for every package across all indexes. When resolving, we consider all versions from all indexes, choosing the \"best\" version found (typically, the highest compatible version).\n\nThis most closely matches pip's behavior, but exposes the resolver to \"dependency confusion\" attacks whereby malicious actors can publish packages to public indexes with the same name as internal packages, causing the resolver to install the malicious package in lieu of the intended internal package.\n\nSee: ", + "description": "Search for every package name across all indexes, preferring the \"best\" version found. If a\npackage version is in multiple indexes, only look at the entry for the first index.\n\nIn this strategy, we look for every package across all indexes. When resolving, we consider\nall versions from all indexes, choosing the \"best\" version found (typically, the highest\ncompatible version).\n\nThis most closely matches pip's behavior, but exposes the resolver to \"dependency confusion\"\nattacks whereby malicious actors can publish packages to public indexes with the same name\nas internal packages, causing the resolver to install the malicious package in lieu of\nthe intended internal package.\n\nSee: ", "type": "string", - "enum": [ - "unsafe-best-match" - ] + "const": "unsafe-best-match" } ] }, @@ -1037,16 +1015,12 @@ { "description": "Do not use keyring for credential lookup.", "type": "string", - "enum": [ - "disabled" - ] + "const": "disabled" }, { "description": "Use the `keyring` command for credential lookup.", "type": "string", - "enum": [ - "subprocess" - ] + "const": "subprocess" } ] }, @@ -1055,30 +1029,22 @@ { "description": "Clone (i.e., copy-on-write) packages from the wheel into the `site-packages` directory.", "type": "string", - "enum": [ - "clone" - ] + "const": "clone" }, { "description": "Copy packages from the wheel into the `site-packages` directory.", "type": "string", - "enum": [ - "copy" - ] + "const": "copy" }, { "description": "Hard link packages from the wheel into the `site-packages` directory.", "type": "string", - "enum": [ - "hardlink" - ] + "const": "hardlink" }, { - "description": "Symbolically link packages from the wheel into the `site-packages` directory.\n\nWARNING: The use of symlinks is discouraged, as they create tight coupling between the cache and the target environment. For example, clearing the cache (`uv cache clear`) will break all installed packages by way of removing the underlying source files. Use symlinks with caution.", + "description": "Symbolically link packages from the wheel into the `site-packages` directory.\n\nWARNING: The use of symlinks is discouraged, as they create tight coupling between the\ncache and the target environment. For example, clearing the cache (`uv cache clear`) will\nbreak all installed packages by way of removing the underlying source files. Use symlinks\nwith caution.", "type": "string", - "enum": [ - "symlink" - ] + "const": "symlink" } ] }, @@ -1087,7 +1053,7 @@ "type": "string" }, "PackageName": { - "description": "The normalized name of a package.\n\nConverts the name to lowercase and collapses runs of `-`, `_`, and `.` down to a single `-`. For example, `---`, `.`, and `__` are all converted to a single `-`.\n\nSee: ", + "description": "The normalized name of a package.\n\nConverts the name to lowercase and collapses runs of `-`, `_`, and `.` down to a single `-`.\nFor example, `---`, `.`, and `__` are all converted to a single `-`.\n\nSee: ", "type": "string" }, "PackageNameSpecifier": { @@ -1096,11 +1062,8 @@ "pattern": "^(:none:|:all:|([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9]))$" }, "PipGroupName": { - "description": "The pip-compatible variant of a [`GroupName`].\n\nEither or :. If is omitted it defaults to \"pyproject.toml\".", + "description": "The pip-compatible variant of a [`GroupName`].\n\nEither or :.\nIf is omitted it defaults to \"pyproject.toml\".", "type": "object", - "required": [ - "name" - ], "properties": { "name": { "$ref": "#/definitions/GroupName" @@ -1111,10 +1074,13 @@ "null" ] } - } + }, + "required": [ + "name" + ] }, "PipOptions": { - "description": "Settings that are specific to the `uv pip` command-line interface.\n\nThese values will be ignored when running commands outside the `uv pip` namespace (e.g., `uv lock`, `uvx`).", + "description": "Settings that are specific to the `uv pip` command-line interface.\n\nThese values will be ignored when running commands outside the `uv pip` namespace (e.g.,\n`uv lock`, `uvx`).", "type": "object", "properties": { "all-extras": { @@ -1125,14 +1091,14 @@ ] }, "allow-empty-requirements": { - "description": "Allow `uv pip sync` with empty requirements, which will clear the environment of all packages.", + "description": "Allow `uv pip sync` with empty requirements, which will clear the environment of all\npackages.", "type": [ "boolean", "null" ] }, "annotation-style": { - "description": "The style of the annotation comments included in the output file, used to indicate the source of each package.", + "description": "The style of the annotation comments included in the output file, used to indicate the\nsource of each package.", "anyOf": [ { "$ref": "#/definitions/AnnotationStyle" @@ -1143,21 +1109,21 @@ ] }, "break-system-packages": { - "description": "Allow uv to modify an `EXTERNALLY-MANAGED` Python installation.\n\nWARNING: `--break-system-packages` is intended for use in continuous integration (CI) environments, when installing into Python installations that are managed by an external package manager, like `apt`. It should be used with caution, as such Python installations explicitly recommend against modifications by other package managers (like uv or pip).", + "description": "Allow uv to modify an `EXTERNALLY-MANAGED` Python installation.\n\nWARNING: `--break-system-packages` is intended for use in continuous integration (CI)\nenvironments, when installing into Python installations that are managed by an external\npackage manager, like `apt`. It should be used with caution, as such Python installations\nexplicitly recommend against modifications by other package managers (like uv or pip).", "type": [ "boolean", "null" ] }, "compile-bytecode": { - "description": "Compile Python files to bytecode after installation.\n\nBy default, uv does not compile Python (`.py`) files to bytecode (`__pycache__/*.pyc`); instead, compilation is performed lazily the first time a module is imported. For use-cases in which start time is critical, such as CLI applications and Docker containers, this option can be enabled to trade longer installation times for faster start times.\n\nWhen enabled, uv will process the entire site-packages directory (including packages that are not being modified by the current operation) for consistency. Like pip, it will also ignore errors.", + "description": "Compile Python files to bytecode after installation.\n\nBy default, uv does not compile Python (`.py`) files to bytecode (`__pycache__/*.pyc`);\ninstead, compilation is performed lazily the first time a module is imported. For use-cases\nin which start time is critical, such as CLI applications and Docker containers, this option\ncan be enabled to trade longer installation times for faster start times.\n\nWhen enabled, uv will process the entire site-packages directory (including packages that\nare not being modified by the current operation) for consistency. Like pip, it will also\nignore errors.", "type": [ "boolean", "null" ] }, "config-settings": { - "description": "Settings to pass to the [PEP 517](https://peps.python.org/pep-0517/) build backend, specified as `KEY=VALUE` pairs.", + "description": "Settings to pass to the [PEP 517](https://peps.python.org/pep-0517/) build backend,\nspecified as `KEY=VALUE` pairs.", "anyOf": [ { "$ref": "#/definitions/ConfigSettings" @@ -1175,7 +1141,7 @@ ] }, "dependency-metadata": { - "description": "Pre-defined static metadata for dependencies of the project (direct or transitive). When provided, enables the resolver to use the specified metadata instead of querying the registry or building the relevant package from source.\n\nMetadata should be provided in adherence with the [Metadata 2.3](https://packaging.python.org/en/latest/specifications/core-metadata/) standard, though only the following fields are respected:\n\n- `name`: The name of the package. - (Optional) `version`: The version of the package. If omitted, the metadata will be applied to all versions of the package. - (Optional) `requires-dist`: The dependencies of the package (e.g., `werkzeug>=0.14`). - (Optional) `requires-python`: The Python version required by the package (e.g., `>=3.10`). - (Optional) `provides-extras`: The extras provided by the package.", + "description": "Pre-defined static metadata for dependencies of the project (direct or transitive). When\nprovided, enables the resolver to use the specified metadata instead of querying the\nregistry or building the relevant package from source.\n\nMetadata should be provided in adherence with the [Metadata 2.3](https://packaging.python.org/en/latest/specifications/core-metadata/)\nstandard, though only the following fields are respected:\n\n- `name`: The name of the package.\n- (Optional) `version`: The version of the package. If omitted, the metadata will be applied\n to all versions of the package.\n- (Optional) `requires-dist`: The dependencies of the package (e.g., `werkzeug>=0.14`).\n- (Optional) `requires-python`: The Python version required by the package (e.g., `>=3.10`).\n- (Optional) `provides-extras`: The extras provided by the package.", "type": [ "array", "null" @@ -1199,7 +1165,7 @@ ] }, "emit-index-annotation": { - "description": "Include comment annotations indicating the index used to resolve each package (e.g., `# from https://pypi.org/simple`).", + "description": "Include comment annotations indicating the index used to resolve each package (e.g.,\n`# from https://pypi.org/simple`).", "type": [ "boolean", "null" @@ -1213,14 +1179,14 @@ ] }, "emit-marker-expression": { - "description": "Whether to emit a marker string indicating the conditions under which the set of pinned dependencies is valid.\n\nThe pinned dependencies may be valid even when the marker expression is false, but when the expression is true, the requirements are known to be correct.", + "description": "Whether to emit a marker string indicating the conditions under which the set of pinned\ndependencies is valid.\n\nThe pinned dependencies may be valid even when the marker expression is\nfalse, but when the expression is true, the requirements are known to\nbe correct.", "type": [ "boolean", "null" ] }, "exclude-newer": { - "description": "Limit candidate packages to those that were uploaded prior to a given point in time.\n\nAccepts a superset of [RFC 3339](https://www.rfc-editor.org/rfc/rfc3339.html) (e.g., `2006-12-02T02:07:43Z`). A full timestamp is required to ensure that the resolver will behave consistently across timezones.", + "description": "Limit candidate packages to those that were uploaded prior to a given point in time.\n\nAccepts a superset of [RFC 3339](https://www.rfc-editor.org/rfc/rfc3339.html) (e.g.,\n`2006-12-02T02:07:43Z`). A full timestamp is required to ensure that the resolver will\nbehave consistently across timezones.", "anyOf": [ { "$ref": "#/definitions/ExcludeNewer" @@ -1241,7 +1207,7 @@ } }, "extra-index-url": { - "description": "Extra URLs of package indexes to use, in addition to `--index-url`.\n\nAccepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/) (the simple repository API), or a local directory laid out in the same format.\n\nAll indexes provided via this flag take priority over the index specified by [`index_url`](#index-url). When multiple indexes are provided, earlier values take priority.\n\nTo control uv's resolution strategy when multiple indexes are present, see [`index_strategy`](#index-strategy).", + "description": "Extra URLs of package indexes to use, in addition to `--index-url`.\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\nAll indexes provided via this flag take priority over the index specified by\n[`index_url`](#index-url). When multiple indexes are provided, earlier values take priority.\n\nTo control uv's resolution strategy when multiple indexes are present, see\n[`index_strategy`](#index-strategy).", "type": [ "array", "null" @@ -1251,7 +1217,7 @@ } }, "find-links": { - "description": "Locations to search for candidate distributions, in addition to those found in the registry indexes.\n\nIf a path, the target must be a directory that contains packages as wheel files (`.whl`) or source distributions (e.g., `.tar.gz` or `.zip`) at the top level.\n\nIf a URL, the page must contain a flat list of links to package files adhering to the formats described above.", + "description": "Locations to search for candidate distributions, in addition to those found in the registry\nindexes.\n\nIf a path, the target must be a directory that contains packages as wheel files (`.whl`) or\nsource distributions (e.g., `.tar.gz` or `.zip`) at the top level.\n\nIf a URL, the page must contain a flat list of links to package files adhering to the\nformats described above.", "type": [ "array", "null" @@ -1261,7 +1227,7 @@ } }, "fork-strategy": { - "description": "The strategy to use when selecting multiple versions of a given package across Python versions and platforms.\n\nBy default, uv will optimize for selecting the latest version of each package for each supported Python version (`requires-python`), while minimizing the number of selected versions across platforms.\n\nUnder `fewest`, uv will minimize the number of selected versions for each package, preferring older versions that are compatible with a wider range of supported Python versions or platforms.", + "description": "The strategy to use when selecting multiple versions of a given package across Python\nversions and platforms.\n\nBy default, uv will optimize for selecting the latest version of each package for each\nsupported Python version (`requires-python`), while minimizing the number of selected\nversions across platforms.\n\nUnder `fewest`, uv will minimize the number of selected versions for each package,\npreferring older versions that are compatible with a wider range of supported Python\nversions or platforms.", "anyOf": [ { "$ref": "#/definitions/ForkStrategy" @@ -1289,7 +1255,7 @@ } }, "index-strategy": { - "description": "The strategy to use when resolving against multiple index URLs.\n\nBy default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (`first-index`). This prevents \"dependency confusion\" attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.", + "description": "The strategy to use when resolving against multiple index URLs.\n\nBy default, uv will stop at the first index on which a given package is available, and\nlimit resolutions to those present on that first index (`first-index`). This prevents\n\"dependency confusion\" attacks, whereby an attacker can upload a malicious package under the\nsame name to an alternate index.", "anyOf": [ { "$ref": "#/definitions/IndexStrategy" @@ -1300,7 +1266,7 @@ ] }, "index-url": { - "description": "The URL of the Python package index (by default: ).\n\nAccepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/) (the simple repository API), or a local directory laid out in the same format.\n\nThe index provided by this setting is given lower priority than any indexes specified via [`extra_index_url`](#extra-index-url).", + "description": "The URL of the Python package index (by default: ).\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\nThe index provided by this setting is given lower priority than any indexes specified via\n[`extra_index_url`](#extra-index-url).", "anyOf": [ { "$ref": "#/definitions/IndexUrl" @@ -1311,7 +1277,7 @@ ] }, "keyring-provider": { - "description": "Attempt to use `keyring` for authentication for index URLs.\n\nAt present, only `--keyring-provider subprocess` is supported, which configures uv to use the `keyring` CLI to handle authentication.", + "description": "Attempt to use `keyring` for authentication for index URLs.\n\nAt present, only `--keyring-provider subprocess` is supported, which configures uv to\nuse the `keyring` CLI to handle authentication.", "anyOf": [ { "$ref": "#/definitions/KeyringProviderType" @@ -1322,7 +1288,7 @@ ] }, "link-mode": { - "description": "The method to use when installing packages from the global cache.\n\nDefaults to `clone` (also known as Copy-on-Write) on macOS, and `hardlink` on Linux and Windows.", + "description": "The method to use when installing packages from the global cache.\n\nDefaults to `clone` (also known as Copy-on-Write) on macOS, and `hardlink` on Linux and\nWindows.", "anyOf": [ { "$ref": "#/definitions/LinkMode" @@ -1333,14 +1299,14 @@ ] }, "no-annotate": { - "description": "Exclude comment annotations indicating the source of each package from the output file generated by `uv pip compile`.", + "description": "Exclude comment annotations indicating the source of each package from the output file\ngenerated by `uv pip compile`.", "type": [ "boolean", "null" ] }, "no-binary": { - "description": "Don't install pre-built wheels.\n\nThe given packages will be built and installed from source. The resolver will still use pre-built wheels to extract package metadata, if available.\n\nMultiple packages may be provided. Disable binaries for all packages with `:all:`. Clear previously specified packages with `:none:`.", + "description": "Don't install pre-built wheels.\n\nThe given packages will be built and installed from source. The resolver will still use\npre-built wheels to extract package metadata, if available.\n\nMultiple packages may be provided. Disable binaries for all packages with `:all:`.\nClear previously specified packages with `:none:`.", "type": [ "array", "null" @@ -1350,21 +1316,21 @@ } }, "no-build": { - "description": "Don't build source distributions.\n\nWhen enabled, resolving will not run arbitrary Python code. The cached wheels of already-built source distributions will be reused, but operations that require building distributions will exit with an error.\n\nAlias for `--only-binary :all:`.", + "description": "Don't build source distributions.\n\nWhen enabled, resolving will not run arbitrary Python code. The cached wheels of\nalready-built source distributions will be reused, but operations that require building\ndistributions will exit with an error.\n\nAlias for `--only-binary :all:`.", "type": [ "boolean", "null" ] }, "no-build-isolation": { - "description": "Disable isolation when building source distributions.\n\nAssumes that build dependencies specified by [PEP 518](https://peps.python.org/pep-0518/) are already installed.", + "description": "Disable isolation when building source distributions.\n\nAssumes that build dependencies specified by [PEP 518](https://peps.python.org/pep-0518/)\nare already installed.", "type": [ "boolean", "null" ] }, "no-build-isolation-package": { - "description": "Disable isolation when building source distributions for a specific package.\n\nAssumes that the packages' build dependencies specified by [PEP 518](https://peps.python.org/pep-0518/) are already installed.", + "description": "Disable isolation when building source distributions for a specific package.\n\nAssumes that the packages' build dependencies specified by [PEP 518](https://peps.python.org/pep-0518/)\nare already installed.", "type": [ "array", "null" @@ -1374,14 +1340,14 @@ } }, "no-deps": { - "description": "Ignore package dependencies, instead only add those packages explicitly listed on the command line to the resulting requirements file.", + "description": "Ignore package dependencies, instead only add those packages explicitly listed\non the command line to the resulting requirements file.", "type": [ "boolean", "null" ] }, "no-emit-package": { - "description": "Specify a package to omit from the output resolution. Its dependencies will still be included in the resolution. Equivalent to pip-compile's `--unsafe-package` option.", + "description": "Specify a package to omit from the output resolution. Its dependencies will still be\nincluded in the resolution. Equivalent to pip-compile's `--unsafe-package` option.", "type": [ "array", "null" @@ -1408,35 +1374,35 @@ ] }, "no-index": { - "description": "Ignore all registry indexes (e.g., PyPI), instead relying on direct URL dependencies and those provided via `--find-links`.", + "description": "Ignore all registry indexes (e.g., PyPI), instead relying on direct URL dependencies and\nthose provided via `--find-links`.", "type": [ "boolean", "null" ] }, "no-sources": { - "description": "Ignore the `tool.uv.sources` table when resolving dependencies. Used to lock against the standards-compliant, publishable package metadata, as opposed to using any local or Git sources.", + "description": "Ignore the `tool.uv.sources` table when resolving dependencies. Used to lock against the\nstandards-compliant, publishable package metadata, as opposed to using any local or Git\nsources.", "type": [ "boolean", "null" ] }, "no-strip-extras": { - "description": "Include extras in the output file.\n\nBy default, uv strips extras, as any packages pulled in by the extras are already included as dependencies in the output file directly. Further, output files generated with `--no-strip-extras` cannot be used as constraints files in `install` and `sync` invocations.", + "description": "Include extras in the output file.\n\nBy default, uv strips extras, as any packages pulled in by the extras are already included\nas dependencies in the output file directly. Further, output files generated with\n`--no-strip-extras` cannot be used as constraints files in `install` and `sync` invocations.", "type": [ "boolean", "null" ] }, "no-strip-markers": { - "description": "Include environment markers in the output file generated by `uv pip compile`.\n\nBy default, uv strips environment markers, as the resolution generated by `compile` is only guaranteed to be correct for the target environment.", + "description": "Include environment markers in the output file generated by `uv pip compile`.\n\nBy default, uv strips environment markers, as the resolution generated by `compile` is\nonly guaranteed to be correct for the target environment.", "type": [ "boolean", "null" ] }, "only-binary": { - "description": "Only use pre-built wheels; don't build source distributions.\n\nWhen enabled, resolving will not run code from the given packages. The cached wheels of already-built source distributions will be reused, but operations that require building distributions will exit with an error.\n\nMultiple packages may be provided. Disable binaries for all packages with `:all:`. Clear previously specified packages with `:none:`.", + "description": "Only use pre-built wheels; don't build source distributions.\n\nWhen enabled, resolving will not run code from the given packages. The cached wheels of already-built\nsource distributions will be reused, but operations that require building distributions will\nexit with an error.\n\nMultiple packages may be provided. Disable binaries for all packages with `:all:`.\nClear previously specified packages with `:none:`.", "type": [ "array", "null" @@ -1446,21 +1412,21 @@ } }, "output-file": { - "description": "Write the requirements generated by `uv pip compile` to the given `requirements.txt` file.\n\nIf the file already exists, the existing versions will be preferred when resolving dependencies, unless `--upgrade` is also specified.", + "description": "Write the requirements generated by `uv pip compile` to the given `requirements.txt` file.\n\nIf the file already exists, the existing versions will be preferred when resolving\ndependencies, unless `--upgrade` is also specified.", "type": [ "string", "null" ] }, "prefix": { - "description": "Install packages into `lib`, `bin`, and other top-level folders under the specified directory, as if a virtual environment were present at that location.\n\nIn general, prefer the use of `--python` to install into an alternate environment, as scripts and other artifacts installed via `--prefix` will reference the installing interpreter, rather than any interpreter added to the `--prefix` directory, rendering them non-portable.", + "description": "Install packages into `lib`, `bin`, and other top-level folders under the specified\ndirectory, as if a virtual environment were present at that location.\n\nIn general, prefer the use of `--python` to install into an alternate environment, as\nscripts and other artifacts installed via `--prefix` will reference the installing\ninterpreter, rather than any interpreter added to the `--prefix` directory, rendering them\nnon-portable.", "type": [ "string", "null" ] }, "prerelease": { - "description": "The strategy to use when considering pre-release versions.\n\nBy default, uv will accept pre-releases for packages that _only_ publish pre-releases, along with first-party requirements that contain an explicit pre-release marker in the declared specifiers (`if-necessary-or-explicit`).", + "description": "The strategy to use when considering pre-release versions.\n\nBy default, uv will accept pre-releases for packages that _only_ publish pre-releases,\nalong with first-party requirements that contain an explicit pre-release marker in the\ndeclared specifiers (`if-necessary-or-explicit`).", "anyOf": [ { "$ref": "#/definitions/PrereleaseMode" @@ -1471,14 +1437,14 @@ ] }, "python": { - "description": "The Python interpreter into which packages should be installed.\n\nBy default, uv installs into the virtual environment in the current working directory or any parent directory. The `--python` option allows you to specify a different interpreter, which is intended for use in continuous integration (CI) environments or other automated workflows.\n\nSupported formats: - `3.10` looks for an installed Python 3.10 in the registry on Windows (see `py --list-paths`), or `python3.10` on Linux and macOS. - `python3.10` or `python.exe` looks for a binary with the given name in `PATH`. - `/home/ferris/.local/bin/python3.10` uses the exact Python at the given path.", + "description": "The Python interpreter into which packages should be installed.\n\nBy default, uv installs into the virtual environment in the current working directory or\nany parent directory. The `--python` option allows you to specify a different interpreter,\nwhich is intended for use in continuous integration (CI) environments or other automated\nworkflows.\n\nSupported formats:\n- `3.10` looks for an installed Python 3.10 in the registry on Windows (see\n `py --list-paths`), or `python3.10` on Linux and macOS.\n- `python3.10` or `python.exe` looks for a binary with the given name in `PATH`.\n- `/home/ferris/.local/bin/python3.10` uses the exact Python at the given path.", "type": [ "string", "null" ] }, "python-platform": { - "description": "The platform for which requirements should be resolved.\n\nRepresented as a \"target triple\", a string that describes the target platform in terms of its CPU, vendor, and operating system name, like `x86_64-unknown-linux-gnu` or `aarch64-apple-darwin`.", + "description": "The platform for which requirements should be resolved.\n\nRepresented as a \"target triple\", a string that describes the target platform in terms of\nits CPU, vendor, and operating system name, like `x86_64-unknown-linux-gnu` or\n`aarch64-apple-darwin`.", "anyOf": [ { "$ref": "#/definitions/TargetTriple" @@ -1489,7 +1455,7 @@ ] }, "python-version": { - "description": "The minimum Python version that should be supported by the resolved requirements (e.g., `3.8` or `3.8.17`).\n\nIf a patch version is omitted, the minimum patch version is assumed. For example, `3.8` is mapped to `3.8.0`.", + "description": "The minimum Python version that should be supported by the resolved requirements (e.g.,\n`3.8` or `3.8.17`).\n\nIf a patch version is omitted, the minimum patch version is assumed. For example, `3.8` is\nmapped to `3.8.0`.", "anyOf": [ { "$ref": "#/definitions/PythonVersion" @@ -1507,7 +1473,7 @@ ] }, "reinstall-package": { - "description": "Reinstall a specific package, regardless of whether it's already installed. Implies `refresh-package`.", + "description": "Reinstall a specific package, regardless of whether it's already installed. Implies\n`refresh-package`.", "type": [ "array", "null" @@ -1517,14 +1483,14 @@ } }, "require-hashes": { - "description": "Require a matching hash for each requirement.\n\nHash-checking mode is all or nothing. If enabled, _all_ requirements must be provided with a corresponding hash or set of hashes. Additionally, if enabled, _all_ requirements must either be pinned to exact versions (e.g., `==1.0.0`), or be specified via direct URL.\n\nHash-checking mode introduces a number of additional constraints:\n\n- Git dependencies are not supported. - Editable installations are not supported. - Local dependencies are not supported, unless they point to a specific wheel (`.whl`) or source archive (`.zip`, `.tar.gz`), as opposed to a directory.", + "description": "Require a matching hash for each requirement.\n\nHash-checking mode is all or nothing. If enabled, _all_ requirements must be provided\nwith a corresponding hash or set of hashes. Additionally, if enabled, _all_ requirements\nmust either be pinned to exact versions (e.g., `==1.0.0`), or be specified via direct URL.\n\nHash-checking mode introduces a number of additional constraints:\n\n- Git dependencies are not supported.\n- Editable installations are not supported.\n- Local dependencies are not supported, unless they point to a specific wheel (`.whl`) or\n source archive (`.zip`, `.tar.gz`), as opposed to a directory.", "type": [ "boolean", "null" ] }, "resolution": { - "description": "The strategy to use when selecting between the different compatible versions for a given package requirement.\n\nBy default, uv will use the latest compatible version of each package (`highest`).", + "description": "The strategy to use when selecting between the different compatible versions for a given\npackage requirement.\n\nBy default, uv will use the latest compatible version of each package (`highest`).", "anyOf": [ { "$ref": "#/definitions/ResolutionMode" @@ -1535,28 +1501,28 @@ ] }, "strict": { - "description": "Validate the Python environment, to detect packages with missing dependencies and other issues.", + "description": "Validate the Python environment, to detect packages with missing dependencies and other\nissues.", "type": [ "boolean", "null" ] }, "system": { - "description": "Install packages into the system Python environment.\n\nBy default, uv installs into the virtual environment in the current working directory or any parent directory. The `--system` option instructs uv to instead use the first Python found in the system `PATH`.\n\nWARNING: `--system` is intended for use in continuous integration (CI) environments and should be used with caution, as it can modify the system Python installation.", + "description": "Install packages into the system Python environment.\n\nBy default, uv installs into the virtual environment in the current working directory or\nany parent directory. The `--system` option instructs uv to instead use the first Python\nfound in the system `PATH`.\n\nWARNING: `--system` is intended for use in continuous integration (CI) environments and\nshould be used with caution, as it can modify the system Python installation.", "type": [ "boolean", "null" ] }, "target": { - "description": "Install packages into the specified directory, rather than into the virtual or system Python environment. The packages will be installed at the top-level of the directory.", + "description": "Install packages into the specified directory, rather than into the virtual or system Python\nenvironment. The packages will be installed at the top-level of the directory.", "type": [ "string", "null" ] }, "torch-backend": { - "description": "The backend to use when fetching packages in the PyTorch ecosystem.\n\nWhen set, uv will ignore the configured index URLs for packages in the PyTorch ecosystem, and will instead use the defined backend.\n\nFor example, when set to `cpu`, uv will use the CPU-only PyTorch index; when set to `cu126`, uv will use the PyTorch index for CUDA 12.6.\n\nThe `auto` mode will attempt to detect the appropriate PyTorch index based on the currently installed CUDA drivers.\n\nThis option is in preview and may change in any future release.", + "description": "The backend to use when fetching packages in the PyTorch ecosystem.\n\nWhen set, uv will ignore the configured index URLs for packages in the PyTorch ecosystem,\nand will instead use the defined backend.\n\nFor example, when set to `cpu`, uv will use the CPU-only PyTorch index; when set to `cu126`,\nuv will use the PyTorch index for CUDA 12.6.\n\nThe `auto` mode will attempt to detect the appropriate PyTorch index based on the currently\ninstalled CUDA drivers.\n\nThis option is in preview and may change in any future release.", "anyOf": [ { "$ref": "#/definitions/TorchMode" @@ -1567,7 +1533,7 @@ ] }, "universal": { - "description": "Perform a universal resolution, attempting to generate a single `requirements.txt` output file that is compatible with all operating systems, architectures, and Python implementations.\n\nIn universal mode, the current Python version (or user-provided `--python-version`) will be treated as a lower bound. For example, `--universal --python-version 3.7` would produce a universal resolution for Python 3.7 and later.", + "description": "Perform a universal resolution, attempting to generate a single `requirements.txt` output\nfile that is compatible with all operating systems, architectures, and Python\nimplementations.\n\nIn universal mode, the current Python version (or user-provided `--python-version`) will be\ntreated as a lower bound. For example, `--universal --python-version 3.7` would produce a\nuniversal resolution for Python 3.7 and later.", "type": [ "boolean", "null" @@ -1581,7 +1547,7 @@ ] }, "upgrade-package": { - "description": "Allow upgrades for a specific package, ignoring pinned versions in any existing output file.\n\nAccepts both standalone package names (`ruff`) and version specifiers (`ruff<0.5.0`).", + "description": "Allow upgrades for a specific package, ignoring pinned versions in any existing output\nfile.\n\nAccepts both standalone package names (`ruff`) and version specifiers (`ruff<0.5.0`).", "type": [ "array", "null" @@ -1591,7 +1557,7 @@ } }, "verify-hashes": { - "description": "Validate any hashes provided in the requirements file.\n\nUnlike `--require-hashes`, `--verify-hashes` does not require that all requirements have hashes; instead, it will limit itself to verifying the hashes of those requirements that do include them.", + "description": "Validate any hashes provided in the requirements file.\n\nUnlike `--require-hashes`, `--verify-hashes` does not require that all requirements have\nhashes; instead, it will limit itself to verifying the hashes of those requirements that do\ninclude them.", "type": [ "boolean", "null" @@ -1600,42 +1566,35 @@ }, "additionalProperties": false }, + "PortablePathBuf": { + "type": "string" + }, "PrereleaseMode": { "oneOf": [ { "description": "Disallow all pre-release versions.", "type": "string", - "enum": [ - "disallow" - ] + "const": "disallow" }, { "description": "Allow all pre-release versions.", "type": "string", - "enum": [ - "allow" - ] + "const": "allow" }, { "description": "Allow pre-release versions if all versions of a package are pre-release.", "type": "string", - "enum": [ - "if-necessary" - ] + "const": "if-necessary" }, { - "description": "Allow pre-release versions for first-party packages with explicit pre-release markers in their version requirements.", + "description": "Allow pre-release versions for first-party packages with explicit pre-release markers in\ntheir version requirements.", "type": "string", - "enum": [ - "explicit" - ] + "const": "explicit" }, { - "description": "Allow pre-release versions if all versions of a package are pre-release, or if the package has an explicit pre-release marker in its version requirements.", + "description": "Allow pre-release versions if all versions of a package are pre-release, or if the package\nhas an explicit pre-release marker in its version requirements.", "type": "string", - "enum": [ - "if-necessary-or-explicit" - ] + "const": "if-necessary-or-explicit" } ] }, @@ -1644,23 +1603,17 @@ { "description": "Automatically download managed Python installations when needed.", "type": "string", - "enum": [ - "automatic" - ] + "const": "automatic" }, { "description": "Do not automatically download managed Python installations; require explicit installation.", "type": "string", - "enum": [ - "manual" - ] + "const": "manual" }, { "description": "Do not ever allow Python downloads.", "type": "string", - "enum": [ - "never" - ] + "const": "never" } ] }, @@ -1669,30 +1622,22 @@ { "description": "Only use managed Python installations; never use system Python installations.", "type": "string", - "enum": [ - "only-managed" - ] + "const": "only-managed" }, { - "description": "Prefer managed Python installations over system Python installations.\n\nSystem Python installations are still preferred over downloading managed Python versions. Use `only-managed` to always fetch a managed Python version.", + "description": "Prefer managed Python installations over system Python installations.\n\nSystem Python installations are still preferred over downloading managed Python versions.\nUse `only-managed` to always fetch a managed Python version.", "type": "string", - "enum": [ - "managed" - ] + "const": "managed" }, { "description": "Prefer system Python installations over managed Python installations.\n\nIf a system Python installation cannot be found, a managed Python installation can be used.", "type": "string", - "enum": [ - "system" - ] + "const": "system" }, { "description": "Only use system Python installations; never use managed Python installations.", "type": "string", - "enum": [ - "only-system" - ] + "const": "only-system" } ] }, @@ -1714,32 +1659,25 @@ { "description": "Resolve the highest compatible version of each package.", "type": "string", - "enum": [ - "highest" - ] + "const": "highest" }, { "description": "Resolve the lowest compatible version of each package.", "type": "string", - "enum": [ - "lowest" - ] + "const": "lowest" }, { - "description": "Resolve the lowest compatible version of any direct dependencies, and the highest compatible version of any transitive dependencies.", + "description": "Resolve the lowest compatible version of any direct dependencies, and the highest\ncompatible version of any transitive dependencies.", "type": "string", - "enum": [ - "lowest-direct" - ] + "const": "lowest-direct" } ] }, "SchemaConflictItem": { - "description": "A single item in a conflicting set.\n\nEach item is a pair of an (optional) package and a corresponding extra or group name for that package.", + "description": "A single item in a conflicting set.\n\nEach item is a pair of an (optional) package and a corresponding extra or group name for that\npackage.", "type": "object", "properties": { "extra": { - "default": null, "anyOf": [ { "$ref": "#/definitions/ExtraName" @@ -1747,10 +1685,10 @@ { "type": "null" } - ] + ], + "default": null }, "group": { - "default": null, "anyOf": [ { "$ref": "#/definitions/GroupName" @@ -1758,10 +1696,10 @@ { "type": "null" } - ] + ], + "default": null }, "package": { - "default": null, "anyOf": [ { "$ref": "#/definitions/PackageName" @@ -1769,33 +1707,34 @@ { "type": "null" } - ] + ], + "default": null } } }, "SchemaConflictSet": { - "description": "Like [`ConflictSet`], but for deserialization in `pyproject.toml`.\n\nThe schema format is different from the in-memory format. Specifically, the schema format does not allow specifying the package name (or will make it optional in the future), where as the in-memory format needs the package name.", + "description": "Like [`ConflictSet`], but for deserialization in `pyproject.toml`.\n\nThe schema format is different from the in-memory format. Specifically, the\nschema format does not allow specifying the package name (or will make it\noptional in the future), where as the in-memory format needs the package\nname.", "type": "array", "items": { "$ref": "#/definitions/SchemaConflictItem" } }, "SchemaConflicts": { - "description": "Like [`Conflicts`], but for deserialization in `pyproject.toml`.\n\nThe schema format is different from the in-memory format. Specifically, the schema format does not allow specifying the package name (or will make it optional in the future), where as the in-memory format needs the package name.\n\nN.B. `Conflicts` is still used for (de)serialization. Specifically, in the lock file, where the package name is required.", + "description": "Like [`Conflicts`], but for deserialization in `pyproject.toml`.\n\nThe schema format is different from the in-memory format. Specifically, the\nschema format does not allow specifying the package name (or will make it\noptional in the future), where as the in-memory format needs the package\nname.\n\nN.B. `Conflicts` is still used for (de)serialization. Specifically, in the\nlock file, where the package name is required.", "type": "array", "items": { "$ref": "#/definitions/SchemaConflictSet" } }, + "SerdePattern": { + "type": "string" + }, "Source": { "description": "A `tool.uv.sources` value.", "anyOf": [ { - "description": "A remote Git repository, available over HTTPS or SSH.\n\nExample: ```toml flask = { git = \"https://github.com/pallets/flask\", tag = \"3.0.0\" } ```", + "description": "A remote Git repository, available over HTTPS or SSH.\n\nExample:\n```toml\nflask = { git = \"https://github.com/pallets/flask\", tag = \"3.0.0\" }\n```", "type": "object", - "required": [ - "git" - ], "properties": { "branch": { "type": [ @@ -1815,8 +1754,11 @@ }, "git": { "description": "The repository URL (without the `git+` prefix).", - "type": "string", - "format": "uri" + "allOf": [ + { + "$ref": "#/definitions/DisplaySafeUrl" + } + ] }, "group": { "anyOf": [ @@ -1841,7 +1783,7 @@ "description": "The path to the directory with the `pyproject.toml`, if it's not in the archive root.", "anyOf": [ { - "$ref": "#/definitions/String" + "$ref": "#/definitions/PortablePathBuf" }, { "type": "null" @@ -1855,14 +1797,14 @@ ] } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "git" + ] }, { - "description": "A remote `http://` or `https://` URL, either a wheel (`.whl`) or a source distribution (`.zip`, `.tar.gz`).\n\nExample: ```toml flask = { url = \"https://files.pythonhosted.org/packages/61/80/ffe1da13ad9300f87c93af113edd0638c75138c42a0994becfacac078c06/flask-3.0.3-py3-none-any.whl\" } ```", + "description": "A remote `http://` or `https://` URL, either a wheel (`.whl`) or a source distribution\n(`.zip`, `.tar.gz`).\n\nExample:\n```toml\nflask = { url = \"https://files.pythonhosted.org/packages/61/80/ffe1da13ad9300f87c93af113edd0638c75138c42a0994becfacac078c06/flask-3.0.3-py3-none-any.whl\" }\n```", "type": "object", - "required": [ - "url" - ], "properties": { "extra": { "anyOf": [ @@ -1888,10 +1830,10 @@ "$ref": "#/definitions/MarkerTree" }, "subdirectory": { - "description": "For source distributions, the path to the directory with the `pyproject.toml`, if it's not in the archive root.", + "description": "For source distributions, the path to the directory with the `pyproject.toml`, if it's\nnot in the archive root.", "anyOf": [ { - "$ref": "#/definitions/String" + "$ref": "#/definitions/PortablePathBuf" }, { "type": "null" @@ -1899,18 +1841,17 @@ ] }, "url": { - "type": "string", - "format": "uri" + "$ref": "#/definitions/DisplaySafeUrl" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "url" + ] }, { - "description": "The path to a dependency, either a wheel (a `.whl` file), source distribution (a `.zip` or `.tar.gz` file), or source tree (i.e., a directory containing a `pyproject.toml` or `setup.py` file in the root).", + "description": "The path to a dependency, either a wheel (a `.whl` file), source distribution (a `.zip` or\n`.tar.gz` file), or source tree (i.e., a directory containing a `pyproject.toml` or\n`setup.py` file in the root).", "type": "object", - "required": [ - "path" - ], "properties": { "editable": { "description": "`false` by default.", @@ -1943,24 +1884,24 @@ "$ref": "#/definitions/MarkerTree" }, "package": { - "description": "Whether to treat the dependency as a buildable Python package (`true`) or as a virtual package (`false`). If `false`, the package will not be built or installed, but its dependencies will be included in the virtual environment.\n\nWhen omitted, the package status is inferred based on the presence of a `[build-system]` in the project's `pyproject.toml`.", + "description": "Whether to treat the dependency as a buildable Python package (`true`) or as a virtual\npackage (`false`). If `false`, the package will not be built or installed, but its\ndependencies will be included in the virtual environment.\n\nWhen omitted, the package status is inferred based on the presence of a `[build-system]`\nin the project's `pyproject.toml`.", "type": [ "boolean", "null" ] }, "path": { - "$ref": "#/definitions/String" + "$ref": "#/definitions/PortablePathBuf" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "path" + ] }, { "description": "A dependency pinned to a specific index, e.g., `torch` after setting `torch` to `https://download.pytorch.org/whl/cu118`.", "type": "object", - "required": [ - "index" - ], "properties": { "extra": { "anyOf": [ @@ -1989,14 +1930,14 @@ "$ref": "#/definitions/MarkerTree" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "index" + ] }, { "description": "A dependency on another package in the workspace.", "type": "object", - "required": [ - "workspace" - ], "properties": { "extra": { "anyOf": [ @@ -2022,18 +1963,18 @@ "$ref": "#/definitions/MarkerTree" }, "workspace": { - "description": "When set to `false`, the package will be fetched from the remote index, rather than included as a workspace package.", + "description": "When set to `false`, the package will be fetched from the remote index, rather than\nincluded as a workspace package.", "type": "boolean" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "workspace" + ] } ] }, "Sources": { - "$ref": "#/definitions/SourcesWire" - }, - "SourcesWire": { "anyOf": [ { "$ref": "#/definitions/Source" @@ -2047,25 +1988,22 @@ ] }, "StaticMetadata": { - "description": "A subset of the Python Package Metadata 2.3 standard as specified in .", + "description": "A subset of the Python Package Metadata 2.3 standard as specified in\n.", "type": "object", - "required": [ - "name" - ], "properties": { "name": { "$ref": "#/definitions/PackageName" }, "provides-extras": { - "default": [], "type": "array", + "default": [], "items": { "$ref": "#/definitions/ExtraName" } }, "requires-dist": { - "default": [], "type": "array", + "default": [], "items": { "$ref": "#/definitions/Requirement" } @@ -2084,286 +2022,209 @@ "null" ] } - } + }, + "required": [ + "name" + ] }, "StatusCode": { "description": "HTTP status code (100-599)", - "type": "integer", - "format": "uint16", - "maximum": 599.0, - "minimum": 100.0 - }, - "String": { - "type": "string" + "type": "number", + "maximum": 599, + "minimum": 100 }, "TargetTriple": { - "description": "The supported target triples. Each triple consists of an architecture, vendor, and operating system.\n\nSee: ", + "description": "The supported target triples. Each triple consists of an architecture, vendor, and operating\nsystem.\n\nSee: ", "oneOf": [ { "description": "An alias for `x86_64-pc-windows-msvc`, the default target for Windows.", "type": "string", - "enum": [ - "windows" - ] + "const": "windows" }, { "description": "An alias for `x86_64-unknown-linux-gnu`, the default target for Linux.", "type": "string", - "enum": [ - "linux" - ] + "const": "linux" }, { "description": "An alias for `aarch64-apple-darwin`, the default target for macOS.", "type": "string", - "enum": [ - "macos" - ] + "const": "macos" }, { "description": "A 64-bit x86 Windows target.", "type": "string", - "enum": [ - "x86_64-pc-windows-msvc" - ] + "const": "x86_64-pc-windows-msvc" }, { "description": "A 32-bit x86 Windows target.", "type": "string", - "enum": [ - "i686-pc-windows-msvc" - ] + "const": "i686-pc-windows-msvc" }, { "description": "An x86 Linux target. Equivalent to `x86_64-manylinux_2_17`.", "type": "string", - "enum": [ - "x86_64-unknown-linux-gnu" - ] + "const": "x86_64-unknown-linux-gnu" }, { - "description": "An ARM-based macOS target, as seen on Apple Silicon devices\n\nBy default, assumes the least-recent, non-EOL macOS version (13.0), but respects the `MACOSX_DEPLOYMENT_TARGET` environment variable if set.", + "description": "An ARM-based macOS target, as seen on Apple Silicon devices\n\nBy default, assumes the least-recent, non-EOL macOS version (13.0), but respects\nthe `MACOSX_DEPLOYMENT_TARGET` environment variable if set.", "type": "string", - "enum": [ - "aarch64-apple-darwin" - ] + "const": "aarch64-apple-darwin" }, { - "description": "An x86 macOS target.\n\nBy default, assumes the least-recent, non-EOL macOS version (13.0), but respects the `MACOSX_DEPLOYMENT_TARGET` environment variable if set.", + "description": "An x86 macOS target.\n\nBy default, assumes the least-recent, non-EOL macOS version (13.0), but respects\nthe `MACOSX_DEPLOYMENT_TARGET` environment variable if set.", "type": "string", - "enum": [ - "x86_64-apple-darwin" - ] + "const": "x86_64-apple-darwin" }, { "description": "An ARM64 Linux target. Equivalent to `aarch64-manylinux_2_17`.", "type": "string", - "enum": [ - "aarch64-unknown-linux-gnu" - ] + "const": "aarch64-unknown-linux-gnu" }, { "description": "An ARM64 Linux target.", "type": "string", - "enum": [ - "aarch64-unknown-linux-musl" - ] + "const": "aarch64-unknown-linux-musl" }, { "description": "An `x86_64` Linux target.", "type": "string", - "enum": [ - "x86_64-unknown-linux-musl" - ] + "const": "x86_64-unknown-linux-musl" }, { "description": "An `x86_64` target for the `manylinux2014` platform. Equivalent to `x86_64-manylinux_2_17`.", "type": "string", - "enum": [ - "x86_64-manylinux2014" - ] + "const": "x86_64-manylinux2014" }, { "description": "An `x86_64` target for the `manylinux_2_17` platform.", "type": "string", - "enum": [ - "x86_64-manylinux_2_17" - ] + "const": "x86_64-manylinux_2_17" }, { "description": "An `x86_64` target for the `manylinux_2_28` platform.", "type": "string", - "enum": [ - "x86_64-manylinux_2_28" - ] + "const": "x86_64-manylinux_2_28" }, { "description": "An `x86_64` target for the `manylinux_2_31` platform.", "type": "string", - "enum": [ - "x86_64-manylinux_2_31" - ] + "const": "x86_64-manylinux_2_31" }, { "description": "An `x86_64` target for the `manylinux_2_32` platform.", "type": "string", - "enum": [ - "x86_64-manylinux_2_32" - ] + "const": "x86_64-manylinux_2_32" }, { "description": "An `x86_64` target for the `manylinux_2_33` platform.", "type": "string", - "enum": [ - "x86_64-manylinux_2_33" - ] + "const": "x86_64-manylinux_2_33" }, { "description": "An `x86_64` target for the `manylinux_2_34` platform.", "type": "string", - "enum": [ - "x86_64-manylinux_2_34" - ] + "const": "x86_64-manylinux_2_34" }, { "description": "An `x86_64` target for the `manylinux_2_35` platform.", "type": "string", - "enum": [ - "x86_64-manylinux_2_35" - ] + "const": "x86_64-manylinux_2_35" }, { "description": "An `x86_64` target for the `manylinux_2_36` platform.", "type": "string", - "enum": [ - "x86_64-manylinux_2_36" - ] + "const": "x86_64-manylinux_2_36" }, { "description": "An `x86_64` target for the `manylinux_2_37` platform.", "type": "string", - "enum": [ - "x86_64-manylinux_2_37" - ] + "const": "x86_64-manylinux_2_37" }, { "description": "An `x86_64` target for the `manylinux_2_38` platform.", "type": "string", - "enum": [ - "x86_64-manylinux_2_38" - ] + "const": "x86_64-manylinux_2_38" }, { "description": "An `x86_64` target for the `manylinux_2_39` platform.", "type": "string", - "enum": [ - "x86_64-manylinux_2_39" - ] + "const": "x86_64-manylinux_2_39" }, { "description": "An `x86_64` target for the `manylinux_2_40` platform.", "type": "string", - "enum": [ - "x86_64-manylinux_2_40" - ] + "const": "x86_64-manylinux_2_40" }, { "description": "An ARM64 target for the `manylinux2014` platform. Equivalent to `aarch64-manylinux_2_17`.", "type": "string", - "enum": [ - "aarch64-manylinux2014" - ] + "const": "aarch64-manylinux2014" }, { "description": "An ARM64 target for the `manylinux_2_17` platform.", "type": "string", - "enum": [ - "aarch64-manylinux_2_17" - ] + "const": "aarch64-manylinux_2_17" }, { "description": "An ARM64 target for the `manylinux_2_28` platform.", "type": "string", - "enum": [ - "aarch64-manylinux_2_28" - ] + "const": "aarch64-manylinux_2_28" }, { "description": "An ARM64 target for the `manylinux_2_31` platform.", "type": "string", - "enum": [ - "aarch64-manylinux_2_31" - ] + "const": "aarch64-manylinux_2_31" }, { "description": "An ARM64 target for the `manylinux_2_32` platform.", "type": "string", - "enum": [ - "aarch64-manylinux_2_32" - ] + "const": "aarch64-manylinux_2_32" }, { "description": "An ARM64 target for the `manylinux_2_33` platform.", "type": "string", - "enum": [ - "aarch64-manylinux_2_33" - ] + "const": "aarch64-manylinux_2_33" }, { "description": "An ARM64 target for the `manylinux_2_34` platform.", "type": "string", - "enum": [ - "aarch64-manylinux_2_34" - ] + "const": "aarch64-manylinux_2_34" }, { "description": "An ARM64 target for the `manylinux_2_35` platform.", "type": "string", - "enum": [ - "aarch64-manylinux_2_35" - ] + "const": "aarch64-manylinux_2_35" }, { "description": "An ARM64 target for the `manylinux_2_36` platform.", "type": "string", - "enum": [ - "aarch64-manylinux_2_36" - ] + "const": "aarch64-manylinux_2_36" }, { "description": "An ARM64 target for the `manylinux_2_37` platform.", "type": "string", - "enum": [ - "aarch64-manylinux_2_37" - ] + "const": "aarch64-manylinux_2_37" }, { "description": "An ARM64 target for the `manylinux_2_38` platform.", "type": "string", - "enum": [ - "aarch64-manylinux_2_38" - ] + "const": "aarch64-manylinux_2_38" }, { "description": "An ARM64 target for the `manylinux_2_39` platform.", "type": "string", - "enum": [ - "aarch64-manylinux_2_39" - ] + "const": "aarch64-manylinux_2_39" }, { "description": "An ARM64 target for the `manylinux_2_40` platform.", "type": "string", - "enum": [ - "aarch64-manylinux_2_40" - ] + "const": "aarch64-manylinux_2_40" }, { "description": "A wasm32 target using the the Pyodide 2024 platform. Meant for use with Python 3.12.", "type": "string", - "enum": [ - "wasm32-pyodide2024" - ] + "const": "wasm32-pyodide2024" } ] }, @@ -2383,13 +2244,13 @@ "type": "object", "properties": { "exclude": { - "description": "Packages to exclude as workspace members. If a package matches both `members` and `exclude`, it will be excluded.\n\nSupports both globs and explicit paths.\n\nFor more information on the glob syntax, refer to the [`glob` documentation](https://docs.rs/glob/latest/glob/struct.Pattern.html).", + "description": "Packages to exclude as workspace members. If a package matches both `members` and\n`exclude`, it will be excluded.\n\nSupports both globs and explicit paths.\n\nFor more information on the glob syntax, refer to the [`glob` documentation](https://docs.rs/glob/latest/glob/struct.Pattern.html).", "type": [ "array", "null" ], "items": { - "$ref": "#/definitions/String" + "$ref": "#/definitions/SerdePattern" } }, "members": { @@ -2399,7 +2260,7 @@ "null" ], "items": { - "$ref": "#/definitions/String" + "$ref": "#/definitions/SerdePattern" } } }, @@ -2411,303 +2272,217 @@ { "description": "Select the appropriate PyTorch index based on the operating system and CUDA driver version.", "type": "string", - "enum": [ - "auto" - ] + "const": "auto" }, { "description": "Use the CPU-only PyTorch index.", "type": "string", - "enum": [ - "cpu" - ] + "const": "cpu" }, { "description": "Use the PyTorch index for CUDA 12.8.", "type": "string", - "enum": [ - "cu128" - ] + "const": "cu128" }, { "description": "Use the PyTorch index for CUDA 12.6.", "type": "string", - "enum": [ - "cu126" - ] + "const": "cu126" }, { "description": "Use the PyTorch index for CUDA 12.5.", "type": "string", - "enum": [ - "cu125" - ] + "const": "cu125" }, { "description": "Use the PyTorch index for CUDA 12.4.", "type": "string", - "enum": [ - "cu124" - ] + "const": "cu124" }, { "description": "Use the PyTorch index for CUDA 12.3.", "type": "string", - "enum": [ - "cu123" - ] + "const": "cu123" }, { "description": "Use the PyTorch index for CUDA 12.2.", "type": "string", - "enum": [ - "cu122" - ] + "const": "cu122" }, { "description": "Use the PyTorch index for CUDA 12.1.", "type": "string", - "enum": [ - "cu121" - ] + "const": "cu121" }, { "description": "Use the PyTorch index for CUDA 12.0.", "type": "string", - "enum": [ - "cu120" - ] + "const": "cu120" }, { "description": "Use the PyTorch index for CUDA 11.8.", "type": "string", - "enum": [ - "cu118" - ] + "const": "cu118" }, { "description": "Use the PyTorch index for CUDA 11.7.", "type": "string", - "enum": [ - "cu117" - ] + "const": "cu117" }, { "description": "Use the PyTorch index for CUDA 11.6.", "type": "string", - "enum": [ - "cu116" - ] + "const": "cu116" }, { "description": "Use the PyTorch index for CUDA 11.5.", "type": "string", - "enum": [ - "cu115" - ] + "const": "cu115" }, { "description": "Use the PyTorch index for CUDA 11.4.", "type": "string", - "enum": [ - "cu114" - ] + "const": "cu114" }, { "description": "Use the PyTorch index for CUDA 11.3.", "type": "string", - "enum": [ - "cu113" - ] + "const": "cu113" }, { "description": "Use the PyTorch index for CUDA 11.2.", "type": "string", - "enum": [ - "cu112" - ] + "const": "cu112" }, { "description": "Use the PyTorch index for CUDA 11.1.", "type": "string", - "enum": [ - "cu111" - ] + "const": "cu111" }, { "description": "Use the PyTorch index for CUDA 11.0.", "type": "string", - "enum": [ - "cu110" - ] + "const": "cu110" }, { "description": "Use the PyTorch index for CUDA 10.2.", "type": "string", - "enum": [ - "cu102" - ] + "const": "cu102" }, { "description": "Use the PyTorch index for CUDA 10.1.", "type": "string", - "enum": [ - "cu101" - ] + "const": "cu101" }, { "description": "Use the PyTorch index for CUDA 10.0.", "type": "string", - "enum": [ - "cu100" - ] + "const": "cu100" }, { "description": "Use the PyTorch index for CUDA 9.2.", "type": "string", - "enum": [ - "cu92" - ] + "const": "cu92" }, { "description": "Use the PyTorch index for CUDA 9.1.", "type": "string", - "enum": [ - "cu91" - ] + "const": "cu91" }, { "description": "Use the PyTorch index for CUDA 9.0.", "type": "string", - "enum": [ - "cu90" - ] + "const": "cu90" }, { "description": "Use the PyTorch index for CUDA 8.0.", "type": "string", - "enum": [ - "cu80" - ] + "const": "cu80" }, { "description": "Use the PyTorch index for ROCm 6.3.", "type": "string", - "enum": [ - "rocm6.3" - ] + "const": "rocm6.3" }, { "description": "Use the PyTorch index for ROCm 6.2.4.", "type": "string", - "enum": [ - "rocm6.2.4" - ] + "const": "rocm6.2.4" }, { "description": "Use the PyTorch index for ROCm 6.2.", "type": "string", - "enum": [ - "rocm6.2" - ] + "const": "rocm6.2" }, { "description": "Use the PyTorch index for ROCm 6.1.", "type": "string", - "enum": [ - "rocm6.1" - ] + "const": "rocm6.1" }, { "description": "Use the PyTorch index for ROCm 6.0.", "type": "string", - "enum": [ - "rocm6.0" - ] + "const": "rocm6.0" }, { "description": "Use the PyTorch index for ROCm 5.7.", "type": "string", - "enum": [ - "rocm5.7" - ] + "const": "rocm5.7" }, { "description": "Use the PyTorch index for ROCm 5.6.", "type": "string", - "enum": [ - "rocm5.6" - ] + "const": "rocm5.6" }, { "description": "Use the PyTorch index for ROCm 5.5.", "type": "string", - "enum": [ - "rocm5.5" - ] + "const": "rocm5.5" }, { "description": "Use the PyTorch index for ROCm 5.4.2.", "type": "string", - "enum": [ - "rocm5.4.2" - ] + "const": "rocm5.4.2" }, { "description": "Use the PyTorch index for ROCm 5.4.", "type": "string", - "enum": [ - "rocm5.4" - ] + "const": "rocm5.4" }, { "description": "Use the PyTorch index for ROCm 5.3.", "type": "string", - "enum": [ - "rocm5.3" - ] + "const": "rocm5.3" }, { "description": "Use the PyTorch index for ROCm 5.2.", "type": "string", - "enum": [ - "rocm5.2" - ] + "const": "rocm5.2" }, { "description": "Use the PyTorch index for ROCm 5.1.1.", "type": "string", - "enum": [ - "rocm5.1.1" - ] + "const": "rocm5.1.1" }, { "description": "Use the PyTorch index for ROCm 4.2.", "type": "string", - "enum": [ - "rocm4.2" - ] + "const": "rocm4.2" }, { "description": "Use the PyTorch index for ROCm 4.1.", "type": "string", - "enum": [ - "rocm4.1" - ] + "const": "rocm4.1" }, { "description": "Use the PyTorch index for ROCm 4.0.1.", "type": "string", - "enum": [ - "rocm4.0.1" - ] + "const": "rocm4.0.1" }, { "description": "Use the PyTorch index for Intel XPU.", "type": "string", - "enum": [ - "xpu" - ] + "const": "xpu" } ] }, @@ -2727,9 +2502,7 @@ { "description": "Try trusted publishing when we're already in GitHub Actions, continue if that fails.", "type": "string", - "enum": [ - "automatic" - ] + "const": "automatic" } ] }, @@ -2738,39 +2511,39 @@ "type": "object", "properties": { "data": { - "default": null, "type": [ "string", "null" - ] + ], + "default": null }, "headers": { - "default": null, "type": [ "string", "null" - ] + ], + "default": null }, "platlib": { - "default": null, "type": [ "string", "null" - ] + ], + "default": null }, "purelib": { - "default": null, "type": [ "string", "null" - ] + ], + "default": null }, "scripts": { - "default": null, "type": [ "string", "null" - ] + ], + "default": null } }, "additionalProperties": false From 283323a78acae50e5a01f4a5754dd1fa422b5a4a Mon Sep 17 00:00:00 2001 From: konsti Date: Wed, 25 Jun 2025 09:44:22 +0200 Subject: [PATCH 056/349] Allow symlinks in the build backend (#14212) In workspaces with multiple packages, you usually don't want to include shared files such as the license repeatedly. Instead, we reading from symlinked files. This would be supported if we had used std's `is_file` and read methods, but walkdir's `is_file` does not consider symlinked files as files. See https://github.com/astral-sh/uv/issues/3957#issuecomment-2994675003 --- crates/uv-build-backend/src/lib.rs | 14 +++- crates/uv-build-backend/src/source_dist.rs | 30 ++------ crates/uv-build-backend/src/wheel.rs | 36 ++------- crates/uv/tests/it/build_backend.rs | 90 ++++++++++++++++++++++ 4 files changed, 115 insertions(+), 55 deletions(-) diff --git a/crates/uv-build-backend/src/lib.rs b/crates/uv-build-backend/src/lib.rs index 15ff81a4b..548214c32 100644 --- a/crates/uv-build-backend/src/lib.rs +++ b/crates/uv-build-backend/src/lib.rs @@ -9,12 +9,12 @@ pub use settings::{BuildBackendSettings, WheelDataIncludes}; pub use source_dist::{build_source_dist, list_source_dist}; pub use wheel::{build_editable, build_wheel, list_wheel, metadata}; -use std::fs::FileType; use std::io; use std::path::{Path, PathBuf}; use std::str::FromStr; use thiserror::Error; use tracing::debug; +use walkdir::DirEntry; use uv_fs::Simplified; use uv_globfilter::PortableGlobError; @@ -54,8 +54,6 @@ pub enum Error { #[source] err: walkdir::Error, }, - #[error("Unsupported file type {:?}: `{}`", _1, _0.user_display())] - UnsupportedFileType(PathBuf, FileType), #[error("Failed to write wheel zip archive")] Zip(#[from] zip::result::ZipError), #[error("Failed to write RECORD file")] @@ -86,6 +84,16 @@ trait DirectoryWriter { /// Files added through the method are considered generated when listing included files. fn write_bytes(&mut self, path: &str, bytes: &[u8]) -> Result<(), Error>; + /// Add the file or directory to the path. + fn write_dir_entry(&mut self, entry: &DirEntry, target_path: &str) -> Result<(), Error> { + if entry.file_type().is_dir() { + self.write_directory(target_path)?; + } else { + self.write_file(target_path, entry.path())?; + } + Ok(()) + } + /// Add a local file. fn write_file(&mut self, path: &str, file: &Path) -> Result<(), Error>; diff --git a/crates/uv-build-backend/src/source_dist.rs b/crates/uv-build-backend/src/source_dist.rs index 6285ae7c0..0a302ccf2 100644 --- a/crates/uv-build-backend/src/source_dist.rs +++ b/crates/uv-build-backend/src/source_dist.rs @@ -250,32 +250,16 @@ fn write_source_dist( .expect("walkdir starts with root"); if !include_matcher.match_path(relative) || exclude_matcher.is_match(relative) { - trace!("Excluding: `{}`", relative.user_display()); + trace!("Excluding from sdist: `{}`", relative.user_display()); continue; } - debug!("Including {}", relative.user_display()); - if entry.file_type().is_dir() { - writer.write_directory( - &Path::new(&top_level) - .join(relative) - .portable_display() - .to_string(), - )?; - } else if entry.file_type().is_file() { - writer.write_file( - &Path::new(&top_level) - .join(relative) - .portable_display() - .to_string(), - entry.path(), - )?; - } else { - return Err(Error::UnsupportedFileType( - relative.to_path_buf(), - entry.file_type(), - )); - } + let entry_path = Path::new(&top_level) + .join(relative) + .portable_display() + .to_string(); + debug!("Adding to sdist: {}", relative.user_display()); + writer.write_dir_entry(&entry, &entry_path)?; } debug!("Visited {files_visited} files for source dist build"); diff --git a/crates/uv-build-backend/src/wheel.rs b/crates/uv-build-backend/src/wheel.rs index da376d078..7da232941 100644 --- a/crates/uv-build-backend/src/wheel.rs +++ b/crates/uv-build-backend/src/wheel.rs @@ -164,7 +164,7 @@ fn write_wheel( .path() .strip_prefix(source_tree) .expect("walkdir starts with root"); - let wheel_path = entry + let entry_path = entry .path() .strip_prefix(&src_root) .expect("walkdir starts with root"); @@ -172,21 +172,10 @@ fn write_wheel( trace!("Excluding from module: `{}`", match_path.user_display()); continue; } - let wheel_path = wheel_path.portable_display().to_string(); - debug!("Adding to wheel: `{wheel_path}`"); - - if entry.file_type().is_dir() { - wheel_writer.write_directory(&wheel_path)?; - } else if entry.file_type().is_file() { - wheel_writer.write_file(&wheel_path, entry.path())?; - } else { - // TODO(konsti): We may want to support symlinks, there is support for installing them. - return Err(Error::UnsupportedFileType( - entry.path().to_path_buf(), - entry.file_type(), - )); - } + let entry_path = entry_path.portable_display().to_string(); + debug!("Adding to wheel: {entry_path}"); + wheel_writer.write_dir_entry(&entry, &entry_path)?; } debug!("Visited {files_visited} files for wheel build"); @@ -519,23 +508,12 @@ fn wheel_subdir_from_globs( continue; } - let relative_licenses = Path::new(target) + let license_path = Path::new(target) .join(relative) .portable_display() .to_string(); - - if entry.file_type().is_dir() { - wheel_writer.write_directory(&relative_licenses)?; - } else if entry.file_type().is_file() { - debug!("Adding {} file: `{}`", globs_field, relative.user_display()); - wheel_writer.write_file(&relative_licenses, entry.path())?; - } else { - // TODO(konsti): We may want to support symlinks, there is support for installing them. - return Err(Error::UnsupportedFileType( - entry.path().to_path_buf(), - entry.file_type(), - )); - } + debug!("Adding for {}: `{}`", globs_field, relative.user_display()); + wheel_writer.write_dir_entry(&entry, &license_path)?; } Ok(()) } diff --git a/crates/uv/tests/it/build_backend.rs b/crates/uv/tests/it/build_backend.rs index c2d99ba3e..3dd38278f 100644 --- a/crates/uv/tests/it/build_backend.rs +++ b/crates/uv/tests/it/build_backend.rs @@ -768,3 +768,93 @@ fn complex_namespace_packages() -> Result<()> { ); Ok(()) } + +/// Test that a symlinked file (here: license) gets included. +#[test] +#[cfg(unix)] +fn symlinked_file() -> Result<()> { + let context = TestContext::new("3.12"); + + let project = context.temp_dir.child("project"); + context + .init() + .arg("--preview") + .arg("--build-backend") + .arg("uv") + .arg(project.path()) + .assert() + .success(); + + project.child("pyproject.toml").write_str(indoc! {r#" + [project] + name = "project" + version = "1.0.0" + license-files = ["LICENSE"] + + [build-system] + requires = ["uv_build>=0.5.15,<10000"] + build-backend = "uv_build" + "# + })?; + + let license_file = context.temp_dir.child("LICENSE"); + let license_symlink = project.child("LICENSE"); + + let license_text = "Project license"; + license_file.write_str(license_text)?; + fs_err::os::unix::fs::symlink(license_file.path(), license_symlink.path())?; + + uv_snapshot!(context + .build_backend() + .arg("build-sdist") + .arg(context.temp_dir.path()) + .current_dir(project.path()), @r" + success: true + exit_code: 0 + ----- stdout ----- + project-1.0.0.tar.gz + + ----- stderr ----- + "); + + uv_snapshot!(context + .build_backend() + .arg("build-wheel") + .arg(context.temp_dir.path()) + .current_dir(project.path()), @r" + success: true + exit_code: 0 + ----- stdout ----- + project-1.0.0-py3-none-any.whl + + ----- stderr ----- + "); + + uv_snapshot!(context.filters(), context.pip_install().arg("project-1.0.0-py3-none-any.whl"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + project==1.0.0 (from file://[TEMP_DIR]/project-1.0.0-py3-none-any.whl) + "); + + // Check that we included the actual license text and not a broken symlink. + let installed_license = context + .site_packages() + .join("project-1.0.0.dist-info") + .join("licenses") + .join("LICENSE"); + assert!( + fs_err::symlink_metadata(&installed_license)? + .file_type() + .is_file() + ); + let license = fs_err::read_to_string(&installed_license)?; + assert_eq!(license, license_text); + + Ok(()) +} From 5b2c3595a7afe4539a41702aaaf0409fe571f3ff Mon Sep 17 00:00:00 2001 From: John Mumm Date: Wed, 25 Jun 2025 04:02:06 -0400 Subject: [PATCH 057/349] Require disambiguated relative paths for `--index` (#14152) We do not currently support passing index names to `--index` for installing packages. However, we do accept relative paths that can look like index names. This PR adds the requirement that `--index` values must be disambiguated with a prefix (`./` or `../` on Unix and Windows or `.\\` or `..\\` on Windows). For now, if an ambiguous value is provided, uv will warn that this will not be supported in the future. Currently, if you provide an index name like `--index test` when there is no `test` directory, uv will error with a `Directory not found...` error. That's not very informative if you thought index names were supported. The new warning makes the context clearer. Closes #13921 --- Cargo.lock | 1 + crates/uv-cli/src/lib.rs | 3 + crates/uv-distribution-types/Cargo.toml | 1 + crates/uv-distribution-types/src/index_url.rs | 85 +++++++++++++++++++ crates/uv/src/settings.rs | 6 ++ crates/uv/tests/it/edit.rs | 52 +++++++++++- docs/reference/cli.md | 18 ++++ 7 files changed, 162 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2e1957e9f..781f98079 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5177,6 +5177,7 @@ dependencies = [ "uv-pypi-types", "uv-redacted", "uv-small-str", + "uv-warnings", "version-ranges", ] diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index e58f6c079..bf605198f 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -5130,6 +5130,9 @@ pub struct IndexArgs { /// All indexes provided via this flag take priority over the index specified by /// `--default-index` (which defaults to PyPI). When multiple `--index` flags are provided, /// earlier values take priority. + /// + /// Index names are not supported as values. Relative paths must be disambiguated from index + /// names with `./` or `../` on Unix or `.\\`, `..\\`, `./` or `../` on Windows. // // The nested Vec structure (`Vec>>`) is required for clap's // value parsing mechanism, which processes one value at a time, in order to handle diff --git a/crates/uv-distribution-types/Cargo.toml b/crates/uv-distribution-types/Cargo.toml index dc5a70166..1ca28c5ed 100644 --- a/crates/uv-distribution-types/Cargo.toml +++ b/crates/uv-distribution-types/Cargo.toml @@ -29,6 +29,7 @@ uv-platform-tags = { workspace = true } uv-pypi-types = { workspace = true } uv-redacted = { workspace = true } uv-small-str = { workspace = true } +uv-warnings = { workspace = true } arcstr = { workspace = true } bitflags = { workspace = true } diff --git a/crates/uv-distribution-types/src/index_url.rs b/crates/uv-distribution-types/src/index_url.rs index 9d07c9cbe..2b686c9fe 100644 --- a/crates/uv-distribution-types/src/index_url.rs +++ b/crates/uv-distribution-types/src/index_url.rs @@ -12,6 +12,7 @@ use url::{ParseError, Url}; use uv_pep508::{Scheme, VerbatimUrl, VerbatimUrlError, split_scheme}; use uv_redacted::DisplaySafeUrl; +use uv_warnings::warn_user; use crate::{Index, IndexStatusCodeStrategy, Verbatim}; @@ -135,6 +136,30 @@ impl IndexUrl { Cow::Owned(url) } } + + /// Warn user if the given URL was provided as an ambiguous relative path. + /// + /// This is a temporary warning. Ambiguous values will not be + /// accepted in the future. + pub fn warn_on_disambiguated_relative_path(&self) { + let Self::Path(verbatim_url) = &self else { + return; + }; + + if let Some(path) = verbatim_url.given() { + if !is_disambiguated_path(path) { + if cfg!(windows) { + warn_user!( + "Relative paths passed to `--index` or `--default-index` should be disambiguated from index names (use `.\\{path}` or `./{path}`). Support for ambiguous values will be removed in the future" + ); + } else { + warn_user!( + "Relative paths passed to `--index` or `--default-index` should be disambiguated from index names (use `./{path}`). Support for ambiguous values will be removed in the future" + ); + } + } + } + } } impl Display for IndexUrl { @@ -157,6 +182,28 @@ impl Verbatim for IndexUrl { } } +/// Checks if a path is disambiguated. +/// +/// Disambiguated paths are absolute paths, paths with valid schemes, +/// and paths starting with "./" or "../" on Unix or ".\\", "..\\", +/// "./", or "../" on Windows. +fn is_disambiguated_path(path: &str) -> bool { + if cfg!(windows) { + if path.starts_with(".\\") || path.starts_with("..\\") || path.starts_with('/') { + return true; + } + } + if path.starts_with("./") || path.starts_with("../") || Path::new(path).is_absolute() { + return true; + } + // Check if the path has a scheme (like `file://`) + if let Some((scheme, _)) = split_scheme(path) { + return Scheme::parse(scheme).is_some(); + } + // This is an ambiguous relative path + false +} + /// An error that can occur when parsing an [`IndexUrl`]. #[derive(Error, Debug)] pub enum IndexUrlError { @@ -620,3 +667,41 @@ impl IndexCapabilities { .insert(Flags::FORBIDDEN); } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_index_url_parse_valid_paths() { + // Absolute path + assert!(is_disambiguated_path("/absolute/path")); + // Relative path + assert!(is_disambiguated_path("./relative/path")); + assert!(is_disambiguated_path("../../relative/path")); + if cfg!(windows) { + // Windows absolute path + assert!(is_disambiguated_path("C:/absolute/path")); + // Windows relative path + assert!(is_disambiguated_path(".\\relative\\path")); + assert!(is_disambiguated_path("..\\..\\relative\\path")); + } + } + + #[test] + fn test_index_url_parse_ambiguous_paths() { + // Test single-segment ambiguous path + assert!(!is_disambiguated_path("index")); + // Test multi-segment ambiguous path + assert!(!is_disambiguated_path("relative/path")); + } + + #[test] + fn test_index_url_parse_with_schemes() { + assert!(is_disambiguated_path("file:///absolute/path")); + assert!(is_disambiguated_path("https://registry.com/simple/")); + assert!(is_disambiguated_path( + "git+https://github.com/example/repo.git" + )); + } +} diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 5cbeb1886..58a012d89 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -1386,6 +1386,12 @@ impl AddSettings { ) .collect::>(); + // Warn user if an ambiguous relative path was passed as a value for + // `--index` or `--default-index`. + indexes + .iter() + .for_each(|index| index.url().warn_on_disambiguated_relative_path()); + // If the user passed an `--index-url` or `--extra-index-url`, warn. if installer .index_args diff --git a/crates/uv/tests/it/edit.rs b/crates/uv/tests/it/edit.rs index 6bdaec17b..2f9c91e84 100644 --- a/crates/uv/tests/it/edit.rs +++ b/crates/uv/tests/it/edit.rs @@ -9446,7 +9446,7 @@ fn add_index_with_existing_relative_path_index() -> Result<()> { let wheel_dst = packages.child("ok-1.0.0-py3-none-any.whl"); fs_err::copy(&wheel_src, &wheel_dst)?; - uv_snapshot!(context.filters(), context.add().arg("iniconfig").arg("--index").arg("test-index"), @r" + uv_snapshot!(context.filters(), context.add().arg("iniconfig").arg("--index").arg("./test-index"), @r" success: true exit_code: 0 ----- stdout ----- @@ -9475,7 +9475,7 @@ fn add_index_with_non_existent_relative_path() -> Result<()> { dependencies = [] "#})?; - uv_snapshot!(context.filters(), context.add().arg("iniconfig").arg("--index").arg("test-index"), @r" + uv_snapshot!(context.filters(), context.add().arg("iniconfig").arg("--index").arg("./test-index"), @r" success: false exit_code: 2 ----- stdout ----- @@ -9505,7 +9505,7 @@ fn add_index_with_non_existent_relative_path_with_same_name_as_index() -> Result url = "https://pypi-proxy.fly.dev/simple" "#})?; - uv_snapshot!(context.filters(), context.add().arg("iniconfig").arg("--index").arg("test-index"), @r" + uv_snapshot!(context.filters(), context.add().arg("iniconfig").arg("--index").arg("./test-index"), @r" success: false exit_code: 2 ----- stdout ----- @@ -9528,12 +9528,16 @@ fn add_index_empty_directory() -> Result<()> { version = "0.1.0" requires-python = ">=3.12" dependencies = [] + + [[tool.uv.index]] + name = "test-index" + url = "https://pypi-proxy.fly.dev/simple" "#})?; let packages = context.temp_dir.child("test-index"); packages.create_dir_all()?; - uv_snapshot!(context.filters(), context.add().arg("iniconfig").arg("--index").arg("test-index"), @r" + uv_snapshot!(context.filters(), context.add().arg("iniconfig").arg("--index").arg("./test-index"), @r" success: true exit_code: 0 ----- stdout ----- @@ -9549,6 +9553,46 @@ fn add_index_empty_directory() -> Result<()> { Ok(()) } +#[test] +fn add_index_with_ambiguous_relative_path() -> Result<()> { + let context = TestContext::new("3.12"); + let mut filters = context.filters(); + filters.push((r"\./|\.\\\\", r"[PREFIX]")); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + "#})?; + + #[cfg(unix)] + uv_snapshot!(filters, context.add().arg("iniconfig").arg("--index").arg("test-index"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + warning: Relative paths passed to `--index` or `--default-index` should be disambiguated from index names (use `[PREFIX]test-index`). Support for ambiguous values will be removed in the future + error: Directory not found for index: file://[TEMP_DIR]/test-index + "); + + #[cfg(windows)] + uv_snapshot!(filters, context.add().arg("iniconfig").arg("--index").arg("test-index"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + warning: Relative paths passed to `--index` or `--default-index` should be disambiguated from index names (use `[PREFIX]test-index` or `[PREFIX]test-index`). Support for ambiguous values will be removed in the future + error: Directory not found for index: file://[TEMP_DIR]/test-index + "); + + Ok(()) +} + /// Add a PyPI requirement. #[test] fn add_group_comment() -> Result<()> { diff --git a/docs/reference/cli.md b/docs/reference/cli.md index b9490e2ce..82fe0fa3d 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -123,6 +123,7 @@ uv run [OPTIONS] [COMMAND]
    --index index

    The URLs to use when resolving dependencies, in addition to the default index.

    Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.

    All indexes provided via this flag take priority over the index specified by --default-index (which defaults to PyPI). When multiple --index flags are provided, earlier values take priority.

    +

    Index names are not supported as values. Relative paths must be disambiguated from index names with ./ or ../ on Unix or .\\, ..\\, ./ or ../ on Windows.

    May also be set with the UV_INDEX environment variable.

    --index-strategy index-strategy

    The strategy to use when resolving against multiple index URLs.

    By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (first-index). This prevents "dependency confusion" attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.

    May also be set with the UV_INDEX_STRATEGY environment variable.

    Possible values:

    @@ -479,6 +480,7 @@ uv add [OPTIONS] >
    --index index

    The URLs to use when resolving dependencies, in addition to the default index.

    Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.

    All indexes provided via this flag take priority over the index specified by --default-index (which defaults to PyPI). When multiple --index flags are provided, earlier values take priority.

    +

    Index names are not supported as values. Relative paths must be disambiguated from index names with ./ or ../ on Unix or .\\, ..\\, ./ or ../ on Windows.

    May also be set with the UV_INDEX environment variable.

    --index-strategy index-strategy

    The strategy to use when resolving against multiple index URLs.

    By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (first-index). This prevents "dependency confusion" attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.

    May also be set with the UV_INDEX_STRATEGY environment variable.

    Possible values:

    @@ -663,6 +665,7 @@ uv remove [OPTIONS] ...
    --index index

    The URLs to use when resolving dependencies, in addition to the default index.

    Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.

    All indexes provided via this flag take priority over the index specified by --default-index (which defaults to PyPI). When multiple --index flags are provided, earlier values take priority.

    +

    Index names are not supported as values. Relative paths must be disambiguated from index names with ./ or ../ on Unix or .\\, ..\\, ./ or ../ on Windows.

    May also be set with the UV_INDEX environment variable.

    --index-strategy index-strategy

    The strategy to use when resolving against multiple index URLs.

    By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (first-index). This prevents "dependency confusion" attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.

    May also be set with the UV_INDEX_STRATEGY environment variable.

    Possible values:

    @@ -832,6 +835,7 @@ uv version [OPTIONS] [VALUE]
    --index index

    The URLs to use when resolving dependencies, in addition to the default index.

    Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.

    All indexes provided via this flag take priority over the index specified by --default-index (which defaults to PyPI). When multiple --index flags are provided, earlier values take priority.

    +

    Index names are not supported as values. Relative paths must be disambiguated from index names with ./ or ../ on Unix or .\\, ..\\, ./ or ../ on Windows.

    May also be set with the UV_INDEX environment variable.

    --index-strategy index-strategy

    The strategy to use when resolving against multiple index URLs.

    By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (first-index). This prevents "dependency confusion" attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.

    May also be set with the UV_INDEX_STRATEGY environment variable.

    Possible values:

    @@ -1022,6 +1026,7 @@ uv sync [OPTIONS]
    --index index

    The URLs to use when resolving dependencies, in addition to the default index.

    Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.

    All indexes provided via this flag take priority over the index specified by --default-index (which defaults to PyPI). When multiple --index flags are provided, earlier values take priority.

    +

    Index names are not supported as values. Relative paths must be disambiguated from index names with ./ or ../ on Unix or .\\, ..\\, ./ or ../ on Windows.

    May also be set with the UV_INDEX environment variable.

    --index-strategy index-strategy

    The strategy to use when resolving against multiple index URLs.

    By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (first-index). This prevents "dependency confusion" attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.

    May also be set with the UV_INDEX_STRATEGY environment variable.

    Possible values:

    @@ -1210,6 +1215,7 @@ uv lock [OPTIONS]
    --index index

    The URLs to use when resolving dependencies, in addition to the default index.

    Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.

    All indexes provided via this flag take priority over the index specified by --default-index (which defaults to PyPI). When multiple --index flags are provided, earlier values take priority.

    +

    Index names are not supported as values. Relative paths must be disambiguated from index names with ./ or ../ on Unix or .\\, ..\\, ./ or ../ on Windows.

    May also be set with the UV_INDEX environment variable.

    --index-strategy index-strategy

    The strategy to use when resolving against multiple index URLs.

    By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (first-index). This prevents "dependency confusion" attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.

    May also be set with the UV_INDEX_STRATEGY environment variable.

    Possible values:

    @@ -1383,6 +1389,7 @@ uv export [OPTIONS]
    --index index

    The URLs to use when resolving dependencies, in addition to the default index.

    Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.

    All indexes provided via this flag take priority over the index specified by --default-index (which defaults to PyPI). When multiple --index flags are provided, earlier values take priority.

    +

    Index names are not supported as values. Relative paths must be disambiguated from index names with ./ or ../ on Unix or .\\, ..\\, ./ or ../ on Windows.

    May also be set with the UV_INDEX environment variable.

    --index-strategy index-strategy

    The strategy to use when resolving against multiple index URLs.

    By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (first-index). This prevents "dependency confusion" attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.

    May also be set with the UV_INDEX_STRATEGY environment variable.

    Possible values:

    @@ -1568,6 +1575,7 @@ uv tree [OPTIONS]
    --index index

    The URLs to use when resolving dependencies, in addition to the default index.

    Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.

    All indexes provided via this flag take priority over the index specified by --default-index (which defaults to PyPI). When multiple --index flags are provided, earlier values take priority.

    +

    Index names are not supported as values. Relative paths must be disambiguated from index names with ./ or ../ on Unix or .\\, ..\\, ./ or ../ on Windows.

    May also be set with the UV_INDEX environment variable.

    --index-strategy index-strategy

    The strategy to use when resolving against multiple index URLs.

    By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (first-index). This prevents "dependency confusion" attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.

    May also be set with the UV_INDEX_STRATEGY environment variable.

    Possible values:

    @@ -1827,6 +1835,7 @@ uv tool run [OPTIONS] [COMMAND]
    --index index

    The URLs to use when resolving dependencies, in addition to the default index.

    Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.

    All indexes provided via this flag take priority over the index specified by --default-index (which defaults to PyPI). When multiple --index flags are provided, earlier values take priority.

    +

    Index names are not supported as values. Relative paths must be disambiguated from index names with ./ or ../ on Unix or .\\, ..\\, ./ or ../ on Windows.

    May also be set with the UV_INDEX environment variable.

    --index-strategy index-strategy

    The strategy to use when resolving against multiple index URLs.

    By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (first-index). This prevents "dependency confusion" attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.

    May also be set with the UV_INDEX_STRATEGY environment variable.

    Possible values:

    @@ -1997,6 +2006,7 @@ uv tool install [OPTIONS]
    --index index

    The URLs to use when resolving dependencies, in addition to the default index.

    Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.

    All indexes provided via this flag take priority over the index specified by --default-index (which defaults to PyPI). When multiple --index flags are provided, earlier values take priority.

    +

    Index names are not supported as values. Relative paths must be disambiguated from index names with ./ or ../ on Unix or .\\, ..\\, ./ or ../ on Windows.

    May also be set with the UV_INDEX environment variable.

    --index-strategy index-strategy

    The strategy to use when resolving against multiple index URLs.

    By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (first-index). This prevents "dependency confusion" attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.

    May also be set with the UV_INDEX_STRATEGY environment variable.

    Possible values:

    @@ -2157,6 +2167,7 @@ uv tool upgrade [OPTIONS] ...
    --index index

    The URLs to use when resolving dependencies, in addition to the default index.

    Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.

    All indexes provided via this flag take priority over the index specified by --default-index (which defaults to PyPI). When multiple --index flags are provided, earlier values take priority.

    +

    Index names are not supported as values. Relative paths must be disambiguated from index names with ./ or ../ on Unix or .\\, ..\\, ./ or ../ on Windows.

    May also be set with the UV_INDEX environment variable.

    --index-strategy index-strategy

    The strategy to use when resolving against multiple index URLs.

    By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (first-index). This prevents "dependency confusion" attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.

    May also be set with the UV_INDEX_STRATEGY environment variable.

    Possible values:

    @@ -3252,6 +3263,7 @@ uv pip compile [OPTIONS] >
    --index index

    The URLs to use when resolving dependencies, in addition to the default index.

    Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.

    All indexes provided via this flag take priority over the index specified by --default-index (which defaults to PyPI). When multiple --index flags are provided, earlier values take priority.

    +

    Index names are not supported as values. Relative paths must be disambiguated from index names with ./ or ../ on Unix or .\\, ..\\, ./ or ../ on Windows.

    May also be set with the UV_INDEX environment variable.

    --index-strategy index-strategy

    The strategy to use when resolving against multiple index URLs.

    By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (first-index). This prevents "dependency confusion" attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.

    May also be set with the UV_INDEX_STRATEGY environment variable.

    Possible values:

    @@ -3531,6 +3543,7 @@ uv pip sync [OPTIONS] ...
    --index index

    The URLs to use when resolving dependencies, in addition to the default index.

    Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.

    All indexes provided via this flag take priority over the index specified by --default-index (which defaults to PyPI). When multiple --index flags are provided, earlier values take priority.

    +

    Index names are not supported as values. Relative paths must be disambiguated from index names with ./ or ../ on Unix or .\\, ..\\, ./ or ../ on Windows.

    May also be set with the UV_INDEX environment variable.

    --index-strategy index-strategy

    The strategy to use when resolving against multiple index URLs.

    By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (first-index). This prevents "dependency confusion" attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.

    May also be set with the UV_INDEX_STRATEGY environment variable.

    Possible values:

    @@ -3795,6 +3808,7 @@ uv pip install [OPTIONS] |--editable
    --index index

    The URLs to use when resolving dependencies, in addition to the default index.

    Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.

    All indexes provided via this flag take priority over the index specified by --default-index (which defaults to PyPI). When multiple --index flags are provided, earlier values take priority.

    +

    Index names are not supported as values. Relative paths must be disambiguated from index names with ./ or ../ on Unix or .\\, ..\\, ./ or ../ on Windows.

    May also be set with the UV_INDEX environment variable.

    --index-strategy index-strategy

    The strategy to use when resolving against multiple index URLs.

    By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (first-index). This prevents "dependency confusion" attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.

    May also be set with the UV_INDEX_STRATEGY environment variable.

    Possible values:

    @@ -4214,6 +4228,7 @@ uv pip list [OPTIONS]
    --index index

    The URLs to use when resolving dependencies, in addition to the default index.

    Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.

    All indexes provided via this flag take priority over the index specified by --default-index (which defaults to PyPI). When multiple --index flags are provided, earlier values take priority.

    +

    Index names are not supported as values. Relative paths must be disambiguated from index names with ./ or ../ on Unix or .\\, ..\\, ./ or ../ on Windows.

    May also be set with the UV_INDEX environment variable.

    --index-strategy index-strategy

    The strategy to use when resolving against multiple index URLs.

    By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (first-index). This prevents "dependency confusion" attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.

    May also be set with the UV_INDEX_STRATEGY environment variable.

    Possible values:

    @@ -4387,6 +4402,7 @@ uv pip tree [OPTIONS]
    --index index

    The URLs to use when resolving dependencies, in addition to the default index.

    Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.

    All indexes provided via this flag take priority over the index specified by --default-index (which defaults to PyPI). When multiple --index flags are provided, earlier values take priority.

    +

    Index names are not supported as values. Relative paths must be disambiguated from index names with ./ or ../ on Unix or .\\, ..\\, ./ or ../ on Windows.

    May also be set with the UV_INDEX environment variable.

    --index-strategy index-strategy

    The strategy to use when resolving against multiple index URLs.

    By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (first-index). This prevents "dependency confusion" attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.

    May also be set with the UV_INDEX_STRATEGY environment variable.

    Possible values:

    @@ -4574,6 +4590,7 @@ uv venv [OPTIONS] [PATH]
    --index index

    The URLs to use when resolving dependencies, in addition to the default index.

    Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.

    All indexes provided via this flag take priority over the index specified by --default-index (which defaults to PyPI). When multiple --index flags are provided, earlier values take priority.

    +

    Index names are not supported as values. Relative paths must be disambiguated from index names with ./ or ../ on Unix or .\\, ..\\, ./ or ../ on Windows.

    May also be set with the UV_INDEX environment variable.

    --index-strategy index-strategy

    The strategy to use when resolving against multiple index URLs.

    By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (first-index). This prevents "dependency confusion" attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.

    May also be set with the UV_INDEX_STRATEGY environment variable.

    Possible values:

    @@ -4724,6 +4741,7 @@ uv build [OPTIONS] [SRC]
    --index index

    The URLs to use when resolving dependencies, in addition to the default index.

    Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.

    All indexes provided via this flag take priority over the index specified by --default-index (which defaults to PyPI). When multiple --index flags are provided, earlier values take priority.

    +

    Index names are not supported as values. Relative paths must be disambiguated from index names with ./ or ../ on Unix or .\\, ..\\, ./ or ../ on Windows.

    May also be set with the UV_INDEX environment variable.

    --index-strategy index-strategy

    The strategy to use when resolving against multiple index URLs.

    By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (first-index). This prevents "dependency confusion" attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.

    May also be set with the UV_INDEX_STRATEGY environment variable.

    Possible values:

    From 177df19f303b6021754cd8629d7369c0bca9d76c Mon Sep 17 00:00:00 2001 From: John Mumm Date: Wed, 25 Jun 2025 04:12:32 -0400 Subject: [PATCH 058/349] Add check for using minor version link when creating a venv on Windows (#14252) There was a regression introduced in #13954 on Windows where creating a venv behaved as if there was a minor version link even if none existed. This PR adds a check to fix this. Closes #14249. --- crates/uv-virtualenv/src/virtualenv.rs | 4 ++- crates/uv/tests/it/venv.rs | 43 +++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/crates/uv-virtualenv/src/virtualenv.rs b/crates/uv-virtualenv/src/virtualenv.rs index f466233c0..bad380c4c 100644 --- a/crates/uv-virtualenv/src/virtualenv.rs +++ b/crates/uv-virtualenv/src/virtualenv.rs @@ -147,6 +147,7 @@ pub(crate) fn create( // Create a `.gitignore` file to ignore all files in the venv. fs::write(location.join(".gitignore"), "*")?; + let mut using_minor_version_link = false; let executable_target = if upgradeable && interpreter.is_standalone() { if let Some(minor_version_link) = PythonMinorVersionLink::from_executable( base_python.as_path(), @@ -167,6 +168,7 @@ pub(crate) fn create( &minor_version_link.symlink_directory.display(), &base_python.display() ); + using_minor_version_link = true; minor_version_link.symlink_executable.clone() } } else { @@ -228,7 +230,7 @@ pub(crate) fn create( // interpreters, this target path includes a minor version junction to enable // transparent upgrades. if cfg!(windows) { - if interpreter.is_standalone() { + if using_minor_version_link { let target = scripts.join(WindowsExecutable::Python.exe(interpreter)); create_link_to_executable(target.as_path(), executable_target.clone()) .map_err(Error::Python)?; diff --git a/crates/uv/tests/it/venv.rs b/crates/uv/tests/it/venv.rs index f1860efa2..52291c05d 100644 --- a/crates/uv/tests/it/venv.rs +++ b/crates/uv/tests/it/venv.rs @@ -516,7 +516,7 @@ fn create_venv_respects_group_requires_python() -> Result<()> { name = "foo" version = "1.0.0" dependencies = [] - + [dependency-groups] dev = ["sortedcontainers"] other = ["sniffio"] @@ -549,7 +549,7 @@ fn create_venv_respects_group_requires_python() -> Result<()> { version = "1.0.0" requires-python = ">=3.11" dependencies = [] - + [dependency-groups] dev = ["sortedcontainers"] other = ["sniffio"] @@ -582,7 +582,7 @@ fn create_venv_respects_group_requires_python() -> Result<()> { version = "1.0.0" requires-python = ">=3.10" dependencies = [] - + [dependency-groups] dev = ["sortedcontainers"] other = ["sniffio"] @@ -612,7 +612,7 @@ fn create_venv_respects_group_requires_python() -> Result<()> { name = "foo" version = "1.0.0" dependencies = [] - + [dependency-groups] dev = ["sortedcontainers"] @@ -643,7 +643,7 @@ fn create_venv_respects_group_requires_python() -> Result<()> { version = "1.0.0" requires-python = "<3.12" dependencies = [] - + [dependency-groups] dev = ["sortedcontainers"] other = ["sniffio"] @@ -1052,6 +1052,39 @@ fn non_empty_dir_exists_allow_existing() -> Result<()> { Ok(()) } +/// Run `uv venv` followed by `uv venv --allow-existing`. +#[test] +fn create_venv_then_allow_existing() { + let context = TestContext::new_with_versions(&["3.12"]); + + // Create a venv + uv_snapshot!(context.filters(), context.venv(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + Creating virtual environment at: .venv + Activate with: source .venv/[BIN]/activate + " + ); + + // Create a venv again with `--allow-existing` + uv_snapshot!(context.filters(), context.venv() + .arg("--allow-existing"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + Creating virtual environment at: .venv + Activate with: source .venv/[BIN]/activate + "### + ); +} + #[test] #[cfg(windows)] fn windows_shims() -> Result<()> { From 4ed9c5791ba9d5c304aae14b920d612af26dc2d3 Mon Sep 17 00:00:00 2001 From: John Mumm Date: Wed, 25 Jun 2025 06:06:41 -0400 Subject: [PATCH 059/349] Bump version to 0.7.15 (#14254) --- CHANGELOG.md | 68 ++++++++++++++++++--------- Cargo.lock | 6 +-- crates/uv-build/Cargo.toml | 2 +- crates/uv-build/pyproject.toml | 2 +- crates/uv-version/Cargo.toml | 2 +- crates/uv/Cargo.toml | 2 +- docs/concepts/build-backend.md | 2 +- 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/pre-commit.md | 10 ++-- pyproject.toml | 2 +- 13 files changed, 69 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 159c39331..7ef35e9a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,28 @@ +## 0.7.15 + +### Enhancements + +- Consistently use `Ordering::Relaxed` for standalone atomic use cases ([#14190](https://github.com/astral-sh/uv/pull/14190)) +- Warn on ambiguous relative paths for `--index` ([#14152](https://github.com/astral-sh/uv/pull/14152)) +- Skip GitHub fast path when rate-limited ([#13033](https://github.com/astral-sh/uv/pull/13033)) +- Preserve newlines in `schema.json` descriptions ([#13693](https://github.com/astral-sh/uv/pull/13693)) + +### Bug fixes + +- Add check for using minor version link when creating a venv on Windows ([#14252](https://github.com/astral-sh/uv/pull/14252)) +- Strip query parameters when parsing source URL ([#14224](https://github.com/astral-sh/uv/pull/14224)) + +### Documentation + +- Add a link to PyPI FAQ to clarify what per-project token is ([#14242](https://github.com/astral-sh/uv/pull/14242)) + +### Preview features + +- Allow symlinks in the build backend ([#14212](https://github.com/astral-sh/uv/pull/14212)) + ## 0.7.14 ### Enhancements @@ -386,11 +408,11 @@ This release contains various changes that improve correctness and user experien ### Breaking changes - **Update `uv version` to display and update project versions ([#12349](https://github.com/astral-sh/uv/pull/12349))** - + Previously, `uv version` displayed uv's version. Now, `uv version` will display or update the project's version. This interface was [heavily requested](https://github.com/astral-sh/uv/issues/6298) and, after much consideration, we decided that transitioning the top-level command was the best option. - + Here's a brief example: - + ```console $ uv init example Initialized project `example` at `./example` @@ -402,72 +424,72 @@ This release contains various changes that improve correctness and user experien $ uv version --short 1.0.0 ``` - + If used outside of a project, uv will fallback to showing its own version still: - + ```console $ uv version warning: failed to read project: No `pyproject.toml` found in current directory or any parent directory running `uv self version` for compatibility with old `uv version` command. this fallback will be removed soon, pass `--preview` to make this an error. - + uv 0.7.0 (4433f41c9 2025-04-29) ``` - + As described in the warning, `--preview` can be used to error instead: - + ```console $ uv version --preview error: No `pyproject.toml` found in current directory or any parent directory ``` - + The previous functionality of `uv version` was moved to `uv self version`. - **Avoid fallback to subsequent indexes on authentication failure ([#12805](https://github.com/astral-sh/uv/pull/12805))** - + When using the `first-index` strategy (the default), uv will stop searching indexes for a package once it is found on a single index. Previously, uv considered a package as "missing" from an index during authentication failures, such as an HTTP 401 or HTTP 403 (normally, missing packages are represented by an HTTP 404). This behavior was motivated by unusual responses from some package indexes, but reduces the safety of uv's index strategy when authentication fails. Now, uv will consider an authentication failure as a stop-point when searching for a package across indexes. The `index.ignore-error-codes` option can be used to recover the existing behavior, e.g.: - + ```toml [[tool.uv.index]] name = "pytorch" url = "https://download.pytorch.org/whl/cpu" ignore-error-codes = [401, 403] ``` - + Since PyTorch's indexes always return a HTTP 403 for missing packages, uv special-cases indexes on the `pytorch.org` domain to ignore that error code by default. - **Require the command in `uvx ` to be available in the Python environment ([#11603](https://github.com/astral-sh/uv/pull/11603))** - + Previously, `uvx` would attempt to execute a command even if it was not provided by a Python package. For example, if we presume `foo` is an empty Python package which provides no command, `uvx foo` would invoke the `foo` command on the `PATH` (if present). Now, uv will error early if the `foo` executable is not provided by the requested Python package. This check is not enforced when `--from` is used, so patterns like `uvx --from foo bash -c "..."` are still valid. uv also still allows `uvx foo` where the `foo` executable is provided by a dependency of `foo` instead of `foo` itself, as this is fairly common for packages which depend on a dedicated package for their command-line interface. - **Use index URL instead of package URL for keyring credential lookups ([#12651](https://github.com/astral-sh/uv/pull/12651))** - + When determining credentials for querying a package URL, uv previously sent the full URL to the `keyring` command. However, some keyring plugins expect to receive the *index URL* (which is usually a parent of the package URL). Now, uv requests credentials for the index URL instead. This behavior matches `pip`. - **Remove `--version` from subcommands ([#13108](https://github.com/astral-sh/uv/pull/13108))** - + Previously, uv allowed the `--version` flag on arbitrary subcommands, e.g., `uv run --version`. However, the `--version` flag is useful for other operations since uv is a package manager. Consequently, we've removed the `--version` flag from subcommands — it is only available as `uv --version`. - **Omit Python 3.7 downloads from managed versions ([#13022](https://github.com/astral-sh/uv/pull/13022))** - + Python 3.7 is EOL and not formally supported by uv; however, Python 3.7 was previously available for download on a subset of platforms. - **Reject non-PEP 751 TOML files in install, compile, and export commands ([#13120](https://github.com/astral-sh/uv/pull/13120), [#13119](https://github.com/astral-sh/uv/pull/13119))** - + Previously, uv treated arbitrary `.toml` files passed to commands (e.g., `uv pip install -r foo.toml` or `uv pip compile -o foo.toml`) as `requirements.txt`-formatted files. Now, uv will error instead. If using PEP 751 lockfiles, use the standardized format for custom names instead, e.g., `pylock.foo.toml`. - **Ignore arbitrary Python requests in version files ([#12909](https://github.com/astral-sh/uv/pull/12909))** - + uv allows arbitrary strings to be used for Python version requests, in which they are treated as an executable name to search for in the `PATH`. However, using this form of request in `.python-version` files is non-standard and conflicts with `pyenv-virtualenv` which writes environment names to `.python-version` files. In this release, uv will now ignore requests that are arbitrary strings when found in `.python-version` files. - **Error on unknown dependency object specifiers ([12811](https://github.com/astral-sh/uv/pull/12811))** - + The `[dependency-groups]` entries can include "object specifiers", e.g. `set-phasers-to = ...` in: - + ```toml [dependency-groups] foo = ["pyparsing"] bar = [{set-phasers-to = "stun"}] ``` - + However, the only current spec-compliant object specifier is `include-group`. Previously, uv would ignore unknown object specifiers. Now, uv will error. - **Make `--frozen` and `--no-sources` conflicting options ([#12671](https://github.com/astral-sh/uv/pull/12671))** - + Using `--no-sources` always requires a new resolution and `--frozen` will always fail when used with it. Now, this conflict is encoded in the CLI options for clarity. - **Treat empty `UV_PYTHON_INSTALL_DIR` and `UV_TOOL_DIR` as unset ([#12907](https://github.com/astral-sh/uv/pull/12907), [#12905](https://github.com/astral-sh/uv/pull/12905))** - + Previously, these variables were treated as set to the current working directory when set to an empty string. Now, uv will ignore these variables when empty. This matches uv's behavior for other environment variables which configure directories. ### Enhancements diff --git a/Cargo.lock b/Cargo.lock index 781f98079..75b6fe7e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4570,7 +4570,7 @@ dependencies = [ [[package]] name = "uv" -version = "0.7.14" +version = "0.7.15" dependencies = [ "anstream", "anyhow", @@ -4735,7 +4735,7 @@ dependencies = [ [[package]] name = "uv-build" -version = "0.7.14" +version = "0.7.15" dependencies = [ "anyhow", "uv-build-backend", @@ -5923,7 +5923,7 @@ dependencies = [ [[package]] name = "uv-version" -version = "0.7.14" +version = "0.7.15" [[package]] name = "uv-virtualenv" diff --git a/crates/uv-build/Cargo.toml b/crates/uv-build/Cargo.toml index 26c046b2b..94b4adbc3 100644 --- a/crates/uv-build/Cargo.toml +++ b/crates/uv-build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-build" -version = "0.7.14" +version = "0.7.15" edition.workspace = true rust-version.workspace = true homepage.workspace = true diff --git a/crates/uv-build/pyproject.toml b/crates/uv-build/pyproject.toml index 4dab6a520..4b8c522d6 100644 --- a/crates/uv-build/pyproject.toml +++ b/crates/uv-build/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uv-build" -version = "0.7.14" +version = "0.7.15" description = "The uv build backend" authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }] requires-python = ">=3.8" diff --git a/crates/uv-version/Cargo.toml b/crates/uv-version/Cargo.toml index b92014be4..9eef680a2 100644 --- a/crates/uv-version/Cargo.toml +++ b/crates/uv-version/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-version" -version = "0.7.14" +version = "0.7.15" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml index e63cbfe40..f4afb87fa 100644 --- a/crates/uv/Cargo.toml +++ b/crates/uv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv" -version = "0.7.14" +version = "0.7.15" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } diff --git a/docs/concepts/build-backend.md b/docs/concepts/build-backend.md index 2162aaaa5..2e93cc546 100644 --- a/docs/concepts/build-backend.md +++ b/docs/concepts/build-backend.md @@ -19,7 +19,7 @@ existing project, add it to the `[build-system]` section in your `pyproject.toml ```toml [build-system] -requires = ["uv_build>=0.7.14,<0.8.0"] +requires = ["uv_build>=0.7.15,<0.8.0"] build-backend = "uv_build" ``` diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index a87ce695a..33281a0c8 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.7.14/install.sh | sh + $ curl -LsSf https://astral.sh/uv/0.7.15/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.7.14/install.ps1 | iex" + PS> powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/0.7.15/install.ps1 | iex" ``` !!! tip diff --git a/docs/guides/integration/aws-lambda.md b/docs/guides/integration/aws-lambda.md index 467088736..b48dba1b1 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.7.14 AS uv +FROM ghcr.io/astral-sh/uv:0.7.15 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.7.14 AS uv +FROM ghcr.io/astral-sh/uv:0.7.15 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 01e66f775..d8e6d69a3 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.7.14` +- `ghcr.io/astral-sh/uv:{major}.{minor}.{patch}`, e.g., `ghcr.io/astral-sh/uv:0.7.15` - `ghcr.io/astral-sh/uv:{major}.{minor}`, e.g., `ghcr.io/astral-sh/uv:0.7` (the latest patch version) @@ -75,7 +75,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.7.14-alpine`. +`ghcr.io/astral-sh/uv:{major}.{minor}-{base}`, e.g., `ghcr.io/astral-sh/uv:0.7.15-alpine`. For more details, see the [GitHub Container](https://github.com/astral-sh/uv/pkgs/container/uv) page. @@ -113,7 +113,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.7.14 /uv /uvx /bin/ +COPY --from=ghcr.io/astral-sh/uv:0.7.15 /uv /uvx /bin/ ``` !!! tip @@ -131,7 +131,7 @@ COPY --from=ghcr.io/astral-sh/uv:0.7.14 /uv /uvx /bin/ Or, with the installer: ```dockerfile -ADD https://astral.sh/uv/0.7.14/install.sh /uv-installer.sh +ADD https://astral.sh/uv/0.7.15/install.sh /uv-installer.sh ``` ### Installing a project @@ -557,5 +557,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.7.14`, or (even better) the specific image digest, + version tag, e.g., `ghcr.io/astral-sh/uv:0.7.15`, 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 0bd1cc1b1..ea96199a2 100644 --- a/docs/guides/integration/github.md +++ b/docs/guides/integration/github.md @@ -47,7 +47,7 @@ jobs: uses: astral-sh/setup-uv@v5 with: # Install a specific version of uv. - version: "0.7.14" + version: "0.7.15" ``` ## Setting up Python diff --git a/docs/guides/integration/pre-commit.md b/docs/guides/integration/pre-commit.md index 326832e7e..9e78d1595 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.7.14 + rev: 0.7.15 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.7.14 + rev: 0.7.15 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.7.14 + rev: 0.7.15 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.7.14 + rev: 0.7.15 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.7.14 + rev: 0.7.15 hooks: # Compile requirements - id: pip-compile diff --git a/pyproject.toml b/pyproject.toml index 86dd0a1e2..5ede7780c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "uv" -version = "0.7.14" +version = "0.7.15" 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" From 4b348512c270d4fc45d4c5ab55df0bae4e2d93cd Mon Sep 17 00:00:00 2001 From: Daniel Vianna <1708810+dmvianna@users.noreply.github.com> Date: Thu, 26 Jun 2025 01:35:41 +1000 Subject: [PATCH 060/349] GCP Artifact Registry download URLs must have /simple path (#14251) --- docs/guides/integration/alternative-indexes.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/guides/integration/alternative-indexes.md b/docs/guides/integration/alternative-indexes.md index 52ec6e365..0258e4a74 100644 --- a/docs/guides/integration/alternative-indexes.md +++ b/docs/guides/integration/alternative-indexes.md @@ -142,7 +142,7 @@ To use Google Artifact Registry, add the index to your project: ```toml title="pyproject.toml" [[tool.uv.index]] name = "private-registry" -url = "https://-python.pkg.dev//" +url = "https://-python.pkg.dev///simple/" ``` ### Authenticate with a Google access token @@ -219,8 +219,8 @@ First, add a `publish-url` to the index you want to publish packages to. For exa ```toml title="pyproject.toml" hl_lines="4" [[tool.uv.index]] name = "private-registry" -url = "https://-python.pkg.dev//" -publish-url = "https://-python.pkg.dev//" +url = "https://-python.pkg.dev///simple/" +publish-url = "https://-python.pkg.dev///" ``` Then, configure credentials (if not using keyring): @@ -239,7 +239,7 @@ $ uv publish --index private-registry To use `uv publish` without adding the `publish-url` to the project, you can set `UV_PUBLISH_URL`: ```console -$ export UV_PUBLISH_URL=https://-python.pkg.dev// +$ export UV_PUBLISH_URL=https://-python.pkg.dev/// $ uv publish ``` From a27e60a22fb3cc891083c8c322e946125e0bbd62 Mon Sep 17 00:00:00 2001 From: John Mumm Date: Thu, 26 Jun 2025 16:47:18 +0200 Subject: [PATCH 061/349] Temporarily disable Artifactory registry test (#14276) I'm waiting on a response to get our subscription back up. Then I can re-enable this. But for now, this would cause failing CI tests. --- .github/workflows/ci.yml | 6 +++--- scripts/registries-test.py | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b33f96fe2..0ef7244d5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1480,9 +1480,9 @@ jobs: run: ./uv run -p ${{ env.PYTHON_VERSION }} scripts/registries-test.py --uv ./uv --color always --all env: RUST_LOG: uv=debug - UV_TEST_ARTIFACTORY_TOKEN: ${{ secrets.UV_TEST_ARTIFACTORY_TOKEN }} - UV_TEST_ARTIFACTORY_URL: ${{ secrets.UV_TEST_ARTIFACTORY_URL }} - UV_TEST_ARTIFACTORY_USERNAME: ${{ secrets.UV_TEST_ARTIFACTORY_USERNAME }} + # UV_TEST_ARTIFACTORY_TOKEN: ${{ secrets.UV_TEST_ARTIFACTORY_TOKEN }} + # UV_TEST_ARTIFACTORY_URL: ${{ secrets.UV_TEST_ARTIFACTORY_URL }} + # UV_TEST_ARTIFACTORY_USERNAME: ${{ secrets.UV_TEST_ARTIFACTORY_USERNAME }} UV_TEST_AWS_URL: ${{ secrets.UV_TEST_AWS_URL }} UV_TEST_AWS_USERNAME: aws UV_TEST_AZURE_TOKEN: ${{ secrets.UV_TEST_AZURE_TOKEN }} diff --git a/scripts/registries-test.py b/scripts/registries-test.py index 2d4c1d2aa..b6bbf9b59 100644 --- a/scripts/registries-test.py +++ b/scripts/registries-test.py @@ -56,7 +56,8 @@ DEFAULT_TIMEOUT = 30 DEFAULT_PKG_NAME = "astral-registries-test-pkg" KNOWN_REGISTRIES = [ - "artifactory", + # TODO(john): Restore this when subscription starts up again + # "artifactory", "azure", "aws", "cloudsmith", From 469246d177a9e7d36d62a63b83f1befb47aff48e Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 26 Jun 2025 09:58:24 -0500 Subject: [PATCH 062/349] Fix `emit_index_annotation_multiple_indexes` test case (#14277) uv is taken on Test PyPI now, so the existing test fails --- crates/uv/tests/it/pip_compile.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/crates/uv/tests/it/pip_compile.rs b/crates/uv/tests/it/pip_compile.rs index 49e78a5c9..4b4d231dd 100644 --- a/crates/uv/tests/it/pip_compile.rs +++ b/crates/uv/tests/it/pip_compile.rs @@ -12806,28 +12806,34 @@ fn emit_index_annotation_multiple_indexes() -> Result<()> { let context = TestContext::new("3.12"); let requirements_in = context.temp_dir.child("requirements.in"); - requirements_in.write_str("uv\nrequests")?; + requirements_in.write_str("httpcore\nrequests")?; uv_snapshot!(context.filters(), context.pip_compile() .arg("requirements.in") .arg("--extra-index-url") .arg("https://test.pypi.org/simple") - .arg("--emit-index-annotation"), @r###" + .arg("--emit-index-annotation"), @r" success: true exit_code: 0 ----- stdout ----- # This file was autogenerated by uv via the following command: # uv pip compile --cache-dir [CACHE_DIR] requirements.in --emit-index-annotation + certifi==2016.8.8 + # via httpcore + # from https://test.pypi.org/simple + h11==0.14.0 + # via httpcore + # from https://pypi.org/simple + httpcore==1.0.4 + # via -r requirements.in + # from https://pypi.org/simple requests==2.5.4.1 # via -r requirements.in # from https://test.pypi.org/simple - uv==0.1.24 - # via -r requirements.in - # from https://pypi.org/simple ----- stderr ----- - Resolved 2 packages in [TIME] - "### + Resolved 4 packages in [TIME] + " ); Ok(()) From 1e02008d8bf5a79a89edb7d17228370efe477606 Mon Sep 17 00:00:00 2001 From: Aria Desires Date: Thu, 26 Jun 2025 12:05:45 -0400 Subject: [PATCH 063/349] add more proper docker login if (#14278) --- .github/workflows/build-docker.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 9a2e5ba1d..272a69e9a 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -45,6 +45,7 @@ jobs: name: plan runs-on: ubuntu-latest outputs: + login: ${{ steps.plan.outputs.login }} push: ${{ steps.plan.outputs.push }} tag: ${{ steps.plan.outputs.tag }} action: ${{ steps.plan.outputs.action }} @@ -53,13 +54,16 @@ jobs: env: DRY_RUN: ${{ inputs.plan == '' || fromJson(inputs.plan).announcement_tag_is_implicit }} TAG: ${{ inputs.plan != '' && fromJson(inputs.plan).announcement_tag }} + IS_LOCAL_PR: ${{ github.event.pull_request.head.repo.full_name == 'astral-sh/uv' }} id: plan run: | if [ "${{ env.DRY_RUN }}" == "false" ]; then + echo "login=true" >> "$GITHUB_OUTPUT" echo "push=true" >> "$GITHUB_OUTPUT" echo "tag=${{ env.TAG }}" >> "$GITHUB_OUTPUT" echo "action=build and publish" >> "$GITHUB_OUTPUT" else + echo "login=${{ env.IS_LOCAL_PR }}" >> "$GITHUB_OUTPUT" echo "push=false" >> "$GITHUB_OUTPUT" echo "tag=dry-run" >> "$GITHUB_OUTPUT" echo "action=build" >> "$GITHUB_OUTPUT" @@ -90,6 +94,7 @@ jobs: # Login to DockerHub (when not pushing, it's to avoid rate-limiting) - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 + if: ${{ needs.docker-plan.outputs.login == 'true' }} with: username: ${{ needs.docker-plan.outputs.push == 'true' && 'astral' || 'astralshbot' }} password: ${{ needs.docker-plan.outputs.push == 'true' && secrets.DOCKERHUB_TOKEN_RW || secrets.DOCKERHUB_TOKEN_RO }} @@ -195,6 +200,7 @@ jobs: steps: # Login to DockerHub (when not pushing, it's to avoid rate-limiting) - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 + if: ${{ needs.docker-plan.outputs.login == 'true' }} with: username: ${{ needs.docker-plan.outputs.push == 'true' && 'astral' || 'astralshbot' }} password: ${{ needs.docker-plan.outputs.push == 'true' && secrets.DOCKERHUB_TOKEN_RW || secrets.DOCKERHUB_TOKEN_RO }} From d27cec78b405422310c670c9f9c2aa060c54186a Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 26 Jun 2025 11:23:37 -0500 Subject: [PATCH 064/349] Restore snapshot for `sync_dry_run` (#14274) In addition to our flake catch, keep a snapshot. Extends https://github.com/astral-sh/uv/pull/13817 --- crates/uv/tests/it/sync.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index 1ce892050..4187de957 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -8173,6 +8173,8 @@ fn sync_dry_run() -> Result<()> { + iniconfig==2.0.0 "); + // TMP: Attempt to catch this flake with verbose output + // See https://github.com/astral-sh/uv/issues/13744 let output = context.sync().arg("--dry-run").arg("-vv").output()?; let stderr = String::from_utf8_lossy(&output.stderr); assert!( @@ -8181,6 +8183,19 @@ fn sync_dry_run() -> Result<()> { stderr ); + uv_snapshot!(context.filters(), context.sync().arg("--dry-run"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Discovered existing environment at: .venv + Resolved 2 packages in [TIME] + Found up-to-date lockfile at: uv.lock + Audited 1 package in [TIME] + Would make no changes + "); + Ok(()) } From 8c27c2b494dea00ec98f002bdcb6e6dde6df90d8 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 26 Jun 2025 12:11:34 -0500 Subject: [PATCH 065/349] Add verbose output on flake for `run_groups_requires_python` (#14275) See https://github.com/astral-sh/uv/issues/14160 Same as https://github.com/astral-sh/uv/pull/13817 --- crates/uv/tests/it/run.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/crates/uv/tests/it/run.rs b/crates/uv/tests/it/run.rs index 65d13c527..c2c9bc7a4 100644 --- a/crates/uv/tests/it/run.rs +++ b/crates/uv/tests/it/run.rs @@ -4686,6 +4686,22 @@ fn run_groups_requires_python() -> Result<()> { + typing-extensions==4.10.0 "); + // TMP: Attempt to catch this flake with verbose output + // See https://github.com/astral-sh/uv/issues/14160 + let output = context + .run() + .arg("python") + .arg("-c") + .arg("import typing_extensions") + .arg("-vv") + .output()?; + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + !stderr.contains("Removed virtual environment"), + "{}", + stderr + ); + // Going back to just "dev" we shouldn't churn the venv needlessly uv_snapshot!(context.filters(), context.run() .arg("python").arg("-c").arg("import typing_extensions"), @r" From 1ff8fc09475fc405971c89efd9d72f1df526eb0b Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 26 Jun 2025 13:09:04 -0500 Subject: [PATCH 066/349] Use Flit instead of Poetry for uninstall tests (#14285) Investigating https://github.com/astral-sh/uv/issues/14158 --- crates/uv/tests/it/pip_uninstall.rs | 32 ++-- scripts/packages/flit_editable/.gitignore | 162 ++++++++++++++++++ .../flit_editable/flit_editable/__init__.py | 6 + scripts/packages/flit_editable/pyproject.toml | 17 ++ 4 files changed, 201 insertions(+), 16 deletions(-) create mode 100644 scripts/packages/flit_editable/.gitignore create mode 100644 scripts/packages/flit_editable/flit_editable/__init__.py create mode 100644 scripts/packages/flit_editable/pyproject.toml diff --git a/crates/uv/tests/it/pip_uninstall.rs b/crates/uv/tests/it/pip_uninstall.rs index 5e6cbf6f9..c72b92876 100644 --- a/crates/uv/tests/it/pip_uninstall.rs +++ b/crates/uv/tests/it/pip_uninstall.rs @@ -176,7 +176,7 @@ fn uninstall_editable_by_name() -> Result<()> { "-e {}", context .workspace_root - .join("scripts/packages/poetry_editable") + .join("scripts/packages/flit_editable") .as_os_str() .to_str() .expect("Path is valid unicode") @@ -187,22 +187,22 @@ fn uninstall_editable_by_name() -> Result<()> { .assert() .success(); - context.assert_command("import poetry_editable").success(); + context.assert_command("import flit_editable").success(); // Uninstall the editable by name. uv_snapshot!(context.filters(), context.pip_uninstall() - .arg("poetry-editable"), @r###" + .arg("flit-editable"), @r###" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Uninstalled 1 package in [TIME] - - poetry-editable==0.1.0 (from file://[WORKSPACE]/scripts/packages/poetry_editable) + - flit-editable==0.1.0 (from file://[WORKSPACE]/scripts/packages/flit_editable) "### ); - context.assert_command("import poetry_editable").failure(); + context.assert_command("import flit_editable").failure(); Ok(()) } @@ -216,7 +216,7 @@ fn uninstall_by_path() -> Result<()> { requirements_txt.write_str( context .workspace_root - .join("scripts/packages/poetry_editable") + .join("scripts/packages/flit_editable") .as_os_str() .to_str() .expect("Path is valid unicode"), @@ -228,22 +228,22 @@ fn uninstall_by_path() -> Result<()> { .assert() .success(); - context.assert_command("import poetry_editable").success(); + context.assert_command("import flit_editable").success(); // Uninstall the editable by path. uv_snapshot!(context.filters(), context.pip_uninstall() - .arg(context.workspace_root.join("scripts/packages/poetry_editable")), @r###" + .arg(context.workspace_root.join("scripts/packages/flit_editable")), @r###" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Uninstalled 1 package in [TIME] - - poetry-editable==0.1.0 (from file://[WORKSPACE]/scripts/packages/poetry_editable) + - flit-editable==0.1.0 (from file://[WORKSPACE]/scripts/packages/flit_editable) "### ); - context.assert_command("import poetry_editable").failure(); + context.assert_command("import flit_editable").failure(); Ok(()) } @@ -257,7 +257,7 @@ fn uninstall_duplicate_by_path() -> Result<()> { requirements_txt.write_str( context .workspace_root - .join("scripts/packages/poetry_editable") + .join("scripts/packages/flit_editable") .as_os_str() .to_str() .expect("Path is valid unicode"), @@ -269,23 +269,23 @@ fn uninstall_duplicate_by_path() -> Result<()> { .assert() .success(); - context.assert_command("import poetry_editable").success(); + context.assert_command("import flit_editable").success(); // Uninstall the editable by both path and name. uv_snapshot!(context.filters(), context.pip_uninstall() - .arg("poetry-editable") - .arg(context.workspace_root.join("scripts/packages/poetry_editable")), @r###" + .arg("flit-editable") + .arg(context.workspace_root.join("scripts/packages/flit_editable")), @r###" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Uninstalled 1 package in [TIME] - - poetry-editable==0.1.0 (from file://[WORKSPACE]/scripts/packages/poetry_editable) + - flit-editable==0.1.0 (from file://[WORKSPACE]/scripts/packages/flit_editable) "### ); - context.assert_command("import poetry_editable").failure(); + context.assert_command("import flit_editable").failure(); Ok(()) } diff --git a/scripts/packages/flit_editable/.gitignore b/scripts/packages/flit_editable/.gitignore new file mode 100644 index 000000000..3a8816c9e --- /dev/null +++ b/scripts/packages/flit_editable/.gitignore @@ -0,0 +1,162 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm-project.org/#use-with-ide +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/scripts/packages/flit_editable/flit_editable/__init__.py b/scripts/packages/flit_editable/flit_editable/__init__.py new file mode 100644 index 000000000..4076f6c86 --- /dev/null +++ b/scripts/packages/flit_editable/flit_editable/__init__.py @@ -0,0 +1,6 @@ +def main(): + print("Hello world!") + + +if __name__ == "__main__": + main() diff --git a/scripts/packages/flit_editable/pyproject.toml b/scripts/packages/flit_editable/pyproject.toml new file mode 100644 index 000000000..02f431543 --- /dev/null +++ b/scripts/packages/flit_editable/pyproject.toml @@ -0,0 +1,17 @@ +[project] +name = "flit-editable" +version = "0.1.0" +description = "Example Flit project" +authors = [ + {name = "konstin", email = "konstin@mailbox.org"}, +] +dependencies = [] +requires-python = ">=3.11" +license = {text = "MIT"} + +[project.scripts] +flit-editable = "flit_editable:main" + +[build-system] +requires = ["flit_core>=3.4,<4"] +build-backend = "flit_core.buildapi" From 60528e3e253d9c3d14dc129df68030a0a407c50d Mon Sep 17 00:00:00 2001 From: Jack O'Connor Date: Fri, 20 Jun 2025 16:00:13 -0700 Subject: [PATCH 067/349] Annotate LockedFile with #[must_use] Standard lock guards have the same annotation, because creating them without binding them to a local variable is almost always a mistake. --- crates/uv-fs/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/uv-fs/src/lib.rs b/crates/uv-fs/src/lib.rs index ad4a883ad..dcc0f00b2 100644 --- a/crates/uv-fs/src/lib.rs +++ b/crates/uv-fs/src/lib.rs @@ -601,6 +601,7 @@ pub fn is_virtualenv_base(path: impl AsRef) -> bool { /// A file lock that is automatically released when dropped. #[derive(Debug)] +#[must_use] pub struct LockedFile(fs_err::File); impl LockedFile { From d4d6ede23b95cdd245efb57b97b4fcd6f979e97c Mon Sep 17 00:00:00 2001 From: Jack O'Connor Date: Fri, 20 Jun 2025 16:04:55 -0700 Subject: [PATCH 068/349] Lock the source tree when running setuptools, to protect concurrent builds Fixes https://github.com/astral-sh/uv/issues/13703 --- Cargo.lock | 1 + crates/uv-build-frontend/Cargo.toml | 1 + crates/uv-build-frontend/src/lib.rs | 47 +++++++++++++++++++++-------- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 75b6fe7e4..009b3060f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4798,6 +4798,7 @@ dependencies = [ "tokio", "toml_edit", "tracing", + "uv-cache-key", "uv-configuration", "uv-distribution", "uv-distribution-types", diff --git a/crates/uv-build-frontend/Cargo.toml b/crates/uv-build-frontend/Cargo.toml index 83f8008d9..748e7bb28 100644 --- a/crates/uv-build-frontend/Cargo.toml +++ b/crates/uv-build-frontend/Cargo.toml @@ -17,6 +17,7 @@ doctest = false workspace = true [dependencies] +uv-cache-key = { workspace = true } uv-configuration = { workspace = true } uv-distribution = { workspace = true } uv-distribution-types = { workspace = true } diff --git a/crates/uv-build-frontend/src/lib.rs b/crates/uv-build-frontend/src/lib.rs index 34037ffdd..df6fc09cf 100644 --- a/crates/uv-build-frontend/src/lib.rs +++ b/crates/uv-build-frontend/src/lib.rs @@ -27,10 +27,12 @@ use tokio::process::Command; use tokio::sync::{Mutex, Semaphore}; use tracing::{Instrument, debug, info_span, instrument}; +use uv_cache_key::cache_digest; use uv_configuration::PreviewMode; use uv_configuration::{BuildKind, BuildOutput, ConfigSettings, SourceStrategy}; use uv_distribution::BuildRequires; use uv_distribution_types::{IndexLocations, Requirement, Resolution}; +use uv_fs::LockedFile; use uv_fs::{PythonExt, Simplified}; use uv_pep440::Version; use uv_pep508::PackageName; @@ -201,6 +203,11 @@ impl Pep517Backend { {import} "#, backend_path = backend_path_encoded} } + + fn is_setuptools(&self) -> bool { + // either `setuptools.build_meta` or `setuptools.build_meta:__legacy__` + self.backend.split(':').next() == Some("setuptools.build_meta") + } } /// Uses an [`Rc`] internally, clone freely. @@ -252,6 +259,8 @@ pub struct SourceBuild { environment_variables: FxHashMap, /// Runner for Python scripts. runner: PythonRunner, + /// A file lock representing the source tree, currently only used with setuptools. + _source_tree_lock: Option, } impl SourceBuild { @@ -385,6 +394,23 @@ impl SourceBuild { OsString::from(venv.scripts()) }; + // Depending on the command, setuptools puts `*.egg-info`, `build/`, and `dist/` in the + // source tree, and concurrent invocations of setuptools using the same source dir can + // stomp on each other. We need to lock something to fix that, but we don't want to dump a + // `.lock` file into the source tree that the user will need to .gitignore. Take a global + // proxy lock instead. + let mut source_tree_lock = None; + if pep517_backend.is_setuptools() { + debug!("Locking the source tree for setuptools"); + let canonical_source_path = source_tree.canonicalize()?; + let lock_path = std::env::temp_dir().join(format!( + "uv-setuptools-{}.lock", + cache_digest(&canonical_source_path) + )); + source_tree_lock = + Some(LockedFile::acquire(lock_path, source_tree.to_string_lossy()).await?); + } + // Create the PEP 517 build environment. If build isolation is disabled, we assume the build // environment is already setup. let runner = PythonRunner::new(concurrent_builds, level); @@ -431,6 +457,7 @@ impl SourceBuild { environment_variables, modified_path, runner, + _source_tree_lock: source_tree_lock, }) } @@ -716,16 +743,12 @@ impl SourceBuild { pub async fn build(&self, wheel_dir: &Path) -> Result { // The build scripts run with the extracted root as cwd, so they need the absolute path. let wheel_dir = std::path::absolute(wheel_dir)?; - let filename = self.pep517_build(&wheel_dir, &self.pep517_backend).await?; + let filename = self.pep517_build(&wheel_dir).await?; Ok(filename) } /// Perform a PEP 517 build for a wheel or source distribution (sdist). - async fn pep517_build( - &self, - output_dir: &Path, - pep517_backend: &Pep517Backend, - ) -> Result { + async fn pep517_build(&self, output_dir: &Path) -> Result { // Write the hook output to a file so that we can read it back reliably. let outfile = self .temp_dir @@ -737,7 +760,7 @@ impl SourceBuild { BuildKind::Sdist => { debug!( r#"Calling `{}.build_{}("{}", {})`"#, - pep517_backend.backend, + self.pep517_backend.backend, self.build_kind, output_dir.escape_for_python(), self.config_settings.escape_for_python(), @@ -750,7 +773,7 @@ impl SourceBuild { with open("{}", "w") as fp: fp.write(sdist_filename) "#, - pep517_backend.backend_import(), + self.pep517_backend.backend_import(), self.build_kind, output_dir.escape_for_python(), self.config_settings.escape_for_python(), @@ -766,7 +789,7 @@ impl SourceBuild { }); debug!( r#"Calling `{}.build_{}("{}", {}, {})`"#, - pep517_backend.backend, + self.pep517_backend.backend, self.build_kind, output_dir.escape_for_python(), self.config_settings.escape_for_python(), @@ -780,7 +803,7 @@ impl SourceBuild { with open("{}", "w") as fp: fp.write(wheel_filename) "#, - pep517_backend.backend_import(), + self.pep517_backend.backend_import(), self.build_kind, output_dir.escape_for_python(), self.config_settings.escape_for_python(), @@ -810,7 +833,7 @@ impl SourceBuild { return Err(Error::from_command_output( format!( "Call to `{}.build_{}` failed", - pep517_backend.backend, self.build_kind + self.pep517_backend.backend, self.build_kind ), &output, self.level, @@ -825,7 +848,7 @@ impl SourceBuild { return Err(Error::from_command_output( format!( "Call to `{}.build_{}` failed", - pep517_backend.backend, self.build_kind + self.pep517_backend.backend, self.build_kind ), &output, self.level, From c291d4329ac31b0253dcd79fe187edd066145acc Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 26 Jun 2025 15:42:04 -0400 Subject: [PATCH 069/349] Include path or URL when failing to convert in lockfile (#14292) ## Summary E.g., in #14227, we now get: ``` error: Failed to convert URL to path: https://cdn.jsdelivr.net/pyodide/v0.27.7/full/sniffio-1.3.1-py3-none-any.whl ``` --- crates/uv-resolver/src/lock/mod.rs | 54 ++++++++++++++++++------------ 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/crates/uv-resolver/src/lock/mod.rs b/crates/uv-resolver/src/lock/mod.rs index 0b72014a8..cee6364a9 100644 --- a/crates/uv-resolver/src/lock/mod.rs +++ b/crates/uv-resolver/src/lock/mod.rs @@ -2545,9 +2545,12 @@ impl Package { name: name.clone(), version: version.clone(), })?; - let file_url = - DisplaySafeUrl::from_file_path(workspace_root.join(path).join(file_path)) - .map_err(|()| LockErrorKind::PathToUrl)?; + let file_path = workspace_root.join(path).join(file_path); + let file_url = DisplaySafeUrl::from_file_path(&file_path).map_err(|()| { + LockErrorKind::PathToUrl { + path: file_path.into_boxed_path(), + } + })?; let filename = sdist .filename() .ok_or_else(|| LockErrorKind::MissingFilename { @@ -3250,7 +3253,9 @@ impl Source { Ok(Source::Registry(source)) } IndexUrl::Path(url) => { - let path = url.to_file_path().map_err(|()| LockErrorKind::UrlToPath)?; + let path = url + .to_file_path() + .map_err(|()| LockErrorKind::UrlToPath { url: url.to_url() })?; let path = relative_to(&path, root) .or_else(|_| std::path::absolute(&path)) .map_err(LockErrorKind::IndexRelativePath)?; @@ -3810,14 +3815,17 @@ impl SourceDist { })) } IndexUrl::Path(path) => { - let index_path = path.to_file_path().map_err(|()| LockErrorKind::UrlToPath)?; - let reg_dist_path = reg_dist + let index_path = path + .to_file_path() + .map_err(|()| LockErrorKind::UrlToPath { url: path.to_url() })?; + let reg_dist_url = reg_dist .file .url .to_url() - .map_err(LockErrorKind::InvalidUrl)? + .map_err(LockErrorKind::InvalidUrl)?; + let reg_dist_path = reg_dist_url .to_file_path() - .map_err(|()| LockErrorKind::UrlToPath)?; + .map_err(|()| LockErrorKind::UrlToPath { url: reg_dist_url })?; let path = relative_to(®_dist_path, index_path) .or_else(|_| std::path::absolute(®_dist_path)) .map_err(LockErrorKind::DistributionRelativePath)? @@ -4140,14 +4148,13 @@ impl Wheel { }) } IndexUrl::Path(path) => { - let index_path = path.to_file_path().map_err(|()| LockErrorKind::UrlToPath)?; - let wheel_path = wheel - .file - .url - .to_url() - .map_err(LockErrorKind::InvalidUrl)? + let index_path = path .to_file_path() - .map_err(|()| LockErrorKind::UrlToPath)?; + .map_err(|()| LockErrorKind::UrlToPath { url: path.to_url() })?; + let wheel_url = wheel.file.url.to_url().map_err(LockErrorKind::InvalidUrl)?; + let wheel_path = wheel_url + .to_file_path() + .map_err(|()| LockErrorKind::UrlToPath { url: wheel_url })?; let path = relative_to(&wheel_path, index_path) .or_else(|_| std::path::absolute(&wheel_path)) .map_err(LockErrorKind::DistributionRelativePath)? @@ -4236,9 +4243,12 @@ impl Wheel { .into()); } }; - let file_url = - DisplaySafeUrl::from_file_path(root.join(index_path).join(file_path)) - .map_err(|()| LockErrorKind::PathToUrl)?; + let file_path = root.join(index_path).join(file_path); + let file_url = DisplaySafeUrl::from_file_path(&file_path).map_err(|()| { + LockErrorKind::PathToUrl { + path: file_path.into_boxed_path(), + } + })?; let file = Box::new(uv_distribution_types::File { dist_info_metadata: false, filename: SmallString::from(filename.to_string()), @@ -5449,11 +5459,11 @@ enum LockErrorKind { VerbatimUrlError, ), /// An error that occurs when converting a path to a URL. - #[error("Failed to convert path to URL")] - PathToUrl, + #[error("Failed to convert path to URL: {path}", path = path.display().cyan())] + PathToUrl { path: Box }, /// An error that occurs when converting a URL to a path - #[error("Failed to convert URL to path")] - UrlToPath, + #[error("Failed to convert URL to path: {url}", url = url.cyan())] + UrlToPath { url: DisplaySafeUrl }, /// An error that occurs when multiple packages with the same /// name were found when identifying the root packages. #[error("Found multiple packages matching `{name}`", name = name.cyan())] From 05ab266200b47e430a3d3f5fcebd164840c0b652 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 26 Jun 2025 15:48:12 -0400 Subject: [PATCH 070/349] Avoid using path URL for workspace Git dependencies in `requirements.txt` (#14288) ## Summary Closes https://github.com/astral-sh/uv/issues/13020. --- .../uv-distribution/src/metadata/lowering.rs | 22 ++++++++----- crates/uv-redacted/src/lib.rs | 4 ++- crates/uv/tests/it/pip_compile.rs | 33 +++++++++++++++++++ 3 files changed, 50 insertions(+), 9 deletions(-) diff --git a/crates/uv-distribution/src/metadata/lowering.rs b/crates/uv-distribution/src/metadata/lowering.rs index dd0974a99..330075842 100644 --- a/crates/uv-distribution/src/metadata/lowering.rs +++ b/crates/uv-distribution/src/metadata/lowering.rs @@ -13,7 +13,7 @@ use uv_git_types::{GitReference, GitUrl, GitUrlParseError}; use uv_normalize::{ExtraName, GroupName, PackageName}; use uv_pep440::VersionSpecifiers; use uv_pep508::{MarkerTree, VerbatimUrl, VersionOrUrl, looks_like_git_repository}; -use uv_pypi_types::{ConflictItem, ParsedUrlError, VerbatimParsedUrl}; +use uv_pypi_types::{ConflictItem, ParsedGitUrl, ParsedUrlError, VerbatimParsedUrl}; use uv_redacted::DisplaySafeUrl; use uv_workspace::Workspace; use uv_workspace::pyproject::{PyProjectToml, Source, Sources}; @@ -700,17 +700,23 @@ fn path_source( }; if is_dir { if let Some(git_member) = git_member { + let git = git_member.git_source.git.clone(); let subdirectory = uv_fs::relative_to(install_path, git_member.fetch_root) .expect("Workspace member must be relative"); let subdirectory = uv_fs::normalize_path_buf(subdirectory); + let subdirectory = if subdirectory == PathBuf::new() { + None + } else { + Some(subdirectory.into_boxed_path()) + }; + let url = DisplaySafeUrl::from(ParsedGitUrl { + url: git.clone(), + subdirectory: subdirectory.clone(), + }); return Ok(RequirementSource::Git { - git: git_member.git_source.git.clone(), - subdirectory: if subdirectory == PathBuf::new() { - None - } else { - Some(subdirectory.into_boxed_path()) - }, - url, + git, + subdirectory, + url: VerbatimUrl::from_url(url), }); } diff --git a/crates/uv-redacted/src/lib.rs b/crates/uv-redacted/src/lib.rs index cd023ccbf..5c9a8e278 100644 --- a/crates/uv-redacted/src/lib.rs +++ b/crates/uv-redacted/src/lib.rs @@ -177,7 +177,9 @@ impl FromStr for DisplaySafeUrl { } fn is_ssh_git_username(url: &Url) -> bool { - matches!(url.scheme(), "ssh" | "git+ssh") && url.username() == "git" && url.password().is_none() + matches!(url.scheme(), "ssh" | "git+ssh" | "git+https") + && url.username() == "git" + && url.password().is_none() } fn display_with_redacted_credentials( diff --git a/crates/uv/tests/it/pip_compile.rs b/crates/uv/tests/it/pip_compile.rs index 4b4d231dd..5adfeb7f8 100644 --- a/crates/uv/tests/it/pip_compile.rs +++ b/crates/uv/tests/it/pip_compile.rs @@ -17621,3 +17621,36 @@ fn pubgrub_panic_double_self_dependency_extra() -> Result<()> { Ok(()) } + +/// Sync a Git repository that depends on a package within the same repository via a `path` source. +/// +/// See: +#[test] +#[cfg(feature = "git")] +fn git_path_transitive_dependency() -> Result<()> { + let context = TestContext::new("3.13"); + + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str( + r" + git+https://git@github.com/astral-sh/uv-path-dependency-test.git#subdirectory=package2 + ", + )?; + + uv_snapshot!(context.filters(), context.pip_compile().arg("requirements.in"), @r" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.in + package1 @ git+https://git@github.com/astral-sh/uv-path-dependency-test.git@28781b32cf1f260cdb2c8040628079eb265202bd#subdirectory=package1 + # via package2 + package2 @ git+https://git@github.com/astral-sh/uv-path-dependency-test.git@28781b32cf1f260cdb2c8040628079eb265202bd#subdirectory=package2 + # via -r requirements.in + + ----- stderr ----- + Resolved 2 packages in [TIME] + "); + + Ok(()) +} From 326e4497da9fc64d88a5540ed0463be1db7d65e3 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 26 Jun 2025 16:17:42 -0400 Subject: [PATCH 071/349] Allow local indexes to reference remote files (#14294) ## Summary Previously, we assumed that local indexes only referenced local files. However, it's fine for a local index (like, a `file://`-based Simple API) to reference a remote file, and in fact Pyodide operates this way. Closes https://github.com/astral-sh/uv/issues/14227. ## Test Plan Ran `UV_INDEX=$(pyodide config get package_index) cargo run add anyio`, which produced this lockfile: ```toml version = 1 revision = 2 requires-python = ">=3.13.2" [[package]] name = "anyio" version = "4.9.0" source = { registry = "../../../Library/Caches/.pyodide-xbuildenv-0.30.5/0.27.7/xbuildenv/pyodide-root/package_index" } dependencies = [ { name = "idna" }, { name = "sniffio" }, ] wheels = [ { url = "https://cdn.jsdelivr.net/pyodide/v0.27.7/full/anyio-4.9.0-py3-none-any.whl", hash = "sha256:e1d9180d4361fd71d1bc4a7007fea6cae1d18792dba9d07eaad89f2a8562f71c" }, ] [[package]] name = "foo" version = "0.1.0" source = { virtual = "." } dependencies = [ { name = "anyio" }, ] [package.metadata] requires-dist = [{ name = "anyio", specifier = ">=4.9.0" }] [[package]] name = "idna" version = "3.7" source = { registry = "../../../Library/Caches/.pyodide-xbuildenv-0.30.5/0.27.7/xbuildenv/pyodide-root/package_index" } wheels = [ { url = "https://cdn.jsdelivr.net/pyodide/v0.27.7/full/idna-3.7-py3-none-any.whl", hash = "sha256:9d4685891e3e37434e09b1becda7e96a284e660c7aea9222564d88b6c3527c09" }, ] [[package]] name = "sniffio" version = "1.3.1" source = { registry = "../../../Library/Caches/.pyodide-xbuildenv-0.30.5/0.27.7/xbuildenv/pyodide-root/package_index" } wheels = [ { url = "https://cdn.jsdelivr.net/pyodide/v0.27.7/full/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:9215f9917b34fc73152b134a3fc0a2eb0e4a49b0b956100cad75e84943412bb9" }, ] ``` --- crates/uv-resolver/src/lock/mod.rs | 198 +++++++++++++++++++---------- 1 file changed, 129 insertions(+), 69 deletions(-) diff --git a/crates/uv-resolver/src/lock/mod.rs b/crates/uv-resolver/src/lock/mod.rs index cee6364a9..9834ad845 100644 --- a/crates/uv-resolver/src/lock/mod.rs +++ b/crates/uv-resolver/src/lock/mod.rs @@ -2541,16 +2541,30 @@ impl Package { .as_ref() .expect("version for registry source"); - let file_path = sdist.path().ok_or_else(|| LockErrorKind::MissingPath { - name: name.clone(), - version: version.clone(), - })?; - let file_path = workspace_root.join(path).join(file_path); - let file_url = DisplaySafeUrl::from_file_path(&file_path).map_err(|()| { - LockErrorKind::PathToUrl { - path: file_path.into_boxed_path(), + let file_url = match sdist { + SourceDist::Url { url: file_url, .. } => { + FileLocation::AbsoluteUrl(file_url.clone()) } - })?; + SourceDist::Path { + path: file_path, .. + } => { + let file_path = workspace_root.join(path).join(file_path); + let file_url = + DisplaySafeUrl::from_file_path(&file_path).map_err(|()| { + LockErrorKind::PathToUrl { + path: file_path.into_boxed_path(), + } + })?; + FileLocation::AbsoluteUrl(UrlString::from(file_url)) + } + SourceDist::Metadata { .. } => { + return Err(LockErrorKind::MissingPath { + name: name.clone(), + version: version.clone(), + } + .into()); + } + }; let filename = sdist .filename() .ok_or_else(|| LockErrorKind::MissingFilename { @@ -2571,9 +2585,10 @@ impl Package { requires_python: None, size: sdist.size(), upload_time_utc_ms: sdist.upload_time().map(Timestamp::as_millisecond), - url: FileLocation::AbsoluteUrl(UrlString::from(file_url)), + url: file_url, yanked: None, }); + let index = IndexUrl::from( VerbatimUrl::from_absolute_path(workspace_root.join(path)) .map_err(LockErrorKind::RegistryVerbatimUrl)?, @@ -3688,14 +3703,6 @@ impl SourceDist { } } - fn path(&self) -> Option<&Path> { - match &self { - SourceDist::Metadata { .. } => None, - SourceDist::Url { .. } => None, - SourceDist::Path { path, .. } => Some(path), - } - } - pub(crate) fn hash(&self) -> Option<&Hash> { match &self { SourceDist::Metadata { metadata } => metadata.hash.as_ref(), @@ -3818,34 +3825,57 @@ impl SourceDist { let index_path = path .to_file_path() .map_err(|()| LockErrorKind::UrlToPath { url: path.to_url() })?; - let reg_dist_url = reg_dist + let url = reg_dist .file .url .to_url() .map_err(LockErrorKind::InvalidUrl)?; - let reg_dist_path = reg_dist_url - .to_file_path() - .map_err(|()| LockErrorKind::UrlToPath { url: reg_dist_url })?; - let path = relative_to(®_dist_path, index_path) - .or_else(|_| std::path::absolute(®_dist_path)) - .map_err(LockErrorKind::DistributionRelativePath)? - .into_boxed_path(); - let hash = reg_dist.file.hashes.iter().max().cloned().map(Hash::from); - let size = reg_dist.file.size; - let upload_time = reg_dist - .file - .upload_time_utc_ms - .map(Timestamp::from_millisecond) - .transpose() - .map_err(LockErrorKind::InvalidTimestamp)?; - Ok(Some(SourceDist::Path { - path, - metadata: SourceDistMetadata { - hash, - size, - upload_time, - }, - })) + + if url.scheme() == "file" { + let reg_dist_path = url + .to_file_path() + .map_err(|()| LockErrorKind::UrlToPath { url })?; + let path = relative_to(®_dist_path, index_path) + .or_else(|_| std::path::absolute(®_dist_path)) + .map_err(LockErrorKind::DistributionRelativePath)? + .into_boxed_path(); + let hash = reg_dist.file.hashes.iter().max().cloned().map(Hash::from); + let size = reg_dist.file.size; + let upload_time = reg_dist + .file + .upload_time_utc_ms + .map(Timestamp::from_millisecond) + .transpose() + .map_err(LockErrorKind::InvalidTimestamp)?; + Ok(Some(SourceDist::Path { + path, + metadata: SourceDistMetadata { + hash, + size, + upload_time, + }, + })) + } else { + let url = normalize_file_location(®_dist.file.url) + .map_err(LockErrorKind::InvalidUrl) + .map_err(LockError::from)?; + let hash = reg_dist.file.hashes.iter().max().cloned().map(Hash::from); + let size = reg_dist.file.size; + let upload_time = reg_dist + .file + .upload_time_utc_ms + .map(Timestamp::from_millisecond) + .transpose() + .map_err(LockErrorKind::InvalidTimestamp)?; + Ok(Some(SourceDist::Url { + url, + metadata: SourceDistMetadata { + hash, + size, + upload_time, + }, + })) + } } } } @@ -4152,20 +4182,42 @@ impl Wheel { .to_file_path() .map_err(|()| LockErrorKind::UrlToPath { url: path.to_url() })?; let wheel_url = wheel.file.url.to_url().map_err(LockErrorKind::InvalidUrl)?; - let wheel_path = wheel_url - .to_file_path() - .map_err(|()| LockErrorKind::UrlToPath { url: wheel_url })?; - let path = relative_to(&wheel_path, index_path) - .or_else(|_| std::path::absolute(&wheel_path)) - .map_err(LockErrorKind::DistributionRelativePath)? - .into_boxed_path(); - Ok(Wheel { - url: WheelWireSource::Path { path }, - hash: None, - size: None, - upload_time: None, - filename, - }) + + if wheel_url.scheme() == "file" { + let wheel_path = wheel_url + .to_file_path() + .map_err(|()| LockErrorKind::UrlToPath { url: wheel_url })?; + let path = relative_to(&wheel_path, index_path) + .or_else(|_| std::path::absolute(&wheel_path)) + .map_err(LockErrorKind::DistributionRelativePath)? + .into_boxed_path(); + Ok(Wheel { + url: WheelWireSource::Path { path }, + hash: None, + size: None, + upload_time: None, + filename, + }) + } else { + let url = normalize_file_location(&wheel.file.url) + .map_err(LockErrorKind::InvalidUrl) + .map_err(LockError::from)?; + let hash = wheel.file.hashes.iter().max().cloned().map(Hash::from); + let size = wheel.file.size; + let upload_time = wheel + .file + .upload_time_utc_ms + .map(Timestamp::from_millisecond) + .transpose() + .map_err(LockErrorKind::InvalidTimestamp)?; + Ok(Wheel { + url: WheelWireSource::Url { url }, + hash, + size, + filename, + upload_time, + }) + } } } } @@ -4203,8 +4255,10 @@ impl Wheel { match source { RegistrySource::Url(url) => { - let file_url = match &self.url { - WheelWireSource::Url { url } => url, + let file_location = match &self.url { + WheelWireSource::Url { url: file_url } => { + FileLocation::AbsoluteUrl(file_url.clone()) + } WheelWireSource::Path { .. } | WheelWireSource::Filename { .. } => { return Err(LockErrorKind::MissingUrl { name: filename.name, @@ -4220,7 +4274,7 @@ impl Wheel { requires_python: None, size: self.size, upload_time_utc_ms: self.upload_time.map(Timestamp::as_millisecond), - url: FileLocation::AbsoluteUrl(file_url.clone()), + url: file_location, yanked: None, }); let index = IndexUrl::from(VerbatimUrl::from_url( @@ -4233,9 +4287,21 @@ impl Wheel { }) } RegistrySource::Path(index_path) => { - let file_path = match &self.url { - WheelWireSource::Path { path } => path, - WheelWireSource::Url { .. } | WheelWireSource::Filename { .. } => { + let file_location = match &self.url { + WheelWireSource::Url { url: file_url } => { + FileLocation::AbsoluteUrl(file_url.clone()) + } + WheelWireSource::Path { path: file_path } => { + let file_path = root.join(index_path).join(file_path); + let file_url = + DisplaySafeUrl::from_file_path(&file_path).map_err(|()| { + LockErrorKind::PathToUrl { + path: file_path.into_boxed_path(), + } + })?; + FileLocation::AbsoluteUrl(UrlString::from(file_url)) + } + WheelWireSource::Filename { .. } => { return Err(LockErrorKind::MissingPath { name: filename.name, version: filename.version, @@ -4243,12 +4309,6 @@ impl Wheel { .into()); } }; - let file_path = root.join(index_path).join(file_path); - let file_url = DisplaySafeUrl::from_file_path(&file_path).map_err(|()| { - LockErrorKind::PathToUrl { - path: file_path.into_boxed_path(), - } - })?; let file = Box::new(uv_distribution_types::File { dist_info_metadata: false, filename: SmallString::from(filename.to_string()), @@ -4256,7 +4316,7 @@ impl Wheel { requires_python: None, size: self.size, upload_time_utc_ms: self.upload_time.map(Timestamp::as_millisecond), - url: FileLocation::AbsoluteUrl(UrlString::from(file_url)), + url: file_location, yanked: None, }); let index = IndexUrl::from( From 9ee34dc69b5e98a84a01461b111d66d6543001f0 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 26 Jun 2025 15:40:40 -0500 Subject: [PATCH 072/349] Fix `Indexes::new` doc (#14293) --- crates/uv-auth/src/index.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/uv-auth/src/index.rs b/crates/uv-auth/src/index.rs index e17bbd8fe..b71bc9a62 100644 --- a/crates/uv-auth/src/index.rs +++ b/crates/uv-auth/src/index.rs @@ -86,7 +86,7 @@ impl Indexes { Self(FxHashSet::default()) } - /// Create a new [`AuthIndexUrls`] from an iterator of [`AuthIndexUrl`]s. + /// Create a new [`Indexes`] instance from an iterator of [`Index`]s. pub fn from_indexes(urls: impl IntoIterator) -> Self { let mut index_urls = Self::new(); for url in urls { From efc361223c134840b7650fed1ef9ebd9577b5454 Mon Sep 17 00:00:00 2001 From: Jack O'Connor Date: Thu, 26 Jun 2025 10:20:28 -0700 Subject: [PATCH 073/349] move the test buckets dir into the canonicalized temp dir Previously we were using the XDG data dir to avoid symlinks, but there's no particular guarantee that that's not going to be a symlink too. Using the canonicalized temp dir by default is also slightly nicer for a couple reasons: It's sometimes faster (an in-memory tempfs on e.g. Arch), and it makes overriding `$TMPDIR` or `%TMP%` sufficient to control where tests put temp files, without needing to override `UV_INTERNAL__TEST_DIR` too. --- .github/workflows/setup-dev-drive.ps1 | 1 - Cargo.lock | 1 - crates/uv-static/src/env_vars.rs | 4 ---- crates/uv/Cargo.toml | 1 - crates/uv/tests/it/common/mod.rs | 26 ++++++++++---------------- 5 files changed, 10 insertions(+), 23 deletions(-) diff --git a/.github/workflows/setup-dev-drive.ps1 b/.github/workflows/setup-dev-drive.ps1 index e003cc359..474b082dc 100644 --- a/.github/workflows/setup-dev-drive.ps1 +++ b/.github/workflows/setup-dev-drive.ps1 @@ -85,7 +85,6 @@ Write-Output ` "DEV_DRIVE=$($Drive)" ` "TMP=$($Tmp)" ` "TEMP=$($Tmp)" ` - "UV_INTERNAL__TEST_DIR=$($Tmp)" ` "RUSTUP_HOME=$($Drive)/.rustup" ` "CARGO_HOME=$($Drive)/.cargo" ` "UV_WORKSPACE=$($Drive)/uv" ` diff --git a/Cargo.lock b/Cargo.lock index 009b3060f..d25d7d252 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4584,7 +4584,6 @@ dependencies = [ "ctrlc", "dotenvy", "dunce", - "etcetera", "filetime", "flate2", "fs-err 3.1.1", diff --git a/crates/uv-static/src/env_vars.rs b/crates/uv-static/src/env_vars.rs index 4a44579d7..4ac2976d9 100644 --- a/crates/uv-static/src/env_vars.rs +++ b/crates/uv-static/src/env_vars.rs @@ -359,10 +359,6 @@ impl EnvVars { #[attr_hidden] pub const UV_INTERNAL__SHOW_DERIVATION_TREE: &'static str = "UV_INTERNAL__SHOW_DERIVATION_TREE"; - /// Used to set a temporary directory for some tests. - #[attr_hidden] - pub const UV_INTERNAL__TEST_DIR: &'static str = "UV_INTERNAL__TEST_DIR"; - /// Path to system-level configuration directory on Unix systems. pub const XDG_CONFIG_DIRS: &'static str = "XDG_CONFIG_DIRS"; diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml index f4afb87fa..9b73a9ebd 100644 --- a/crates/uv/Cargo.toml +++ b/crates/uv/Cargo.toml @@ -114,7 +114,6 @@ assert_cmd = { version = "2.0.16" } assert_fs = { version = "1.1.2" } base64 = { workspace = true } byteorder = { version = "1.5.0" } -etcetera = { workspace = true } filetime = { version = "0.2.25" } flate2 = { workspace = true, default-features = false } ignore = { version = "0.4.23" } diff --git a/crates/uv/tests/it/common/mod.rs b/crates/uv/tests/it/common/mod.rs index 4d65aa4a3..9ead0a7c2 100644 --- a/crates/uv/tests/it/common/mod.rs +++ b/crates/uv/tests/it/common/mod.rs @@ -13,7 +13,6 @@ use assert_cmd::assert::{Assert, OutputAssertExt}; use assert_fs::assert::PathAssert; use assert_fs::fixture::{ChildPath, PathChild, PathCopy, PathCreateDir, SymlinkToFile}; use base64::{Engine, prelude::BASE64_STANDARD as base64}; -use etcetera::BaseStrategy; use futures::StreamExt; use indoc::formatdoc; use itertools::Itertools; @@ -407,25 +406,20 @@ impl TestContext { self } - /// Discover the path to the XDG state directory. We use this, rather than the OS-specific - /// temporary directory, because on macOS (and Windows on GitHub Actions), they involve - /// symlinks. (On macOS, the temporary directory is, like `/var/...`, which resolves to - /// `/private/var/...`.) + /// Default to the canonicalized path to the temp directory. We need to do this because on + /// macOS (and Windows on GitHub Actions) the standard temp dir is a symlink. (On macOS, the + /// temporary directory is, like `/var/...`, which resolves to `/private/var/...`.) /// /// It turns out that, at least on macOS, if we pass a symlink as `current_dir`, it gets /// _immediately_ resolved (such that if you call `current_dir` in the running `Command`, it - /// returns resolved symlink). This is problematic, as we _don't_ want to resolve symlinks - /// for user-provided paths. + /// returns resolved symlink). This breaks some snapshot tests, since we _don't_ want to + /// resolve symlinks for user-provided paths. pub fn test_bucket_dir() -> PathBuf { - env::var(EnvVars::UV_INTERNAL__TEST_DIR) - .map(PathBuf::from) - .unwrap_or_else(|_| { - etcetera::base_strategy::choose_base_strategy() - .expect("Failed to find base strategy") - .data_dir() - .join("uv") - .join("tests") - }) + std::env::temp_dir() + .simple_canonicalize() + .expect("failed to canonicalize temp dir") + .join("uv") + .join("tests") } /// Create a new test context with multiple Python versions. From 56266447e228d7c042036f26e86f4fe4f46d66f2 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Fri, 27 Jun 2025 10:27:45 -0400 Subject: [PATCH 074/349] Bump MSRV and `rust-toolchain` version (#14303) ## Summary Per our versioning policy, we stay two versions back (and 1.88 was released today). --- Cargo.toml | 2 +- clippy.toml | 6 +- crates/uv-fs/src/which.rs | 2 +- crates/uv-python/src/discovery.rs | 2 +- crates/uv-trampoline-builder/src/lib.rs | 2 +- crates/uv/tests/it/ecosystem.rs | 2 +- crates/uv/tests/it/pip_compile.rs | 94 +------------------------ crates/uv/tests/it/pip_install.rs | 2 +- rust-toolchain.toml | 2 +- 9 files changed, 11 insertions(+), 103 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5f3cdf40c..817c5c62b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ resolver = "2" [workspace.package] edition = "2024" -rust-version = "1.85" +rust-version = "1.86" homepage = "https://pypi.org/project/uv/" documentation = "https://pypi.org/project/uv/" repository = "https://github.com/astral-sh/uv" diff --git a/clippy.toml b/clippy.toml index 6b3031c84..1151d773d 100644 --- a/clippy.toml +++ b/clippy.toml @@ -37,7 +37,7 @@ disallowed-methods = [ "std::fs::soft_link", "std::fs::symlink_metadata", "std::fs::write", - "std::os::unix::fs::symlink", - "std::os::windows::fs::symlink_dir", - "std::os::windows::fs::symlink_file", + { path = "std::os::unix::fs::symlink", allow-invalid = true }, + { path = "std::os::windows::fs::symlink_dir", allow-invalid = true }, + { path = "std::os::windows::fs::symlink_file", allow-invalid = true }, ] diff --git a/crates/uv-fs/src/which.rs b/crates/uv-fs/src/which.rs index 9dd4cc508..e63174a17 100644 --- a/crates/uv-fs/src/which.rs +++ b/crates/uv-fs/src/which.rs @@ -17,7 +17,7 @@ fn get_binary_type(path: &Path) -> windows::core::Result { .chain(Some(0)) .collect::>(); // SAFETY: winapi call - unsafe { GetBinaryTypeW(PCWSTR(name.as_ptr()), &mut binary_type)? }; + unsafe { GetBinaryTypeW(PCWSTR(name.as_ptr()), &raw mut binary_type)? }; Ok(binary_type) } diff --git a/crates/uv-python/src/discovery.rs b/crates/uv-python/src/discovery.rs index d1f3a690a..67f8f37ff 100644 --- a/crates/uv-python/src/discovery.rs +++ b/crates/uv-python/src/discovery.rs @@ -1433,7 +1433,7 @@ pub(crate) fn is_windows_store_shim(path: &Path) -> bool { 0, buf.as_mut_ptr().cast(), buf.len() as u32 * 2, - &mut bytes_returned, + &raw mut bytes_returned, std::ptr::null_mut(), ) != 0 }; diff --git a/crates/uv-trampoline-builder/src/lib.rs b/crates/uv-trampoline-builder/src/lib.rs index 15b435ec5..2e1cde872 100644 --- a/crates/uv-trampoline-builder/src/lib.rs +++ b/crates/uv-trampoline-builder/src/lib.rs @@ -521,7 +521,7 @@ if __name__ == "__main__": } #[test] - #[ignore] + #[ignore = "This test will spawn a GUI and wait until you close the window."] fn gui_launcher() -> Result<()> { // Create Temp Dirs let temp_dir = assert_fs::TempDir::new()?; diff --git a/crates/uv/tests/it/ecosystem.rs b/crates/uv/tests/it/ecosystem.rs index e96dca62c..a3804f426 100644 --- a/crates/uv/tests/it/ecosystem.rs +++ b/crates/uv/tests/it/ecosystem.rs @@ -73,8 +73,8 @@ fn saleor() -> Result<()> { // Currently ignored because the project doesn't build with `uv` yet. // // Source: https://github.com/apache/airflow/blob/c55438d9b2eb9b6680641eefdd0cbc67a28d1d29/pyproject.toml -#[ignore] #[test] +#[ignore = "Airflow doesn't build with `uv` yet"] fn airflow() -> Result<()> { lock_ecosystem_package("3.12", "airflow") } diff --git a/crates/uv/tests/it/pip_compile.rs b/crates/uv/tests/it/pip_compile.rs index 5adfeb7f8..79a98a3bf 100644 --- a/crates/uv/tests/it/pip_compile.rs +++ b/crates/uv/tests/it/pip_compile.rs @@ -3,9 +3,8 @@ use std::env::current_dir; use std::fs; use std::io::Cursor; -use std::path::PathBuf; -use anyhow::{Context, Result, bail}; +use anyhow::Result; use assert_fs::prelude::*; use flate2::write::GzEncoder; use fs_err::File; @@ -4803,97 +4802,6 @@ fn compile_editable_url_requirement() -> Result<()> { Ok(()) } -#[test] -#[ignore] -fn cache_errors_are_non_fatal() -> Result<()> { - let context = TestContext::new("3.12"); - let requirements_in = context.temp_dir.child("requirements.in"); - // No git dep, git has its own locking strategy - requirements_in.write_str(indoc! {r" - # pypi wheel - pandas - # url wheel - flask @ https://files.pythonhosted.org/packages/36/42/015c23096649b908c809c69388a805a571a3bea44362fe87e33fc3afa01f/flask-3.0.0-py3-none-any.whl - # url source dist - werkzeug @ https://files.pythonhosted.org/packages/0d/cc/ff1904eb5eb4b455e442834dabf9427331ac0fa02853bf83db817a7dd53d/werkzeug-3.0.1.tar.gz - " - })?; - - // Pick a file from each kind of cache - let interpreter_cache = context - .cache_dir - .path() - .join("interpreter-v0") - .read_dir()? - .next() - .context("Expected a python interpreter cache file")?? - .path(); - let cache_files = [ - PathBuf::from("simple-v0/pypi/numpy.msgpack"), - PathBuf::from( - "wheels-v0/pypi/python-dateutil/python_dateutil-2.8.2-py2.py3-none-any.msgpack", - ), - PathBuf::from("wheels-v0/url/4b8be67c801a7ecb/flask/flask-3.0.0-py3-none-any.msgpack"), - PathBuf::from("built-wheels-v0/url/6781bd6440ae72c2/werkzeug/metadata.msgpack"), - interpreter_cache, - ]; - - let check = || { - uv_snapshot!(context.filters(), context.pip_compile() - .arg("pip") - .arg("compile") - .arg(requirements_in.path()) - // It's sufficient to check that we resolve to a fix number of packages - .stdout(std::process::Stdio::null()), @r###" - success: true - exit_code: 0 - ----- stdout ----- - - ----- stderr ----- - Resolved 13 packages in [TIME] - "### - ); - }; - - insta::allow_duplicates! { - check(); - - // Replace some cache files with invalid contents - for file in &cache_files { - let file = context.cache_dir.join(file); - if !file.is_file() { - bail!("Missing cache file {}", file.user_display()); - } - fs_err::write(file, "I borken you cache")?; - } - - check(); - - #[cfg(unix)] - { - use fs_err::os::unix::fs::OpenOptionsExt; - - // Make some files unreadable, so that the read instead of the deserialization will fail - for file in cache_files { - let file = context.cache_dir.join(file); - if !file.is_file() { - bail!("Missing cache file {}", file.user_display()); - } - - fs_err::OpenOptions::new() - .create(true) - .write(true) - .mode(0o000) - .open(file)?; - } - } - - check(); - - Ok(()) - } -} - /// Resolve a distribution from an HTML-only registry. #[test] #[cfg(not(target_env = "musl"))] // No musllinux wheels in the torch index diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index 604b8db15..090fb03a9 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -2344,7 +2344,7 @@ fn install_git_private_https_pat_at_ref() { /// See: . #[test] #[cfg(feature = "git")] -#[ignore] +#[ignore = "Modifies the user's keyring"] fn install_git_private_https_pat_and_username() { let context = TestContext::new(DEFAULT_PYTHON_VERSION); let token = decode_token(common::READ_ONLY_GITHUB_TOKEN); diff --git a/rust-toolchain.toml b/rust-toolchain.toml index e7f22fb8b..c95c90571 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.86" +channel = "1.88" From a824468c8b4055be54747e201a12f8f33e7db12d Mon Sep 17 00:00:00 2001 From: John Mumm Date: Fri, 27 Jun 2025 16:41:14 +0200 Subject: [PATCH 075/349] Respect URL-encoded credentials in redirect location (#14315) uv currently ignores URL-encoded credentials in a redirect location. This PR adds a check for these credentials to the redirect handling logic. If found, they are moved to the Authorization header in the redirect request. Closes #11097 --- crates/uv-client/src/base_client.rs | 11 +++++ crates/uv/tests/it/edit.rs | 63 ++++++++++++++++++++++++++++- 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/crates/uv-client/src/base_client.rs b/crates/uv-client/src/base_client.rs index 85c384b0d..d62621863 100644 --- a/crates/uv-client/src/base_client.rs +++ b/crates/uv-client/src/base_client.rs @@ -25,6 +25,7 @@ use tracing::{debug, trace}; use url::ParseError; use url::Url; +use uv_auth::Credentials; use uv_auth::{AuthMiddleware, Indexes}; use uv_configuration::{KeyringProviderType, TrustedHost}; use uv_fs::Simplified; @@ -725,6 +726,16 @@ fn request_into_redirect( } } + // Check if there are credentials on the redirect location itself. + // If so, move them to Authorization header. + if !redirect_url.username().is_empty() { + if let Some(credentials) = Credentials::from_url(&redirect_url) { + let _ = redirect_url.set_username(""); + let _ = redirect_url.set_password(None); + headers.insert(AUTHORIZATION, credentials.to_header_value()); + } + } + std::mem::swap(req.headers_mut(), &mut headers); *req.url_mut() = Url::from(redirect_url); debug!( diff --git a/crates/uv/tests/it/edit.rs b/crates/uv/tests/it/edit.rs index 2f9c91e84..3c26ab342 100644 --- a/crates/uv/tests/it/edit.rs +++ b/crates/uv/tests/it/edit.rs @@ -12018,6 +12018,61 @@ async fn add_redirect_cross_origin() -> Result<()> { Ok(()) } +/// If uv receives a 302 redirect to a cross-origin server with credentials +/// in the location, use those credentials for the redirect request. +#[tokio::test] +async fn add_redirect_cross_origin_credentials_in_location() -> Result<()> { + let context = TestContext::new("3.12"); + let filters = context + .filters() + .into_iter() + .chain([(r"127\.0\.0\.1:\d*", "[LOCALHOST]")]) + .collect::>(); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str(indoc! { r#" + [project] + name = "foo" + version = "1.0.0" + requires-python = ">=3.12" + dependencies = [] + "# + })?; + + let redirect_server = MockServer::start().await; + + Mock::given(method("GET")) + .respond_with(|req: &wiremock::Request| { + // Responds with credentials in the location + let redirect_url = redirect_url_to_base( + req, + "https://public:heron@pypi-proxy.fly.dev/basic-auth/simple/", + ); + ResponseTemplate::new(302).insert_header("Location", &redirect_url) + }) + .mount(&redirect_server) + .await; + + let redirect_url = Url::parse(&redirect_server.uri())?; + + uv_snapshot!(filters, context.add().arg("--default-index").arg(redirect_url.as_str()).arg("anyio"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + 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 + " + ); + + Ok(()) +} + /// uv currently fails to look up keyring credentials on a cross-origin redirect. #[tokio::test] async fn add_redirect_with_keyring_cross_origin() -> Result<()> { @@ -12145,14 +12200,18 @@ async fn pip_install_redirect_with_netrc_cross_origin() -> Result<()> { } fn redirect_url_to_pypi_proxy(req: &wiremock::Request) -> String { + redirect_url_to_base(req, "https://pypi-proxy.fly.dev/basic-auth/simple/") +} + +fn redirect_url_to_base(req: &wiremock::Request, base: &str) -> String { let last_path_segment = req .url .path_segments() .expect("path has segments") - .filter(|segment| !segment.is_empty()) // Filter out empty segments + .filter(|segment| !segment.is_empty()) .next_back() .expect("path has a package segment"); - format!("https://pypi-proxy.fly.dev/basic-auth/simple/{last_path_segment}/") + format!("{base}{last_path_segment}/") } /// Test the error message when adding a package with multiple existing references in From 5754f2f2db03824f3f6c6b915e508c09d141088b Mon Sep 17 00:00:00 2001 From: John Mumm Date: Fri, 27 Jun 2025 17:11:21 +0200 Subject: [PATCH 076/349] Normalize index URLs to remove trailing slash (#14245) This PR updates `IndexUrl` parsing to normalize non-file URLs by removing trailing slashes. It also normalizes registry source URLs when using them to validate the lockfile. Prior to this change, when writing an index URL to the lockfile, uv would use a trailing slash if present in the provided URL and no trailing slash otherwise. This can cause surprising behavior. For example, `uv lock --locked` will fail when a package is added with an `--index` value without a trailing slash and then `uv lock --locked` is run with a `pyproject.toml` version of the index URL that contains a trailing slash. This PR fixes this and adds a test for the scenario. It might be safe to normalize file URLs in the same way, but since slashes have a well-defined meaning in the context of files and directories, I chose not to normalize them here. Closes #13707. --- crates/uv-distribution-types/src/file.rs | 11 + crates/uv-distribution-types/src/index_url.rs | 15 +- crates/uv-resolver/src/lock/mod.rs | 6 +- crates/uv/tests/it/edit.rs | 24 +- crates/uv/tests/it/lock.rs | 201 +++++++++++- crates/uv/tests/it/lock_scenarios.rs | 306 +++++++++--------- crates/uv/tests/it/sync.rs | 2 +- 7 files changed, 391 insertions(+), 174 deletions(-) diff --git a/crates/uv-distribution-types/src/file.rs b/crates/uv-distribution-types/src/file.rs index e17901f80..2d30eb0f2 100644 --- a/crates/uv-distribution-types/src/file.rs +++ b/crates/uv-distribution-types/src/file.rs @@ -171,6 +171,17 @@ impl UrlString { .unwrap_or_else(|| self.0.clone()), ) } + + /// Return the [`UrlString`] with trailing slash removed. + #[must_use] + pub fn without_trailing_slash(&self) -> Self { + Self( + self.as_ref() + .strip_suffix('/') + .map(SmallString::from) + .unwrap_or_else(|| self.0.clone()), + ) + } } impl AsRef for UrlString { diff --git a/crates/uv-distribution-types/src/index_url.rs b/crates/uv-distribution-types/src/index_url.rs index 2b686c9fe..90b5ad809 100644 --- a/crates/uv-distribution-types/src/index_url.rs +++ b/crates/uv-distribution-types/src/index_url.rs @@ -38,13 +38,22 @@ impl IndexUrl { /// /// If no root directory is provided, relative paths are resolved against the current working /// directory. + /// + /// Normalizes non-file URLs by removing trailing slashes for consistency. pub fn parse(path: &str, root_dir: Option<&Path>) -> Result { let url = match split_scheme(path) { Some((scheme, ..)) => { match Scheme::parse(scheme) { - Some(_) => { - // Ex) `https://pypi.org/simple` - VerbatimUrl::parse_url(path)? + Some(scheme) => { + if scheme.is_file() { + // Ex) `file:///path/to/something/` + VerbatimUrl::parse_url(path)? + } else { + // Ex) `https://pypi.org/simple/` + // Remove a trailing slash if it exists. + let normalized_path = path.strip_suffix('/').unwrap_or(path); + VerbatimUrl::parse_url(normalized_path)? + } } None => { // Ex) `C:\Users\user\index` diff --git a/crates/uv-resolver/src/lock/mod.rs b/crates/uv-resolver/src/lock/mod.rs index 9834ad845..8ff3097de 100644 --- a/crates/uv-resolver/src/lock/mod.rs +++ b/crates/uv-resolver/src/lock/mod.rs @@ -1478,9 +1478,11 @@ impl Lock { if let Source::Registry(index) = &package.id.source { match index { RegistrySource::Url(url) => { + // Normalize URL before validating. + let url = url.without_trailing_slash(); if remotes .as_ref() - .is_some_and(|remotes| !remotes.contains(url)) + .is_some_and(|remotes| !remotes.contains(&url)) { let name = &package.id.name; let version = &package @@ -1793,7 +1795,7 @@ pub enum SatisfiesResult<'lock> { /// The lockfile is missing a workspace member. MissingRoot(PackageName), /// The lockfile referenced a remote index that was not provided - MissingRemoteIndex(&'lock PackageName, &'lock Version, &'lock UrlString), + MissingRemoteIndex(&'lock PackageName, &'lock Version, UrlString), /// The lockfile referenced a local index that was not provided MissingLocalIndex(&'lock PackageName, &'lock Version, &'lock Path), /// A package in the lockfile contains different `requires-dist` metadata than expected. diff --git a/crates/uv/tests/it/edit.rs b/crates/uv/tests/it/edit.rs index 3c26ab342..ee0bf04ee 100644 --- a/crates/uv/tests/it/edit.rs +++ b/crates/uv/tests/it/edit.rs @@ -4374,7 +4374,7 @@ fn add_lower_bound_local() -> Result<()> { filters => context.filters(), }, { assert_snapshot!( - pyproject_toml, @r###" + pyproject_toml, @r#" [project] name = "project" version = "0.1.0" @@ -4384,8 +4384,8 @@ fn add_lower_bound_local() -> Result<()> { ] [[tool.uv.index]] - url = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" - "### + url = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" + "# ); }); @@ -4403,7 +4403,7 @@ fn add_lower_bound_local() -> Result<()> { [[package]] name = "local-simple-a" version = "1.2.3+foo" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/local_simple_a-1.2.3+foo.tar.gz", hash = "sha256:ebd55c4a79d0a5759126657cb289ff97558902abcfb142e036b993781497edac" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/local_simple_a-1.2.3+foo-py3-none-any.whl", hash = "sha256:6f30e2e709b3e171cd734bb58705229a582587c29e0a7041227435583c7224cc" }, @@ -9265,7 +9265,7 @@ fn add_index_with_trailing_slash() -> Result<()> { filters => context.filters(), }, { assert_snapshot!( - pyproject_toml, @r###" + pyproject_toml, @r#" [project] name = "project" version = "0.1.0" @@ -9278,8 +9278,8 @@ fn add_index_with_trailing_slash() -> Result<()> { constraint-dependencies = ["markupsafe<3"] [[tool.uv.index]] - url = "https://pypi.org/simple/" - "### + url = "https://pypi.org/simple" + "# ); }); @@ -9303,7 +9303,7 @@ fn add_index_with_trailing_slash() -> Result<()> { [[package]] name = "iniconfig" version = "2.0.0" - source = { registry = "https://pypi.org/simple/" } + 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" }, @@ -11200,7 +11200,7 @@ fn repeated_index_cli_reversed() -> Result<()> { filters => context.filters(), }, { assert_snapshot!( - pyproject_toml, @r###" + pyproject_toml, @r#" [project] name = "project" version = "0.1.0" @@ -11210,8 +11210,8 @@ fn repeated_index_cli_reversed() -> Result<()> { ] [[tool.uv.index]] - url = "https://test.pypi.org/simple/" - "### + url = "https://test.pypi.org/simple" + "# ); }); @@ -11232,7 +11232,7 @@ fn repeated_index_cli_reversed() -> Result<()> { [[package]] name = "iniconfig" version = "2.0.0" - source = { registry = "https://test.pypi.org/simple/" } + source = { registry = "https://test.pypi.org/simple" } sdist = { url = "https://test-files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646, upload-time = "2023-01-07T11:08:16.826Z" } wheels = [ { url = "https://test-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:14.843Z" }, diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index ac20124a0..e0fc8749d 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -15479,7 +15479,7 @@ fn lock_trailing_slash() -> Result<()> { [[package]] name = "anyio" version = "3.7.0" - source = { registry = "https://pypi.org/simple/" } + source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "sniffio" }, @@ -15492,7 +15492,7 @@ fn lock_trailing_slash() -> Result<()> { [[package]] name = "idna" version = "3.6" - source = { registry = "https://pypi.org/simple/" } + 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" }, @@ -15512,7 +15512,7 @@ fn lock_trailing_slash() -> Result<()> { [[package]] name = "sniffio" version = "1.3.1" - source = { registry = "https://pypi.org/simple/" } + source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, @@ -28257,3 +28257,198 @@ fn lock_conflict_for_disjoint_platform() -> Result<()> { Ok(()) } + +/// Add a package with an `--index` URL with no trailing slash. Run `uv lock --locked` +/// with a `pyproject.toml` with that same URL but with a trailing slash. +#[test] +fn lock_with_inconsistent_trailing_slash() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + + [[tool.uv.index]] + name = "pypi-proxy" + url = "https://pypi-proxy.fly.dev/simple/" + "#, + )?; + + let no_trailing_slash_url = "https://pypi-proxy.fly.dev/simple"; + + uv_snapshot!(context.filters(), context.add().arg("anyio").arg("--index").arg(no_trailing_slash_url), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + 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 + "); + + let lock = context.read("uv.lock"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + lock, @r#" + version = 1 + revision = 2 + requires-python = ">=3.12" + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [[package]] + name = "anyio" + version = "4.3.0" + source = { registry = "https://pypi-proxy.fly.dev/simple" } + dependencies = [ + { name = "idna" }, + { name = "sniffio" }, + ] + sdist = { url = "https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6", size = 159642, upload-time = "2024-02-19T08:36:28.641Z" } + wheels = [ + { url = "https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8", size = 85584, upload-time = "2024-02-19T08:36:26.842Z" }, + ] + + [[package]] + name = "idna" + version = "3.6" + source = { registry = "https://pypi-proxy.fly.dev/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 = "anyio" }, + ] + + [package.metadata] + requires-dist = [{ name = "anyio", specifier = ">=4.3.0" }] + + [[package]] + name = "sniffio" + version = "1.3.1" + source = { registry = "https://pypi-proxy.fly.dev/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } + wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, + ] + "# + ); + }); + + // Re-run with `--locked`. + uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 4 packages in [TIME] + "); + + Ok(()) +} + +/// Run `uv lock --locked` with a lockfile with trailing slashes on index URLs. +#[test] +fn lock_with_index_trailing_slashes_in_lockfile() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["anyio"] + + [[tool.uv.index]] + name = "pypi-proxy" + url = "https://pypi-proxy.fly.dev/simple" + "#, + )?; + + let lock = context.temp_dir.child("uv.lock"); + lock.write_str(r#" + version = 1 + revision = 2 + requires-python = ">=3.12" + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [[package]] + name = "anyio" + version = "4.3.0" + source = { registry = "https://pypi-proxy.fly.dev/simple/" } + dependencies = [ + { name = "idna" }, + { name = "sniffio" }, + ] + sdist = { url = "https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6", size = 159642, upload-time = "2024-02-19T08:36:28.641Z" } + wheels = [ + { url = "https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8", size = 85584, upload-time = "2024-02-19T08:36:26.842Z" }, + ] + + [[package]] + name = "idna" + version = "3.6" + source = { registry = "https://pypi-proxy.fly.dev/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 = "anyio" }, + ] + + [package.metadata] + requires-dist = [{ name = "anyio" }] + + [[package]] + name = "sniffio" + version = "1.3.1" + source = { registry = "https://pypi-proxy.fly.dev/simple/" } + sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } + wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, + ] + "# + )?; + + // Run `uv lock --locked`. + uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 4 packages in [TIME] + "); + + Ok(()) +} diff --git a/crates/uv/tests/it/lock_scenarios.rs b/crates/uv/tests/it/lock_scenarios.rs index 3be986ad1..801214fa5 100644 --- a/crates/uv/tests/it/lock_scenarios.rs +++ b/crates/uv/tests/it/lock_scenarios.rs @@ -158,7 +158,7 @@ fn wrong_backtracking_basic() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/wrong_backtracking_basic_a-1.0.0.tar.gz", hash = "sha256:5251a827291d4e5b7ca11c742df3aa26802cc55442e3f5fc307ff3423b8f9295" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/wrong_backtracking_basic_a-1.0.0-py3-none-any.whl", hash = "sha256:d9a7ee79b176cd36c9db03e36bc3325856dd4fb061aefc6159eecad6e8776e88" }, @@ -167,7 +167,7 @@ fn wrong_backtracking_basic() -> Result<()> { [[package]] name = "package-b" version = "2.0.9" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } dependencies = [ { name = "package-a" }, ] @@ -340,7 +340,7 @@ fn wrong_backtracking_indirect() -> Result<()> { [[package]] name = "package-a" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/wrong_backtracking_indirect_a-2.0.0.tar.gz", hash = "sha256:5891b5a45aac67b3afb90f66913d7ced2ada7cad1676fe427136b7324935bb1e" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/wrong_backtracking_indirect_a-2.0.0-py3-none-any.whl", hash = "sha256:68cb37193f4b2277630ad083522f59ac0449cb1c59e943884d04cc0e2a04cba7" }, @@ -349,7 +349,7 @@ fn wrong_backtracking_indirect() -> Result<()> { [[package]] name = "package-b" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } dependencies = [ { name = "package-b-inner" }, ] @@ -361,7 +361,7 @@ fn wrong_backtracking_indirect() -> Result<()> { [[package]] name = "package-b-inner" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } dependencies = [ { name = "package-too-old" }, ] @@ -373,7 +373,7 @@ fn wrong_backtracking_indirect() -> Result<()> { [[package]] name = "package-too-old" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/wrong_backtracking_indirect_too_old-1.0.0.tar.gz", hash = "sha256:1b674a931c34e29d20f22e9b92206b648769fa9e35770ab680466dbaa1335090" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/wrong_backtracking_indirect_too_old-1.0.0-py3-none-any.whl", hash = "sha256:15f8fe39323691c883c3088f8873220944428210a74db080f60a61a74c1fc6b0" }, @@ -477,7 +477,7 @@ fn fork_allows_non_conflicting_non_overlapping_dependencies() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_allows_non_conflicting_non_overlapping_dependencies_a-1.0.0.tar.gz", hash = "sha256:dd40a6bd59fbeefbf9f4936aec3df6fb6017e57d334f85f482ae5dd03ae353b9" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_allows_non_conflicting_non_overlapping_dependencies_a-1.0.0-py3-none-any.whl", hash = "sha256:8111e996c2a4e04c7a7cf91cf6f8338f5195c22ecf2303d899c4ef4e718a8175" }, @@ -592,7 +592,7 @@ fn fork_allows_non_conflicting_repeated_dependencies() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_allows_non_conflicting_repeated_dependencies_a-1.0.0.tar.gz", hash = "sha256:45ca30f1f66eaf6790198fad279b6448719092f2128f23b99f2ede0d6dde613b" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_allows_non_conflicting_repeated_dependencies_a-1.0.0-py3-none-any.whl", hash = "sha256:fc3f6d2fab10d1bb4f52bd9a7de69dc97ed1792506706ca78bdc9e95d6641a6b" }, @@ -699,7 +699,7 @@ fn fork_basic() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "sys_platform == 'darwin'", ] @@ -711,7 +711,7 @@ fn fork_basic() -> Result<()> { [[package]] name = "package-a" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -725,8 +725,8 @@ fn fork_basic() -> Result<()> { version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'darwin'" }, - { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, + { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'darwin'" }, + { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, ] [package.metadata] @@ -1002,7 +1002,7 @@ fn fork_filter_sibling_dependencies() -> Result<()> { [[package]] name = "package-a" version = "4.3.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "sys_platform == 'darwin'", ] @@ -1014,7 +1014,7 @@ fn fork_filter_sibling_dependencies() -> Result<()> { [[package]] name = "package-a" version = "4.4.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -1026,9 +1026,9 @@ fn fork_filter_sibling_dependencies() -> Result<()> { [[package]] name = "package-b" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } dependencies = [ - { name = "package-d", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, + { name = "package-d", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_filter_sibling_dependencies_b-1.0.0.tar.gz", hash = "sha256:af3f861d6df9a2bbad55bae02acf17384ea2efa1abbf19206ac56cb021814613" } wheels = [ @@ -1038,9 +1038,9 @@ fn fork_filter_sibling_dependencies() -> Result<()> { [[package]] name = "package-c" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } dependencies = [ - { name = "package-d", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'darwin'" }, + { name = "package-d", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_filter_sibling_dependencies_c-1.0.0.tar.gz", hash = "sha256:c03742ca6e81c2a5d7d8cb72d1214bf03b2925e63858a19097f17d3e1a750192" } wheels = [ @@ -1050,7 +1050,7 @@ fn fork_filter_sibling_dependencies() -> Result<()> { [[package]] name = "package-d" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -1062,7 +1062,7 @@ fn fork_filter_sibling_dependencies() -> Result<()> { [[package]] name = "package-d" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "sys_platform == 'darwin'", ] @@ -1076,8 +1076,8 @@ fn fork_filter_sibling_dependencies() -> Result<()> { version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "package-a", version = "4.3.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'darwin'" }, - { name = "package-a", version = "4.4.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, + { name = "package-a", version = "4.3.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'darwin'" }, + { name = "package-a", version = "4.4.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, { name = "package-b", marker = "sys_platform == 'linux'" }, { name = "package-c", marker = "sys_platform == 'darwin'" }, ] @@ -1180,7 +1180,7 @@ fn fork_upgrade() -> Result<()> { [[package]] name = "package-bar" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_upgrade_bar-2.0.0.tar.gz", hash = "sha256:2e7b5370d7be19b5af56092a8364a2718a7b8516142a12a95656b82d1b9c8cbc" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_upgrade_bar-2.0.0-py3-none-any.whl", hash = "sha256:d8ce562bf363e849fbf4add170a519b5412ab63e378fb4b7ea290183c77616fc" }, @@ -1189,7 +1189,7 @@ fn fork_upgrade() -> Result<()> { [[package]] name = "package-foo" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } dependencies = [ { name = "package-bar" }, ] @@ -1310,7 +1310,7 @@ fn fork_incomplete_markers() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "python_full_version < '3.10'", ] @@ -1322,7 +1322,7 @@ fn fork_incomplete_markers() -> Result<()> { [[package]] name = "package-a" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "python_full_version >= '3.11'", ] @@ -1334,7 +1334,7 @@ fn fork_incomplete_markers() -> Result<()> { [[package]] name = "package-b" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } dependencies = [ { name = "package-c", marker = "python_full_version == '3.10.*'" }, ] @@ -1346,7 +1346,7 @@ fn fork_incomplete_markers() -> Result<()> { [[package]] name = "package-c" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_incomplete_markers_c-1.0.0.tar.gz", hash = "sha256:ecc02ea1cc8d3b561c8dcb9d2ba1abcdae2dd32de608bf8e8ed2878118426022" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_incomplete_markers_c-1.0.0-py3-none-any.whl", hash = "sha256:03fa287aa4cb78457211cb3df7459b99ba1ee2259aae24bc745eaab45e7eaaee" }, @@ -1357,8 +1357,8 @@ fn fork_incomplete_markers() -> Result<()> { version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "python_full_version < '3.10'" }, - { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "python_full_version >= '3.11'" }, + { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "python_full_version < '3.10'" }, + { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "python_full_version >= '3.11'" }, { name = "package-b" }, ] @@ -1462,7 +1462,7 @@ fn fork_marker_accrue() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } dependencies = [ { name = "package-c", marker = "sys_platform == 'linux'" }, ] @@ -1474,7 +1474,7 @@ fn fork_marker_accrue() -> Result<()> { [[package]] name = "package-b" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } dependencies = [ { name = "package-c", marker = "sys_platform == 'darwin'" }, ] @@ -1486,7 +1486,7 @@ fn fork_marker_accrue() -> Result<()> { [[package]] name = "package-c" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_marker_accrue_c-1.0.0.tar.gz", hash = "sha256:a3e09ac3dc8e787a08ebe8d5d6072e09720c76cbbcb76a6645d6f59652742015" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_marker_accrue_c-1.0.0-py3-none-any.whl", hash = "sha256:b0c8719d38c91b2a8548bd065b1d2153fbe031b37775ed244e76fe5bdfbb502e" }, @@ -1680,15 +1680,15 @@ fn fork_marker_inherit_combined_allowed() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "implementation_name == 'pypy' and sys_platform == 'darwin'", "implementation_name == 'cpython' and sys_platform == 'darwin'", "implementation_name != 'cpython' and implementation_name != 'pypy' and sys_platform == 'darwin'", ] dependencies = [ - { name = "package-b", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "implementation_name == 'pypy' and sys_platform == 'darwin'" }, - { name = "package-b", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "implementation_name == 'cpython' and sys_platform == 'darwin'" }, + { name = "package-b", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "implementation_name == 'pypy' and sys_platform == 'darwin'" }, + { name = "package-b", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "implementation_name == 'cpython' and sys_platform == 'darwin'" }, ] sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_marker_inherit_combined_allowed_a-1.0.0.tar.gz", hash = "sha256:c7232306e8597d46c3fe53a3b1472f99b8ff36b3169f335ba0a5b625e193f7d4" } wheels = [ @@ -1698,7 +1698,7 @@ fn fork_marker_inherit_combined_allowed() -> Result<()> { [[package]] name = "package-a" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -1710,7 +1710,7 @@ fn fork_marker_inherit_combined_allowed() -> Result<()> { [[package]] name = "package-b" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "implementation_name == 'pypy' and sys_platform == 'darwin'", ] @@ -1725,7 +1725,7 @@ fn fork_marker_inherit_combined_allowed() -> Result<()> { [[package]] name = "package-b" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "implementation_name == 'cpython' and sys_platform == 'darwin'", ] @@ -1737,7 +1737,7 @@ fn fork_marker_inherit_combined_allowed() -> Result<()> { [[package]] name = "package-c" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_marker_inherit_combined_allowed_c-1.0.0.tar.gz", hash = "sha256:7ce8efca029cfa952e64f55c2d47fe33975c7f77ec689384bda11cbc3b7ef1db" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_marker_inherit_combined_allowed_c-1.0.0-py3-none-any.whl", hash = "sha256:6a6b776dedabceb6a6c4f54a5d932076fa3fed1380310491999ca2d31e13b41c" }, @@ -1748,8 +1748,8 @@ fn fork_marker_inherit_combined_allowed() -> Result<()> { version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'darwin'" }, - { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, + { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'darwin'" }, + { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, ] [package.metadata] @@ -1866,15 +1866,15 @@ fn fork_marker_inherit_combined_disallowed() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "implementation_name == 'pypy' and sys_platform == 'darwin'", "implementation_name == 'cpython' and sys_platform == 'darwin'", "implementation_name != 'cpython' and implementation_name != 'pypy' and sys_platform == 'darwin'", ] dependencies = [ - { name = "package-b", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "implementation_name == 'pypy' and sys_platform == 'darwin'" }, - { name = "package-b", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "implementation_name == 'cpython' and sys_platform == 'darwin'" }, + { name = "package-b", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "implementation_name == 'pypy' and sys_platform == 'darwin'" }, + { name = "package-b", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "implementation_name == 'cpython' and sys_platform == 'darwin'" }, ] sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_marker_inherit_combined_disallowed_a-1.0.0.tar.gz", hash = "sha256:92081d91570582f3a94ed156f203de53baca5b3fdc350aa1c831c7c42723e798" } wheels = [ @@ -1884,7 +1884,7 @@ fn fork_marker_inherit_combined_disallowed() -> Result<()> { [[package]] name = "package-a" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -1896,7 +1896,7 @@ fn fork_marker_inherit_combined_disallowed() -> Result<()> { [[package]] name = "package-b" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "implementation_name == 'pypy' and sys_platform == 'darwin'", ] @@ -1908,7 +1908,7 @@ fn fork_marker_inherit_combined_disallowed() -> Result<()> { [[package]] name = "package-b" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "implementation_name == 'cpython' and sys_platform == 'darwin'", ] @@ -1922,8 +1922,8 @@ fn fork_marker_inherit_combined_disallowed() -> Result<()> { version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'darwin'" }, - { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, + { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'darwin'" }, + { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, ] [package.metadata] @@ -2041,15 +2041,15 @@ fn fork_marker_inherit_combined() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "implementation_name == 'pypy' and sys_platform == 'darwin'", "implementation_name == 'cpython' and sys_platform == 'darwin'", "implementation_name != 'cpython' and implementation_name != 'pypy' and sys_platform == 'darwin'", ] dependencies = [ - { name = "package-b", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "implementation_name == 'pypy' and sys_platform == 'darwin'" }, - { name = "package-b", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "implementation_name == 'cpython' and sys_platform == 'darwin'" }, + { name = "package-b", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "implementation_name == 'pypy' and sys_platform == 'darwin'" }, + { name = "package-b", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "implementation_name == 'cpython' and sys_platform == 'darwin'" }, ] sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_marker_inherit_combined_a-1.0.0.tar.gz", hash = "sha256:2ec4c9dbb7078227d996c344b9e0c1b365ed0000de9527b2ba5b616233636f07" } wheels = [ @@ -2059,7 +2059,7 @@ fn fork_marker_inherit_combined() -> Result<()> { [[package]] name = "package-a" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -2071,7 +2071,7 @@ fn fork_marker_inherit_combined() -> Result<()> { [[package]] name = "package-b" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "implementation_name == 'pypy' and sys_platform == 'darwin'", ] @@ -2083,7 +2083,7 @@ fn fork_marker_inherit_combined() -> Result<()> { [[package]] name = "package-b" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "implementation_name == 'cpython' and sys_platform == 'darwin'", ] @@ -2097,8 +2097,8 @@ fn fork_marker_inherit_combined() -> Result<()> { version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'darwin'" }, - { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, + { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'darwin'" }, + { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, ] [package.metadata] @@ -2205,7 +2205,7 @@ fn fork_marker_inherit_isolated() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "sys_platform == 'darwin'", ] @@ -2217,7 +2217,7 @@ fn fork_marker_inherit_isolated() -> Result<()> { [[package]] name = "package-a" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -2232,7 +2232,7 @@ fn fork_marker_inherit_isolated() -> Result<()> { [[package]] name = "package-b" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_marker_inherit_isolated_b-1.0.0.tar.gz", hash = "sha256:96f8c3cabc5795e08a064c89ec76a4bfba8afe3c13d647161b4a1568b4584ced" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_marker_inherit_isolated_b-1.0.0-py3-none-any.whl", hash = "sha256:c8affc2f13f9bcd08b3d1601a21a1781ea14d52a8cddc708b29428c9c3d53ea5" }, @@ -2243,8 +2243,8 @@ fn fork_marker_inherit_isolated() -> Result<()> { version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'darwin'" }, - { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, + { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'darwin'" }, + { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, ] [package.metadata] @@ -2359,7 +2359,7 @@ fn fork_marker_inherit_transitive() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "sys_platform == 'darwin'", ] @@ -2374,7 +2374,7 @@ fn fork_marker_inherit_transitive() -> Result<()> { [[package]] name = "package-a" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -2386,7 +2386,7 @@ fn fork_marker_inherit_transitive() -> Result<()> { [[package]] name = "package-b" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } dependencies = [ { name = "package-c", marker = "sys_platform == 'darwin'" }, ] @@ -2398,7 +2398,7 @@ fn fork_marker_inherit_transitive() -> Result<()> { [[package]] name = "package-c" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_marker_inherit_transitive_c-1.0.0.tar.gz", hash = "sha256:58bb788896b2297f2948f51a27fc48cfe44057c687a3c0c4d686b107975f7f32" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_marker_inherit_transitive_c-1.0.0-py3-none-any.whl", hash = "sha256:ad2cbb0582ec6f4dc9549d1726d2aae66cd1fdf0e355acc70cd720cf65ae4d86" }, @@ -2409,8 +2409,8 @@ fn fork_marker_inherit_transitive() -> Result<()> { version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'darwin'" }, - { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, + { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'darwin'" }, + { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, ] [package.metadata] @@ -2519,7 +2519,7 @@ fn fork_marker_inherit() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "sys_platform == 'darwin'", ] @@ -2531,7 +2531,7 @@ fn fork_marker_inherit() -> Result<()> { [[package]] name = "package-a" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -2545,8 +2545,8 @@ fn fork_marker_inherit() -> Result<()> { version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'darwin'" }, - { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, + { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'darwin'" }, + { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, ] [package.metadata] @@ -2662,7 +2662,7 @@ fn fork_marker_limited_inherit() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "sys_platform == 'darwin'", ] @@ -2674,7 +2674,7 @@ fn fork_marker_limited_inherit() -> Result<()> { [[package]] name = "package-a" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -2686,7 +2686,7 @@ fn fork_marker_limited_inherit() -> Result<()> { [[package]] name = "package-b" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } dependencies = [ { name = "package-c", marker = "sys_platform == 'linux'" }, ] @@ -2698,7 +2698,7 @@ fn fork_marker_limited_inherit() -> Result<()> { [[package]] name = "package-c" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_marker_limited_inherit_c-1.0.0.tar.gz", hash = "sha256:8dcb05f5dff09fec52ab507b215ff367fe815848319a17929db997ad3afe88ae" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_marker_limited_inherit_c-1.0.0-py3-none-any.whl", hash = "sha256:877a87a4987ad795ddaded3e7266ed7defdd3cfbe07a29500cb6047637db4065" }, @@ -2709,8 +2709,8 @@ fn fork_marker_limited_inherit() -> Result<()> { version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'darwin'" }, - { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, + { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'darwin'" }, + { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, { name = "package-b" }, ] @@ -2822,7 +2822,7 @@ fn fork_marker_selection() -> Result<()> { [[package]] name = "package-a" version = "0.1.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_marker_selection_a-0.1.0.tar.gz", hash = "sha256:ece83ba864a62d5d747439f79a0bf36aa4c18d15bca96aab855ffc2e94a8eef7" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_marker_selection_a-0.1.0-py3-none-any.whl", hash = "sha256:a3b9d6e46cc226d20994cc60653fd59d81d96527749f971a6f59ef8cbcbc7c01" }, @@ -2831,7 +2831,7 @@ fn fork_marker_selection() -> Result<()> { [[package]] name = "package-b" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "sys_platform == 'darwin'", ] @@ -2843,7 +2843,7 @@ fn fork_marker_selection() -> Result<()> { [[package]] name = "package-b" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -2858,8 +2858,8 @@ fn fork_marker_selection() -> Result<()> { source = { virtual = "." } dependencies = [ { name = "package-a" }, - { name = "package-b", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'darwin'" }, - { name = "package-b", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, + { name = "package-b", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'darwin'" }, + { name = "package-b", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, ] [package.metadata] @@ -2985,7 +2985,7 @@ fn fork_marker_track() -> Result<()> { [[package]] name = "package-a" version = "1.3.1" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } dependencies = [ { name = "package-c", marker = "implementation_name == 'iron'" }, ] @@ -2997,7 +2997,7 @@ fn fork_marker_track() -> Result<()> { [[package]] name = "package-b" version = "2.7" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "sys_platform == 'darwin'", ] @@ -3009,7 +3009,7 @@ fn fork_marker_track() -> Result<()> { [[package]] name = "package-b" version = "2.8" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -3021,7 +3021,7 @@ fn fork_marker_track() -> Result<()> { [[package]] name = "package-c" version = "1.10" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_marker_track_c-1.10.tar.gz", hash = "sha256:c89006d893254790b0fcdd1b33520241c8ff66ab950c6752b745e006bdeff144" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_marker_track_c-1.10-py3-none-any.whl", hash = "sha256:cedcb8fbcdd9fbde4eea76612e57536c8b56507a9d7f7a92e483cb56b18c57a3" }, @@ -3033,8 +3033,8 @@ fn fork_marker_track() -> Result<()> { source = { virtual = "." } dependencies = [ { name = "package-a" }, - { name = "package-b", version = "2.7", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'darwin'" }, - { name = "package-b", version = "2.8", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, + { name = "package-b", version = "2.7", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'darwin'" }, + { name = "package-b", version = "2.8", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, ] [package.metadata] @@ -3137,7 +3137,7 @@ fn fork_non_fork_marker_transitive() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } dependencies = [ { name = "package-c", marker = "sys_platform == 'linux'" }, ] @@ -3149,7 +3149,7 @@ fn fork_non_fork_marker_transitive() -> Result<()> { [[package]] name = "package-b" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } dependencies = [ { name = "package-c", marker = "sys_platform == 'darwin'" }, ] @@ -3161,7 +3161,7 @@ fn fork_non_fork_marker_transitive() -> Result<()> { [[package]] name = "package-c" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_non_fork_marker_transitive_c-2.0.0.tar.gz", hash = "sha256:ffab9124854f64c8b5059ccaed481547f54abac868ba98aa6a454c0163cdb1c7" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_non_fork_marker_transitive_c-2.0.0-py3-none-any.whl", hash = "sha256:2b72d6af81967e1c55f30d920d6a7b913fce6ad0a0658ec79972a3d1a054e85f" }, @@ -3453,7 +3453,7 @@ fn fork_overlapping_markers_basic() -> Result<()> { [[package]] name = "package-a" version = "1.2.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_overlapping_markers_basic_a-1.2.0.tar.gz", hash = "sha256:f8c2058d80430d62b15c87fd66040a6c0dd23d32e7f144a932899c0c74bdff2a" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_overlapping_markers_basic_a-1.2.0-py3-none-any.whl", hash = "sha256:04293ed42eb3620c9ddf56e380a8408a30733d5d38f321a35c024d03e7116083" }, @@ -3636,11 +3636,11 @@ fn preferences_dependent_forking_bistable() -> Result<()> { [[package]] name = "package-cleaver" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } dependencies = [ - { name = "package-fork-if-not-forked", version = "3.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, + { name = "package-fork-if-not-forked", version = "3.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, { name = "package-fork-if-not-forked-proxy", marker = "sys_platform != 'linux'" }, - { name = "package-reject-cleaver1", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, + { name = "package-reject-cleaver1", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, { name = "package-reject-cleaver1-proxy" }, ] sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/preferences_dependent_forking_bistable_cleaver-1.0.0.tar.gz", hash = "sha256:64e5ee0c81d6a51fb71ed517fd04cc26c656908ad05073270e67c2f9b92194c5" } @@ -3651,7 +3651,7 @@ fn preferences_dependent_forking_bistable() -> Result<()> { [[package]] name = "package-fork-if-not-forked" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "sys_platform != 'linux'", ] @@ -3663,7 +3663,7 @@ fn preferences_dependent_forking_bistable() -> Result<()> { [[package]] name = "package-fork-if-not-forked" version = "3.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -3675,9 +3675,9 @@ fn preferences_dependent_forking_bistable() -> Result<()> { [[package]] name = "package-fork-if-not-forked-proxy" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } dependencies = [ - { name = "package-fork-if-not-forked", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform != 'linux'" }, + { name = "package-fork-if-not-forked", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform != 'linux'" }, ] sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/preferences_dependent_forking_bistable_fork_if_not_forked_proxy-1.0.0.tar.gz", hash = "sha256:0ed00a7c8280348225835fadc76db8ecc6b4a9ee11351a6c432c475f8d1579de" } wheels = [ @@ -3687,7 +3687,7 @@ fn preferences_dependent_forking_bistable() -> Result<()> { [[package]] name = "package-reject-cleaver1" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -3699,7 +3699,7 @@ fn preferences_dependent_forking_bistable() -> Result<()> { [[package]] name = "package-reject-cleaver1" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "sys_platform != 'linux'", ] @@ -3711,9 +3711,9 @@ fn preferences_dependent_forking_bistable() -> Result<()> { [[package]] name = "package-reject-cleaver1-proxy" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } dependencies = [ - { name = "package-reject-cleaver1", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform != 'linux'" }, + { name = "package-reject-cleaver1", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform != 'linux'" }, ] sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/preferences_dependent_forking_bistable_reject_cleaver1_proxy-1.0.0.tar.gz", hash = "sha256:6b6eaa229d55de992e36084521d2f62dce35120a866e20354d0e5617e16e00ce" } wheels = [ @@ -4048,7 +4048,7 @@ fn preferences_dependent_forking_tristable() -> Result<()> { [[package]] name = "package-bar" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "sys_platform != 'linux'", ] @@ -4064,7 +4064,7 @@ fn preferences_dependent_forking_tristable() -> Result<()> { [[package]] name = "package-bar" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -4076,7 +4076,7 @@ fn preferences_dependent_forking_tristable() -> Result<()> { [[package]] name = "package-c" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -4088,7 +4088,7 @@ fn preferences_dependent_forking_tristable() -> Result<()> { [[package]] name = "package-c" version = "3.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "sys_platform != 'linux'", ] @@ -4100,9 +4100,9 @@ fn preferences_dependent_forking_tristable() -> Result<()> { [[package]] name = "package-cleaver" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } dependencies = [ - { name = "package-bar", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform != 'linux'" }, + { name = "package-bar", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform != 'linux'" }, { name = "package-foo", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/preferences_dependent_forking_tristable_cleaver-1.0.0.tar.gz", hash = "sha256:49ec5779d0722586652e3ceb4ca2bf053a79dc3fa2d7ccd428a359bcc885a248" } @@ -4113,9 +4113,9 @@ fn preferences_dependent_forking_tristable() -> Result<()> { [[package]] name = "package-d" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } dependencies = [ - { name = "package-c", version = "3.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform != 'linux'" }, + { name = "package-c", version = "3.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform != 'linux'" }, ] sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/preferences_dependent_forking_tristable_d-1.0.0.tar.gz", hash = "sha256:690b69acb46d0ebfb11a81f401d2ea2e2e6a8ae97f199d345715e9bd40a7ceba" } wheels = [ @@ -4125,10 +4125,10 @@ fn preferences_dependent_forking_tristable() -> Result<()> { [[package]] name = "package-foo" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } dependencies = [ - { name = "package-c", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, - { name = "package-c", version = "3.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform != 'linux'" }, + { name = "package-c", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, + { name = "package-c", version = "3.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform != 'linux'" }, { name = "package-reject-cleaver-1" }, ] sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/preferences_dependent_forking_tristable_foo-1.0.0.tar.gz", hash = "sha256:7c1a2ca51dd2156cf36c3400e38595e11b09442052f4bd1d6b3d53eb5b2acf32" } @@ -4139,10 +4139,10 @@ fn preferences_dependent_forking_tristable() -> Result<()> { [[package]] name = "package-reject-cleaver-1" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } dependencies = [ - { name = "package-unrelated-dep2", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, - { name = "package-unrelated-dep2", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform != 'linux'" }, + { name = "package-unrelated-dep2", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, + { name = "package-unrelated-dep2", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform != 'linux'" }, ] sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/preferences_dependent_forking_tristable_reject_cleaver_1-1.0.0.tar.gz", hash = "sha256:6ef93ca22db3a054559cb34f574ffa3789951f2f82b213c5502d0e9ff746f15e" } wheels = [ @@ -4152,7 +4152,7 @@ fn preferences_dependent_forking_tristable() -> Result<()> { [[package]] name = "package-unrelated-dep2" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -4164,7 +4164,7 @@ fn preferences_dependent_forking_tristable() -> Result<()> { [[package]] name = "package-unrelated-dep2" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "sys_platform != 'linux'", ] @@ -4178,8 +4178,8 @@ fn preferences_dependent_forking_tristable() -> Result<()> { version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "package-bar", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform != 'linux'" }, - { name = "package-bar", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, + { name = "package-bar", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform != 'linux'" }, + { name = "package-bar", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, { name = "package-cleaver" }, { name = "package-foo" }, ] @@ -4342,7 +4342,7 @@ fn preferences_dependent_forking() -> Result<()> { [[package]] name = "package-bar" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "sys_platform != 'linux'", ] @@ -4354,7 +4354,7 @@ fn preferences_dependent_forking() -> Result<()> { [[package]] name = "package-bar" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -4366,9 +4366,9 @@ fn preferences_dependent_forking() -> Result<()> { [[package]] name = "package-cleaver" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } dependencies = [ - { name = "package-bar", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform != 'linux'" }, + { name = "package-bar", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform != 'linux'" }, { name = "package-foo", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/preferences_dependent_forking_cleaver-1.0.0.tar.gz", hash = "sha256:0347b927fdf7731758ea53e1594309fc6311ca6983f36553bc11654a264062b2" } @@ -4379,7 +4379,7 @@ fn preferences_dependent_forking() -> Result<()> { [[package]] name = "package-foo" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/preferences_dependent_forking_foo-1.0.0.tar.gz", hash = "sha256:abf1c0ac825ee5961e683067634916f98c6651a6d4473ff87d8b57c17af8fed2" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/preferences_dependent_forking_foo-1.0.0-py3-none-any.whl", hash = "sha256:85348e8df4892b9f297560c16abcf231828f538dc07339ed121197a00a0626a5" }, @@ -4390,8 +4390,8 @@ fn preferences_dependent_forking() -> Result<()> { version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "package-bar", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform != 'linux'" }, - { name = "package-bar", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, + { name = "package-bar", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform != 'linux'" }, + { name = "package-bar", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, { name = "package-cleaver" }, { name = "package-foo" }, ] @@ -4525,15 +4525,15 @@ fn fork_remaining_universe_partitioning() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "os_name == 'darwin' and sys_platform == 'illumos'", "os_name == 'linux' and sys_platform == 'illumos'", "os_name != 'darwin' and os_name != 'linux' and sys_platform == 'illumos'", ] dependencies = [ - { name = "package-b", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "os_name == 'darwin' and sys_platform == 'illumos'" }, - { name = "package-b", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "os_name == 'linux' and sys_platform == 'illumos'" }, + { name = "package-b", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "os_name == 'darwin' and sys_platform == 'illumos'" }, + { name = "package-b", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "os_name == 'linux' and sys_platform == 'illumos'" }, ] sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_remaining_universe_partitioning_a-1.0.0.tar.gz", hash = "sha256:d5be0af9a1958ec08ca2827b47bfd507efc26cab03ecf7ddf204e18e8a3a18ae" } wheels = [ @@ -4543,7 +4543,7 @@ fn fork_remaining_universe_partitioning() -> Result<()> { [[package]] name = "package-a" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "sys_platform == 'windows'", ] @@ -4555,7 +4555,7 @@ fn fork_remaining_universe_partitioning() -> Result<()> { [[package]] name = "package-b" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "os_name == 'darwin' and sys_platform == 'illumos'", ] @@ -4567,7 +4567,7 @@ fn fork_remaining_universe_partitioning() -> Result<()> { [[package]] name = "package-b" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } resolution-markers = [ "os_name == 'linux' and sys_platform == 'illumos'", ] @@ -4581,8 +4581,8 @@ fn fork_remaining_universe_partitioning() -> Result<()> { version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'illumos'" }, - { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'windows'" }, + { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'illumos'" }, + { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'windows'" }, ] [package.metadata] @@ -4845,7 +4845,7 @@ fn fork_requires_python_patch_overlap() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_requires_python_patch_overlap_a-1.0.0.tar.gz", hash = "sha256:ac2820ee4808788674295192d79a709e3259aa4eef5b155e77f719ad4eaa324d" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_requires_python_patch_overlap_a-1.0.0-py3-none-any.whl", hash = "sha256:43a750ba4eaab749d608d70e94d3d51e083cc21f5a52ac99b5967b26486d5ef1" }, @@ -5031,7 +5031,7 @@ fn requires_python_wheels() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/requires_python_wheels_a-1.0.0.tar.gz", hash = "sha256:9a11ff73fdc513c4dab0d3e137f4145a00ef0dfc95154360c8f503eed62a03c9" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/requires_python_wheels_a-1.0.0-cp310-cp310-any.whl", hash = "sha256:b979494a0d7dc825b84d6c516ac407143915f6d2840d229ee2a36b3d06deb61d" }, @@ -5130,7 +5130,7 @@ fn unreachable_package() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/unreachable_package_a-1.0.0.tar.gz", hash = "sha256:308f0b6772e99dcb33acee38003b176e3acffbe01c3c511585db9a7d7ec008f7" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/unreachable_package_a-1.0.0-py3-none-any.whl", hash = "sha256:cc472ded9f3b260e6cda0e633fa407a13607e190422cb455f02beebd32d6751f" }, @@ -5241,7 +5241,7 @@ fn unreachable_wheels() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/unreachable_wheels_a-1.0.0.tar.gz", hash = "sha256:91c6619d1cfa227f3662c0c062b1c0c16efe11e589db2f1836e809e2c6d9961e" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/unreachable_wheels_a-1.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:e9fb30c5eb114114f9031d0ad2238614c2dcce203c5992848305ccda8f38a53e" }, @@ -5250,7 +5250,7 @@ fn unreachable_wheels() -> Result<()> { [[package]] name = "package-b" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/unreachable_wheels_b-1.0.0.tar.gz", hash = "sha256:253ae69b963651cd5ac16601a445e2e179db9eac552e8cfc37aadf73a88931ed" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/unreachable_wheels_b-1.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3de2212ca86f1137324965899ce7f48640ed8db94578f4078d641520b77e13e" }, @@ -5260,7 +5260,7 @@ fn unreachable_wheels() -> Result<()> { [[package]] name = "package-c" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/unreachable_wheels_c-1.0.0.tar.gz", hash = "sha256:5c4783e85f0fa57b720fd02b5c7e0ff8bc98121546fe2cce435710efe4a34b28" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/unreachable_wheels_c-1.0.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:4b846c5b1646b04828a2bef6c9d180ff7cfd725866013dcec8933de7fb5f9e8d" }, @@ -5362,7 +5362,7 @@ fn marker_variants_have_different_extras() -> Result<()> { [[package]] name = "package-psycopg" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } dependencies = [ { name = "package-tzdata", marker = "sys_platform == 'win32'" }, ] @@ -5379,7 +5379,7 @@ fn marker_variants_have_different_extras() -> Result<()> { [[package]] name = "package-psycopg-binary" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/marker_variants_have_different_extras_psycopg_binary-1.0.0.tar.gz", hash = "sha256:9939771dfe78d76e3583492aaec576719780f744b36198b1f18bb16bb5048995" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/marker_variants_have_different_extras_psycopg_binary-1.0.0-py3-none-any.whl", hash = "sha256:4fb0aef60e76bc7e339d60dc919f3b6e27e49184ffdef9fb2c3f6902b23b6bd2" }, @@ -5388,7 +5388,7 @@ fn marker_variants_have_different_extras() -> Result<()> { [[package]] name = "package-tzdata" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/marker_variants_have_different_extras_tzdata-1.0.0.tar.gz", hash = "sha256:5aa31d0aec969afbc13584c3209ca2380107bdab68578f881eb2da543ac2ee8e" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/marker_variants_have_different_extras_tzdata-1.0.0-py3-none-any.whl", hash = "sha256:7466eec7ed202434492e7c09a4a7327517aec6d549aaca0436dcc100f9fcb6a5" }, @@ -5515,7 +5515,7 @@ fn virtual_package_extra_priorities() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } dependencies = [ { name = "package-b" }, ] @@ -5527,7 +5527,7 @@ fn virtual_package_extra_priorities() -> Result<()> { [[package]] name = "package-b" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/virtual_package_extra_priorities_b-1.0.0.tar.gz", hash = "sha256:79a54df14eb28687678447f5270f578f73b325f8234e620d375a87708fd7345c" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/virtual_package_extra_priorities_b-1.0.0-py3-none-any.whl", hash = "sha256:2aab1a3b90f215cb55b9bfde55b3c3617225ca0da726e8c9543c0727734f1df9" }, @@ -5635,7 +5635,7 @@ fn specific_architecture() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } dependencies = [ { name = "package-b", marker = "platform_machine == 'x86_64'" }, { name = "package-c", marker = "platform_machine == 'aarch64'" }, @@ -5649,7 +5649,7 @@ fn specific_architecture() -> Result<()> { [[package]] name = "package-b" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/specific_architecture_b-1.0.0-cp313-cp313-freebsd_13_aarch64.whl", hash = "sha256:4ce70a68440d4aaa31cc1c6174b83b741e9b8f3074ad0f3ef41c572795378999" }, { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/specific_architecture_b-1.0.0-cp313-cp313-freebsd_13_x86_64.whl", hash = "sha256:4ce70a68440d4aaa31cc1c6174b83b741e9b8f3074ad0f3ef41c572795378999" }, @@ -5660,7 +5660,7 @@ fn specific_architecture() -> Result<()> { [[package]] name = "package-c" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/specific_architecture_c-1.0.0-cp313-cp313-freebsd_13_aarch64.whl", hash = "sha256:b028c88fe496724cea4a7d95eb789a000b7f000067f95c922b09461be2746a3d" }, { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/specific_architecture_c-1.0.0-cp313-cp313-freebsd_13_x86_64.whl", hash = "sha256:b028c88fe496724cea4a7d95eb789a000b7f000067f95c922b09461be2746a3d" }, @@ -5671,7 +5671,7 @@ fn specific_architecture() -> Result<()> { [[package]] name = "package-d" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/specific_architecture_d-1.0.0-cp313-cp313-freebsd_13_aarch64.whl", hash = "sha256:842864c1348694fab33199eb05921602c2abfc77844a81085a55db02edd30da4" }, { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/specific_architecture_d-1.0.0-cp313-cp313-freebsd_13_x86_64.whl", hash = "sha256:842864c1348694fab33199eb05921602c2abfc77844a81085a55db02edd30da4" }, diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index 4187de957..da59682ab 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -9953,7 +9953,7 @@ fn sync_required_environment_hint() -> Result<()> { ----- stderr ----- Resolved 2 packages in [TIME] - error: Distribution `no-sdist-no-wheels-with-matching-platform-a==1.0.0 @ registry+https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/` can't be installed because it doesn't have a source distribution or wheel for the current platform + error: Distribution `no-sdist-no-wheels-with-matching-platform-a==1.0.0 @ registry+https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html` can't be installed because it doesn't have a source distribution or wheel for the current platform hint: You're on [PLATFORM] (`[TAG]`), but `no-sdist-no-wheels-with-matching-platform-a` (v1.0.0) only has wheels for the following platform: `macosx_10_0_ppc64`; consider adding your platform to `tool.uv.required-environments` to ensure uv resolves to a version with compatible wheels "); From 880c5e4949725fa902e0b6ff117456a08e0409de Mon Sep 17 00:00:00 2001 From: John Mumm Date: Fri, 27 Jun 2025 19:26:28 +0200 Subject: [PATCH 077/349] Ensure preview default Python installs are upgradeable (#14261) Python `bin` installations installed with `uv python install --default --preview` (no version specified) were not being installed as upgradeable. Instead each link was pointed at the highest patch version for a minor version. This change ensures that these preview default installations are also treated as upgradeable. The PR includes some updates to the related tests. First, it checks the default install without specified version case. Second, since it's adding more read link checks, it creates a new `read_link` helper method to consolidate repeated logic and replace instances of `#[cfg(unix/windows)` with `if cfg!(unix/windows)`. Fixes #14247 --- crates/uv/src/commands/python/install.rs | 2 +- crates/uv/tests/it/python_install.rs | 209 ++++++++++++++++------- 2 files changed, 144 insertions(+), 67 deletions(-) diff --git a/crates/uv/src/commands/python/install.rs b/crates/uv/src/commands/python/install.rs index 7ad96fffe..08f95003e 100644 --- a/crates/uv/src/commands/python/install.rs +++ b/crates/uv/src/commands/python/install.rs @@ -451,7 +451,7 @@ pub(crate) async fn install( .expect("We should have a bin directory with preview enabled") .as_path(); - let upgradeable = preview.is_enabled() && is_default_install + let upgradeable = (default || is_default_install) || requested_minor_versions.contains(&installation.key().version().python_version()); create_bin_links( diff --git a/crates/uv/tests/it/python_install.rs b/crates/uv/tests/it/python_install.rs index 2b6f03d4b..817e71052 100644 --- a/crates/uv/tests/it/python_install.rs +++ b/crates/uv/tests/it/python_install.rs @@ -356,27 +356,20 @@ fn python_install_preview() { bin_python.assert(predicate::path::is_symlink()); // The link should be to a path containing a minor version symlink directory - #[cfg(unix)] - { + if cfg!(unix) { insta::with_settings!({ filters => context.filters(), }, { insta::assert_snapshot!( - &bin_python - .read_link() - .unwrap_or_else(|_| panic!("{} should be readable", bin_python.path().display())) - .as_os_str().to_string_lossy(), - @"[TEMP_DIR]/managed/cpython-3.13-[PLATFORM]/bin/python3.13" + read_link(&bin_python), @"[TEMP_DIR]/managed/cpython-3.13-[PLATFORM]/bin/python3.13" ); }); - } - #[cfg(windows)] - { + } else if cfg!(windows) { insta::with_settings!({ filters => context.filters(), }, { insta::assert_snapshot!( - launcher_path(&bin_python).display(), @"[TEMP_DIR]/managed/cpython-3.13-[PLATFORM]/python" + read_link(&bin_python), @"[TEMP_DIR]/managed/cpython-3.13-[PLATFORM]/python" ); }); } @@ -505,27 +498,20 @@ fn python_install_preview() { .child(format!("python3.11{}", std::env::consts::EXE_SUFFIX)); // The link should be to a path containing a minor version symlink directory - #[cfg(unix)] - { + if cfg!(unix) { insta::with_settings!({ filters => context.filters(), }, { insta::assert_snapshot!( - &bin_python - .read_link() - .unwrap_or_else(|_| panic!("{} should be readable", bin_python.path().display())) - .as_os_str().to_string_lossy(), - @"[TEMP_DIR]/managed/cpython-3.11-[PLATFORM]/bin/python3.11" + read_link(&bin_python), @"[TEMP_DIR]/managed/cpython-3.11-[PLATFORM]/bin/python3.11" ); }); - } - #[cfg(windows)] - { + } else if cfg!(windows) { insta::with_settings!({ filters => context.filters(), }, { insta::assert_snapshot!( - launcher_path(&bin_python).display(), @"[TEMP_DIR]/managed/cpython-3.11-[PLATFORM]/python" + read_link(&bin_python), @"[TEMP_DIR]/managed/cpython-3.11-[PLATFORM]/python" ); }); } @@ -563,15 +549,15 @@ fn python_install_preview() { filters => context.filters(), }, { insta::assert_snapshot!( - read_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.8-[PLATFORM]/bin/python3.12" + canonicalize_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.8-[PLATFORM]/bin/python3.12" ); }); - } else { + } else if cfg!(windows) { insta::with_settings!({ filters => context.filters(), }, { insta::assert_snapshot!( - read_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.8-[PLATFORM]/python" + canonicalize_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.8-[PLATFORM]/python" ); }); } @@ -600,27 +586,20 @@ fn python_install_preview_upgrade() { "###); // Installing with a patch version should cause the link to be to the patch installation. - #[cfg(unix)] - { + if cfg!(unix) { insta::with_settings!({ filters => context.filters(), }, { insta::assert_snapshot!( - &bin_python - .read_link() - .unwrap_or_else(|_| panic!("{} should be readable", bin_python.display())) - .display(), - @"[TEMP_DIR]/managed/cpython-3.12.5-[PLATFORM]/bin/python3.12" + read_link(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.5-[PLATFORM]/bin/python3.12" ); }); - } - #[cfg(windows)] - { + } else if cfg!(windows) { insta::with_settings!({ filters => context.filters(), }, { insta::assert_snapshot!( - launcher_path(&bin_python).display(), @"[TEMP_DIR]/managed/cpython-3.12.5-[PLATFORM]/python" + read_link(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.5-[PLATFORM]/python" ); }); } @@ -641,15 +620,15 @@ fn python_install_preview_upgrade() { filters => context.filters(), }, { insta::assert_snapshot!( - read_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.5-[PLATFORM]/bin/python3.12" + canonicalize_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.5-[PLATFORM]/bin/python3.12" ); }); - } else { + } else if cfg!(windows) { insta::with_settings!({ filters => context.filters(), }, { insta::assert_snapshot!( - read_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.5-[PLATFORM]/python" + canonicalize_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.5-[PLATFORM]/python" ); }); } @@ -670,15 +649,15 @@ fn python_install_preview_upgrade() { filters => context.filters(), }, { insta::assert_snapshot!( - read_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.5-[PLATFORM]/bin/python3.12" + canonicalize_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.5-[PLATFORM]/bin/python3.12" ); }); - } else { + } else if cfg!(windows) { insta::with_settings!({ filters => context.filters(), }, { insta::assert_snapshot!( - read_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.5-[PLATFORM]/python" + canonicalize_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.5-[PLATFORM]/python" ); }); } @@ -699,15 +678,15 @@ fn python_install_preview_upgrade() { filters => context.filters(), }, { insta::assert_snapshot!( - read_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.4-[PLATFORM]/bin/python3.12" + canonicalize_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.4-[PLATFORM]/bin/python3.12" ); }); - } else { + } else if cfg!(windows) { insta::with_settings!({ filters => context.filters(), }, { insta::assert_snapshot!( - read_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.4-[PLATFORM]/python" + canonicalize_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.4-[PLATFORM]/python" ); }); } @@ -728,15 +707,15 @@ fn python_install_preview_upgrade() { filters => context.filters(), }, { insta::assert_snapshot!( - read_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.6-[PLATFORM]/bin/python3.12" + canonicalize_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.6-[PLATFORM]/bin/python3.12" ); }); - } else { + } else if cfg!(windows) { insta::with_settings!({ filters => context.filters(), }, { insta::assert_snapshot!( - read_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.6-[PLATFORM]/python" + canonicalize_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.6-[PLATFORM]/python" ); }); } @@ -933,7 +912,7 @@ fn python_install_default() { bin_python_default.assert(predicate::path::missing()); // Install the latest version, i.e., a "default install" - uv_snapshot!(context.filters(), context.python_install().arg("--preview"), @r" + uv_snapshot!(context.filters(), context.python_install().arg("--default").arg("--preview"), @r" success: true exit_code: 0 ----- stdout ----- @@ -948,6 +927,75 @@ fn python_install_default() { bin_python_major.assert(predicate::path::exists()); bin_python_default.assert(predicate::path::exists()); + // And 3.13 should be the default + if cfg!(unix) { + insta::with_settings!({ + filters => context.filters(), + }, { + insta::assert_snapshot!( + read_link(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.13-[PLATFORM]/bin/python3.13" + ); + insta::assert_snapshot!( + canonicalize_link_path(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/bin/python3.13" + ); + }); + + insta::with_settings!({ + filters => context.filters(), + }, { + insta::assert_snapshot!( + read_link(&bin_python_minor_13), @"[TEMP_DIR]/managed/cpython-3.13-[PLATFORM]/bin/python3.13" + ); + insta::assert_snapshot!( + canonicalize_link_path(&bin_python_minor_13), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/bin/python3.13" + ); + }); + + insta::with_settings!({ + filters => context.filters(), + }, { + insta::assert_snapshot!( + read_link(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.13-[PLATFORM]/bin/python3.13" + ); + insta::assert_snapshot!( + canonicalize_link_path(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/bin/python3.13" + ); + }); + } else if cfg!(windows) { + insta::with_settings!({ + filters => context.filters(), + }, { + insta::assert_snapshot!( + read_link(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.13-[PLATFORM]/python" + ); + insta::assert_snapshot!( + canonicalize_link_path(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/python" + ); + }); + + insta::with_settings!({ + filters => context.filters(), + }, { + insta::assert_snapshot!( + read_link(&bin_python_minor_13), @"[TEMP_DIR]/managed/cpython-3.13-[PLATFORM]/python" + ); + insta::assert_snapshot!( + canonicalize_link_path(&bin_python_minor_13), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/python" + ); + }); + + insta::with_settings!({ + filters => context.filters(), + }, { + insta::assert_snapshot!( + read_link(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.13-[PLATFORM]/python" + ); + insta::assert_snapshot!( + canonicalize_link_path(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/python" + ); + }); + } + // Uninstall again uv_snapshot!(context.filters(), context.python_uninstall().arg("3.13"), @r" success: true @@ -1001,7 +1049,10 @@ fn python_install_default() { filters => context.filters(), }, { insta::assert_snapshot!( - read_link_path(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/bin/python3.12" + read_link(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.12-[PLATFORM]/bin/python3.12" + ); + insta::assert_snapshot!( + canonicalize_link_path(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/bin/python3.12" ); }); @@ -1009,7 +1060,10 @@ fn python_install_default() { filters => context.filters(), }, { insta::assert_snapshot!( - read_link_path(&bin_python_minor_12), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/bin/python3.12" + read_link(&bin_python_minor_12), @"[TEMP_DIR]/managed/cpython-3.12-[PLATFORM]/bin/python3.12" + ); + insta::assert_snapshot!( + canonicalize_link_path(&bin_python_minor_12), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/bin/python3.12" ); }); @@ -1017,7 +1071,10 @@ fn python_install_default() { filters => context.filters(), }, { insta::assert_snapshot!( - read_link_path(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/bin/python3.12" + read_link(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.12-[PLATFORM]/bin/python3.12" + ); + insta::assert_snapshot!( + canonicalize_link_path(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/bin/python3.12" ); }); } else { @@ -1025,7 +1082,10 @@ fn python_install_default() { filters => context.filters(), }, { insta::assert_snapshot!( - read_link_path(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/python" + read_link(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.12-[PLATFORM]/python" + ); + insta::assert_snapshot!( + canonicalize_link_path(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/python" ); }); @@ -1033,7 +1093,10 @@ fn python_install_default() { filters => context.filters(), }, { insta::assert_snapshot!( - read_link_path(&bin_python_minor_12), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/python" + read_link(&bin_python_minor_12), @"[TEMP_DIR]/managed/cpython-3.12-[PLATFORM]/python" + ); + insta::assert_snapshot!( + canonicalize_link_path(&bin_python_minor_12), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/python" ); }); @@ -1041,7 +1104,10 @@ fn python_install_default() { filters => context.filters(), }, { insta::assert_snapshot!( - read_link_path(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/python" + read_link(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.12-[PLATFORM]/python" + ); + insta::assert_snapshot!( + canonicalize_link_path(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/python" ); }); } @@ -1069,7 +1135,7 @@ fn python_install_default() { filters => context.filters(), }, { insta::assert_snapshot!( - read_link_path(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/bin/python3.13" + canonicalize_link_path(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/bin/python3.13" ); }); @@ -1077,7 +1143,7 @@ fn python_install_default() { filters => context.filters(), }, { insta::assert_snapshot!( - read_link_path(&bin_python_minor_13), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/bin/python3.13" + canonicalize_link_path(&bin_python_minor_13), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/bin/python3.13" ); }); @@ -1085,7 +1151,7 @@ fn python_install_default() { filters => context.filters(), }, { insta::assert_snapshot!( - read_link_path(&bin_python_minor_12), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/bin/python3.12" + canonicalize_link_path(&bin_python_minor_12), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/bin/python3.12" ); }); @@ -1093,15 +1159,15 @@ fn python_install_default() { filters => context.filters(), }, { insta::assert_snapshot!( - read_link_path(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/bin/python3.13" + canonicalize_link_path(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/bin/python3.13" ); }); - } else { + } else if cfg!(windows) { insta::with_settings!({ filters => context.filters(), }, { insta::assert_snapshot!( - read_link_path(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/python" + canonicalize_link_path(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/python" ); }); @@ -1109,7 +1175,7 @@ fn python_install_default() { filters => context.filters(), }, { insta::assert_snapshot!( - read_link_path(&bin_python_minor_13), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/python" + canonicalize_link_path(&bin_python_minor_13), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/python" ); }); @@ -1117,7 +1183,7 @@ fn python_install_default() { filters => context.filters(), }, { insta::assert_snapshot!( - read_link_path(&bin_python_minor_12), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/python" + canonicalize_link_path(&bin_python_minor_12), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/python" ); }); @@ -1125,7 +1191,7 @@ fn python_install_default() { filters => context.filters(), }, { insta::assert_snapshot!( - read_link_path(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/python" + canonicalize_link_path(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/python" ); }); } @@ -1139,7 +1205,7 @@ fn launcher_path(path: &Path) -> PathBuf { launcher.python_path } -fn read_link_path(path: &Path) -> String { +fn canonicalize_link_path(path: &Path) -> String { #[cfg(unix)] let canonical_path = fs_err::canonicalize(path); @@ -1152,6 +1218,17 @@ fn read_link_path(path: &Path) -> String { .to_string() } +fn read_link(path: &Path) -> String { + #[cfg(unix)] + let linked_path = + fs_err::read_link(path).unwrap_or_else(|_| panic!("{} should be readable", path.display())); + + #[cfg(windows)] + let linked_path = launcher_path(path); + + linked_path.simplified_display().to_string() +} + #[test] fn python_install_unknown() { let context: TestContext = TestContext::new_with_versions(&[]).with_managed_python_dirs(); @@ -1212,7 +1289,7 @@ fn python_install_preview_broken_link() { filters => context.filters(), }, { insta::assert_snapshot!( - read_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/bin/python3.13" + canonicalize_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/bin/python3.13" ); }); } From 74468dac15e9d357cc68cca9a32585fd862208b9 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Fri, 27 Jun 2025 12:36:07 -0500 Subject: [PATCH 078/349] Bump `python-build-standalone` releases to include 3.14.0b3 (#14301) See https://github.com/astral-sh/python-build-standalone/releases/tag/20250626 --- crates/uv-python/download-metadata.json | 1512 +++++++++++++++++------ crates/uv/tests/it/python_install.rs | 10 +- 2 files changed, 1161 insertions(+), 361 deletions(-) diff --git a/crates/uv-python/download-metadata.json b/crates/uv-python/download-metadata.json index 71fe83c78..367efbd64 100644 --- a/crates/uv-python/download-metadata.json +++ b/crates/uv-python/download-metadata.json @@ -1,4 +1,804 @@ { + "cpython-3.14.0b3-darwin-aarch64-none": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "93e38ced4feb2fe93d342d75918a1fb107949ce8378b6cb16c0fc78ab0c24d20", + "variant": null + }, + "cpython-3.14.0b3-darwin-x86_64-none": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "ea08efdd3b518139ed8b42aa5416e96e01b254aa78b409c6701163f2210fb37b", + "variant": null + }, + "cpython-3.14.0b3-linux-aarch64-gnu": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "3444de5a35a41a162df647eaec4d758eca966c4243ee22d0d1f8597edf48e5b8", + "variant": null + }, + "cpython-3.14.0b3-linux-armv7-gnueabi": { + "name": "cpython", + "arch": { + "family": "armv7", + "variant": null + }, + "os": "linux", + "libc": "gnueabi", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "ac1ce5cd8aa09f6584636e3bda2a14a8df46aaf435b73511cc0e57e237d77211", + "variant": null + }, + "cpython-3.14.0b3-linux-armv7-gnueabihf": { + "name": "cpython", + "arch": { + "family": "armv7", + "variant": null + }, + "os": "linux", + "libc": "gnueabihf", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "9058c9d70434693f5d6fa052c28dc7d7850e77efee6d32495d9d2605421b656d", + "variant": null + }, + "cpython-3.14.0b3-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": { + "family": "powerpc64le", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "ad7e0d08638de2af6c70c13990b65d1bf412c95c28063de3252b186b48e5f29f", + "variant": null + }, + "cpython-3.14.0b3-linux-riscv64-gnu": { + "name": "cpython", + "arch": { + "family": "riscv64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "e95f10b21b149dc8fa59198d4cc0ff3720ec814aed9d03f33932e31cf8c17bf8", + "variant": null + }, + "cpython-3.14.0b3-linux-s390x-gnu": { + "name": "cpython", + "arch": { + "family": "s390x", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "7887516bf789785ce5cb7fa2eb2b4cffc1c454efe89a8eacfa6c66b2e054c9f8", + "variant": null + }, + "cpython-3.14.0b3-linux-x86_64-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "014a2e7c96957cc3ac3925b8b1c06e6068cab3b20faf5f9f329f7e8d95d41bfd", + "variant": null + }, + "cpython-3.14.0b3-linux-x86_64-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "72e15bb93e93cc4abcdd8ed44a666d12267ecd048d6e3b1bfeda3cc08187e659", + "variant": null + }, + "cpython-3.14.0b3-linux-x86_64_v2-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v2" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "fff629607727d50f04deca03056259f9aee607969fb2bf7db969587e53853302", + "variant": null + }, + "cpython-3.14.0b3-linux-x86_64_v2-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v2" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "acb6aeebf0c6bc6903600ce99d112754cc830a89224d0d63ef3c5c107f01685c", + "variant": null + }, + "cpython-3.14.0b3-linux-x86_64_v3-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v3" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "df49f1a8e5c5aefc28ebc169fff77047878d0ae7fb7bf00221e10fc59f67f5fc", + "variant": null + }, + "cpython-3.14.0b3-linux-x86_64_v3-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v3" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "d4672c060c5d8963af6355daaca40bb53938eee47a7346cab0259abff8e4f724", + "variant": null + }, + "cpython-3.14.0b3-linux-x86_64_v4-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v4" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "925a47c3385ed2e2d498cd8f7a0a9434242b4b42d80ba45149d35da34bc9953b", + "variant": null + }, + "cpython-3.14.0b3-linux-x86_64_v4-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v4" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "3e3b57bd9b3e821bc26107afaa29457ef8e96a50c8254ca89796feb9386928a9", + "variant": null + }, + "cpython-3.14.0b3-windows-i686-none": { + "name": "cpython", + "arch": { + "family": "i686", + "variant": null + }, + "os": "windows", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "9009cd7a90b1ab3e7325278fbaa5363f1c160c92edef05be5c9d0a5c67ede59e", + "variant": null + }, + "cpython-3.14.0b3-windows-x86_64-none": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "windows", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "b50dd04ffb2fdc051964cc733ed624c5ea7cae85ec51060b1b97a406dd00c176", + "variant": null + }, + "cpython-3.14.0b3+freethreaded-darwin-aarch64-none": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "0ad27d76b4a5ebe3fac67bf928ea07bea5335fe6f0f33880277db73640a96df1", + "variant": "freethreaded" + }, + "cpython-3.14.0b3+freethreaded-darwin-x86_64-none": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "26e5c3e51de17455ed4c7f2b81702b175cf230728e4fdd93b3c426d21df09df2", + "variant": "freethreaded" + }, + "cpython-3.14.0b3+freethreaded-linux-aarch64-gnu": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-aarch64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "15abb894679fafa47e71b37fb722de526cad5a55b42998d9ba7201023299631b", + "variant": "freethreaded" + }, + "cpython-3.14.0b3+freethreaded-linux-armv7-gnueabi": { + "name": "cpython", + "arch": { + "family": "armv7", + "variant": null + }, + "os": "linux", + "libc": "gnueabi", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-armv7-unknown-linux-gnueabi-freethreaded%2Blto-full.tar.zst", + "sha256": "a2e1f3628beec26b88eb5d5139fcc95a296d163a5a02910337a6a309450e340f", + "variant": "freethreaded" + }, + "cpython-3.14.0b3+freethreaded-linux-armv7-gnueabihf": { + "name": "cpython", + "arch": { + "family": "armv7", + "variant": null + }, + "os": "linux", + "libc": "gnueabihf", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-armv7-unknown-linux-gnueabihf-freethreaded%2Blto-full.tar.zst", + "sha256": "30896f01c54a99e34d7d91a893568c32f99c597ecba8767ab83f501b770a3831", + "variant": "freethreaded" + }, + "cpython-3.14.0b3+freethreaded-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": { + "family": "powerpc64le", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-ppc64le-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "2127b36c4b16da76a713fb4c2e8a6a2757a6d73a07a6ee4fa14d2a02633e0605", + "variant": "freethreaded" + }, + "cpython-3.14.0b3+freethreaded-linux-riscv64-gnu": { + "name": "cpython", + "arch": { + "family": "riscv64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-riscv64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "dca86f8df4d6a65a69e8deb65e60ed0b27a376f2d677ec9150138d4e3601f4f7", + "variant": "freethreaded" + }, + "cpython-3.14.0b3+freethreaded-linux-s390x-gnu": { + "name": "cpython", + "arch": { + "family": "s390x", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-s390x-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "5f119f34846d6f150c8de3b8ce81418f5cf60f90b51fcc594cb54d6ab4db030d", + "variant": "freethreaded" + }, + "cpython-3.14.0b3+freethreaded-linux-x86_64-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "ac5373d3b945298f34f1ebd5b03ce35ce92165638443ef65f8ca2d2eba07e39d", + "variant": "freethreaded" + }, + "cpython-3.14.0b3+freethreaded-linux-x86_64-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "e08e0202777269916295cf570e78bfb160250f88c20bd6f27fd1a72dcb03c8b9", + "variant": "freethreaded" + }, + "cpython-3.14.0b3+freethreaded-linux-x86_64_v2-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v2" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v2-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "5a36083ac0df9c72416a9cde665c6f86dfe78ebb348fc6a7b4b155ef0112fec9", + "variant": "freethreaded" + }, + "cpython-3.14.0b3+freethreaded-linux-x86_64_v2-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v2" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v2-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "70297d1edad54eea78c8cd5174e21ab7e2ed2d754f512896ae0f4006517b101c", + "variant": "freethreaded" + }, + "cpython-3.14.0b3+freethreaded-linux-x86_64_v3-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v3" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v3-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "8cce11843eae8c78f0045227f7637c451943406aa04c1dc693ca7bf4874b2cbd", + "variant": "freethreaded" + }, + "cpython-3.14.0b3+freethreaded-linux-x86_64_v3-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v3" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v3-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "8da62b2823fb28d8d34093b20ac0d55034a47715afa35ed3a0fab49f2cc74e49", + "variant": "freethreaded" + }, + "cpython-3.14.0b3+freethreaded-linux-x86_64_v4-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v4" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v4-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "ded53979b407e350ad4c9e87646f9521762c0769aa26d9450ba02ec6801961a2", + "variant": "freethreaded" + }, + "cpython-3.14.0b3+freethreaded-linux-x86_64_v4-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v4" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v4-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "d90c08a393ee21fd9c4d3d7f4dcf5b14cb6251d3cb77df15cccc233e67fd85c1", + "variant": "freethreaded" + }, + "cpython-3.14.0b3+freethreaded-windows-i686-none": { + "name": "cpython", + "arch": { + "family": "i686", + "variant": null + }, + "os": "windows", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-i686-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "97b59f1087c7f05c088a84998c0babf1b824358d28759e70c8090ec9a45e36aa", + "variant": "freethreaded" + }, + "cpython-3.14.0b3+freethreaded-windows-x86_64-none": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "windows", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "5c864084d8b8d5b5e9d4d5005f92ec2f7bdb65c13bc9b95a9ac52b2bcb4db8e0", + "variant": "freethreaded" + }, + "cpython-3.14.0b3+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "6ad6bcadb2c1b862f3dd8c3a56c90eac2349a54dcf14628385d0d3bf5e993173", + "variant": "debug" + }, + "cpython-3.14.0b3+debug-linux-armv7-gnueabi": { + "name": "cpython", + "arch": { + "family": "armv7", + "variant": null + }, + "os": "linux", + "libc": "gnueabi", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "bd88ac4f905609bc5ee3ec6fc9d3482ce16f05b14052946aec3ed7f9ba8f83d2", + "variant": "debug" + }, + "cpython-3.14.0b3+debug-linux-armv7-gnueabihf": { + "name": "cpython", + "arch": { + "family": "armv7", + "variant": null + }, + "os": "linux", + "libc": "gnueabihf", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "4d94559a5ae00bf849f5204a2f66eee119dd979cc0da8089edd1b57bce5a165f", + "variant": "debug" + }, + "cpython-3.14.0b3+debug-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": { + "family": "powerpc64le", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "11a6c51fe6c0e9dfc9fdd7439151b34f5e4896f82f1894fd1aee32052e5ca4d2", + "variant": "debug" + }, + "cpython-3.14.0b3+debug-linux-riscv64-gnu": { + "name": "cpython", + "arch": { + "family": "riscv64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "4e84e69007d047e1f6a76ea894a8b919e82e7f86860525eb3a42a9cb30ce7044", + "variant": "debug" + }, + "cpython-3.14.0b3+debug-linux-s390x-gnu": { + "name": "cpython", + "arch": { + "family": "s390x", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "f2f8dde10c119bbb3313a7ba4db59dd09124e283f2a65217f50504d18e9c511e", + "variant": "debug" + }, + "cpython-3.14.0b3+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "26b5ad31f9902f271bc5e3e886975d242d4155ea43968e695214147b6f0758a3", + "variant": "debug" + }, + "cpython-3.14.0b3+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "6c608708569a018db38e888410c7871b00ed9b5caa3dabf19ddc51e64e2200ab", + "variant": "debug" + }, + "cpython-3.14.0b3+debug-linux-x86_64_v2-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v2" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "2499487ea8a5aa28c3ae5e9073e9cb7b14fdf45722d58db32ba20107a8ff4657", + "variant": "debug" + }, + "cpython-3.14.0b3+debug-linux-x86_64_v2-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v2" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "88233f533101941f03aef44bb58e44c6e9a2f7802ae85294ff3804472136d363", + "variant": "debug" + }, + "cpython-3.14.0b3+debug-linux-x86_64_v3-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v3" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "edd1800dd1d2abc38db383d7ff61bb21597f608a64ab44cc23f009af0291a96c", + "variant": "debug" + }, + "cpython-3.14.0b3+debug-linux-x86_64_v3-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v3" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "6ceeb66c4876a7484b8ba9847e590d74ca35b67cbe692d06b3d3466a483406f8", + "variant": "debug" + }, + "cpython-3.14.0b3+debug-linux-x86_64_v4-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v4" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "2f4d7ced306f9d4c800e9d7a86183c5f92c7858d8f64e38afd73db45414cbb82", + "variant": "debug" + }, + "cpython-3.14.0b3+debug-linux-x86_64_v4-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v4" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "40191a40700aa65c0f92fcea8cd63de1d840ca5913c11b0226e677e1617dbf5d", + "variant": "debug" + }, "cpython-3.14.0b2-darwin-aarch64-none": { "name": "cpython", "arch": { @@ -4731,8 +5531,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "3e52e6b539dca2729788a06f3f47b2edfc30ba3ef82eb14926f0a23ed0ce4cff", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "2303b54d2780aeac8454b000515ea37c1ce9391dd0719bbf4f9aad453c4460fc", "variant": null }, "cpython-3.13.5-darwin-x86_64-none": { @@ -4747,8 +5547,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "fce29c000087f0ed966580aff703117d8238e2be043a90a2a0ec8352c0708db8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "baf14de314f191fd168fae778796c64e40667b28b8c78ae8b2bc340552e88a9a", "variant": null }, "cpython-3.13.5-linux-aarch64-gnu": { @@ -4763,8 +5563,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "6957a6d66c633890fc97f3da818066cd0d10de7cf695a7c46c4c23b107c02fa7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "bef5e63e7c8c70c2336525ffd6758da801942127ce9f6c7c378fecc4ed09716d", "variant": null }, "cpython-3.13.5-linux-armv7-gnueabi": { @@ -4779,8 +5579,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "1ca9efecff540162b22e5b86152864e621c97463061171f6734cd31d50e39f1d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "ffa3f4b9a45bfac9e4ba045d2419354fccd2e716ffa191eccf0ec0d57af7ad8d", "variant": null }, "cpython-3.13.5-linux-armv7-gnueabihf": { @@ -4795,8 +5595,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "4efad529678f93119e99bd5494070c76f5c54958bc9686ee12fd9e1950c80b27", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "f466eef0279076db5d929081dd287f98c7904fc713dd833a2ba6df4250a3b23e", "variant": null }, "cpython-3.13.5-linux-powerpc64le-gnu": { @@ -4811,8 +5611,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "abbd685fe948653ad79624e568f9f843233e808c8756d6d4429dbe1d3e7550f9", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "3a1a91c3ac60188e27d87fb3487da1dd452bff984c9ed09c419d38c3c373eea7", "variant": null }, "cpython-3.13.5-linux-riscv64-gnu": { @@ -4827,8 +5627,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "4a17bcc199782d0bbaaf073b0eedfac0ebfc5eeab2cc23b9b59968869708779c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "8b5b76554004afec79469408f3facaa0407fac953c1e39badcd69fb4cbe9e7e3", "variant": null }, "cpython-3.13.5-linux-s390x-gnu": { @@ -4843,8 +5643,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "6d877e1b2c302d205f0bddbc76b7ca465fa227d9252147df7821d5474d4ea147", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "9da68c953563b4ff3bf21a014350d76a20da5c5778ed433216f8c2ebc8bfd12b", "variant": null }, "cpython-3.13.5-linux-x86_64-gnu": { @@ -4859,8 +5659,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "5c63e7ffe47baff0a96a685c94fb5075612817741feb4e85ec3cc082c742b4f8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "1894edce20e8df3739d2136368a5c9f38f456f69c9ee4401c01fff4477e2934d", "variant": null }, "cpython-3.13.5-linux-x86_64-musl": { @@ -4875,8 +5675,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "641d0cefa3124e42728fc5dac970d5a172a61d80d2a5a24995f2b6e9ddf71e3f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "8f50133eda9061154eb82a8807cb41ec64d2e17b429ddfcd1c90062e22d442fa", "variant": null }, "cpython-3.13.5-linux-x86_64_v2-gnu": { @@ -4891,8 +5691,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "7f60b2f61fad6c846c7d7c8f523dee285c36cd9d53573314b6ca82eca4e80241", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "bf77b2a07d8c91b27befc58de9e512340ccf7ee4a365c3cde6a59e28de8b3343", "variant": null }, "cpython-3.13.5-linux-x86_64_v2-musl": { @@ -4907,8 +5707,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "21c20b03866e1ec3dcd86224cf82396e58e175768e51030ab83ba21d482cfc26", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "0e3347d77aa1fc36dd5b7a9dfc29968660c305bd2bb763af4abfab6a7f67a80d", "variant": null }, "cpython-3.13.5-linux-x86_64_v3-gnu": { @@ -4923,8 +5723,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "ec8794e649b3e0abac7dccda7de20892ce1ba43f2148e65b2a66edeba42f4c61", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "75347d44b5f7cf8c0642cb43b77797b15e6346633bc17c0fb47f7968d7221fa9", "variant": null }, "cpython-3.13.5-linux-x86_64_v3-musl": { @@ -4939,8 +5739,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "56576436ccc2a9e509becd72ebdbc9abf147a471df6e1022dcd6967975ccee55", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "9e4191282a518d02db5d7ce5f42d7e96393768243f55e6a6a96e9c50f0cc72fa", "variant": null }, "cpython-3.13.5-linux-x86_64_v4-gnu": { @@ -4955,8 +5755,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "b6aa1c65d74b528130244888ee5b47fbf52451aabac2a98e5b87873e73705d87", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "b841a8f77bedde2882f9bc65393939a49142c599b465c12c1bdd86dde52d6fc8", "variant": null }, "cpython-3.13.5-linux-x86_64_v4-musl": { @@ -4971,8 +5771,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "c3322d81ef18e862e0069bfd8824ef1a907135bf2d1c6514312854ea99f0a372", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "446fb13abdcbf8a329074f9bfb4e9803b292a54f6252948edd08d5cf9c973289", "variant": null }, "cpython-3.13.5-windows-i686-none": { @@ -4987,8 +5787,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "254754ec03dd94dc8e67f89b415253a9ee16f0d277478e0e01c25de45b7fc172", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "c458f8200dd040af36d087791c48efa239160de641b7fda8d9c1fc6536a5e4a4", "variant": null }, "cpython-3.13.5-windows-x86_64-none": { @@ -5003,8 +5803,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "361fa8ca96403f890d0ef4f202ea410b2e121273830c979d6517f2c7e733b5e2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "ab9b81bb06a51791040f6a7e9bffa62eec7ae60041d3cf7409ee7431f2237c7b", "variant": null }, "cpython-3.13.5+freethreaded-darwin-aarch64-none": { @@ -5019,8 +5819,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "a29cb4ef8adcd343e0f5bc5c4371cbc859fc7ce6d8f1a3c8d0cd7e44c4b9b866", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "7223a0e13d5e290fa8441b5439d08fca6fe389bcc186f918f2edd808027dcd08", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-darwin-x86_64-none": { @@ -5035,8 +5835,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-x86_64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "52aeb1b4073fa3f180d74a0712ceabc86dd2b40be499599e2e170948fb22acde", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "869ca9d095f9e8f50fc8609d55d6a937c48a7d0b09e7ab5a3679307f9eb90c70", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-aarch64-gnu": { @@ -5051,8 +5851,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-aarch64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "0ef13d13e16b4e58f167694940c6db54591db50bbc7ba61be6901ed5a69ad27b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-aarch64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "8437225a6066e9f57a2ce631a73eceedffeadfe4146b7861e6ace5647a0472da", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-armv7-gnueabi": { @@ -5067,8 +5867,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-armv7-unknown-linux-gnueabi-freethreaded%2Blto-full.tar.zst", - "sha256": "4eb024f92a1e832c7533d17d566c47eabcb7b5684112290695ef76a149282ee4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-armv7-unknown-linux-gnueabi-freethreaded%2Blto-full.tar.zst", + "sha256": "eabb70dff454bf923a728853c840ee318bc5a0061d988900f225de5b1eb4058b", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-armv7-gnueabihf": { @@ -5083,8 +5883,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-armv7-unknown-linux-gnueabihf-freethreaded%2Blto-full.tar.zst", - "sha256": "cac9d5fe76dcc4942b538d5f5a9fa6c20f261bc02a8e75821dd2ea4e6c214074", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-armv7-unknown-linux-gnueabihf-freethreaded%2Blto-full.tar.zst", + "sha256": "aa49a863343cbbae5a7a1104adb11e9d1d26843598eb5ba9e3db135d27df721a", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-powerpc64le-gnu": { @@ -5099,8 +5899,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-ppc64le-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "66545ad4b09385750529ef09a665fc0b0ce698f984df106d7b167e3f7d59eace", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-ppc64le-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "09008067d69b833b831cc6090edb221f1cce780c4586db8231dcfb988d1b7571", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-riscv64-gnu": { @@ -5115,8 +5915,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-riscv64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "a82a741abefa7db61b2aeef36426bd56da5c69dc9dac105d68fba7fe658943ca", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-riscv64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "3bc92a4057557e9a9f7e8bd8e673dfae54f9abbd14217ae4d986ba29c8c1e761", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-s390x-gnu": { @@ -5131,8 +5931,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-s390x-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "403c5758428013d5aa472841294c7b6ec91a572bb7123d02b7f1de24af4b0e13", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-s390x-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "3206aa76d604d87222ef1cd069b4c7428b3a8f991580504ae21f0926c53a97c5", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64-gnu": { @@ -5147,8 +5947,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-x86_64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "33fdd6c42258cdf0402297d9e06842b53d9413d70849cee61755b9b5fb619836", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "a45ffc5a812c3b6db1dce34fc72c35fb3c791075c4602d0fb742c889bc6bf26d", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64-musl": { @@ -5163,8 +5963,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-x86_64-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "35c387d86e2238d65c16962003475074a771bd96a6e6027606365dd9b23307c2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "c0199c9816dea06a4bc6c5f970d4909660302acb62639a54da7b5d6a4bb45106", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v2-gnu": { @@ -5179,8 +5979,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-x86_64_v2-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "5de3a03470af84e56557a5429137f96632536b0dc07ec119988e9936fcd586b5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v2-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "5ab68785f2b5098e5e984bc49b9df1530bee0097dddcd4fe5f197e48d3e619f2", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v2-musl": { @@ -5195,8 +5995,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-x86_64_v2-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "4e4f198e3cb5248921a7292620156412735b154201571f5da6198167b9888b5c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v2-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "b91e0b88eb9295fae197f870f2291a3bd1d47758f2aabc8c2e1996af1b14f180", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v3-gnu": { @@ -5211,8 +6011,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-x86_64_v3-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "b826fe552e2fcc12859f963410e2c1a109929fe5b73978a74f64c6c812fef92f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v3-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "91bdb8c0792ef29cef987b3198e18f9c441794b33f1266c9155530ddfcfa8b3a", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v3-musl": { @@ -5227,8 +6027,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-x86_64_v3-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "20702fe10bef77477eb02c5a1817650560142b477572f391c297e137daf7a057", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v3-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "01191f5268159e7448a320703c40508802baa1780e855771311f01c550d80b58", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v4-gnu": { @@ -5243,8 +6043,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-x86_64_v4-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "d4024ab82373300a8c1262033af61f64b3348379afe9a112004cf6988468b551", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v4-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "ebcd45022331fe1ebdee98a3b16c876d8be147079206fa3ccc4d81b89fd7ac8b", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v4-musl": { @@ -5259,8 +6059,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-x86_64_v4-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "ab04b63ce58c9ff63c5314ad32a28b25b0b1dbd0a521e1ad056550142f55de43", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v4-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "d30c596a8116a92f860d38b5d5a11e6dd5dea2bbbcdf7439e3223e357298b838", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-windows-i686-none": { @@ -5275,8 +6075,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-i686-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", - "sha256": "9256369550eeb71729dca0094d098d161035806c24b9b9054cb8038b05bd7e0f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-i686-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "d1d421904aa75fce2fb5cb59bacb83787fbe7fbc68dc355c7fdbd19094030473", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-windows-x86_64-none": { @@ -5291,8 +6091,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-x86_64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", - "sha256": "9da2f02d81597340163174ee91d91a8733dad2af53fc1b7c79ecc45a739a89d5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "79c5594d758c7db8323abc23325e17955a6c6e300fec04abdeecf29632de1e34", "variant": "freethreaded" }, "cpython-3.13.5+debug-linux-aarch64-gnu": { @@ -5307,8 +6107,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "6280fc111ff607571362c12e8b1b12a3799a3fbec60498bd0ebff77d30efa89f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "bbc5dab19b3444b8056e717babc81d948b4a45b8f1a6e25d1c193fcaf572bf25", "variant": "debug" }, "cpython-3.13.5+debug-linux-armv7-gnueabi": { @@ -5323,8 +6123,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "48b4fb6454358be8383598876d389fcf5cb5144af07d200e0b0e7c7824084e3e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "2be85ab8daa6c898292789d7fe83d939090a44681b0424789fba278a7dded5fd", "variant": "debug" }, "cpython-3.13.5+debug-linux-armv7-gnueabihf": { @@ -5339,8 +6139,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "bd5df3367a45decbb740432d8e838975ac58c40fc495ed54dbbe321dccb0cd44", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "372ca41f12f67a44d0aedd28714daade9d35b4c14157ea4cdd5b12eea3f5ddf8", "variant": "debug" }, "cpython-3.13.5+debug-linux-powerpc64le-gnu": { @@ -5355,8 +6155,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "e95e45ad547ab71a919a22d76b065881926a00920c5cc1ee6d97be4d66daa12d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "0cb23ab284eab18f471716f4aa1ba4ee570a75a6d79fa5cd091264c514e54a91", "variant": "debug" }, "cpython-3.13.5+debug-linux-riscv64-gnu": { @@ -5371,8 +6171,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "ba433760881a5b0da9b46dcdcf2dd8ca6917901e78dbac74c4ba3ab5e6f3ced3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "315184f457d26e6e20cf69ec169f3fdfdd232e6766a29905cbb6044501fcd4e5", "variant": "debug" }, "cpython-3.13.5+debug-linux-s390x-gnu": { @@ -5387,8 +6187,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "f6f6f8187ede00fa3938d4c734008eafec2041636a8987e2c85df3273004b822", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "8bc2ca17f35f14b6f07882e75c7034263e23fbe003e275e068f2d84e823ed2bf", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64-gnu": { @@ -5403,8 +6203,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "137c6262fa2b26f20d7e1bed1c1467a7665086bb88dc1c2cb40cf23e7da6d469", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "994e77601b9c4f9f48000f575e1d1c640b8b3d795fb65b6b4656b7664df2f95c", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64-musl": { @@ -5419,8 +6219,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "f3c2dce0fb4b62106d3957fc23b757a604251ff624a0d66ef126fab4ece9de0c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "50027e1a8a82d7924993b49941c6c6fdeeee282cb31807021c44459637b1ca1e", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v2-gnu": { @@ -5435,8 +6235,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "962846d36bbb78d76f6ac1db77fb37bb9fdda4d545d211048cc3212247890845", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "e9c3d4fdaabe465969533d0129966957088a609814f5f909e25b1397d4fbe960", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v2-musl": { @@ -5451,8 +6251,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "515b5cdbb612b606a2215aa2ce94114a2442b34e96a1fcc4a45cb3944a0cc159", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "1e16597f1d1a72a9ffde00d9d76eda5fdd269d3c69d40b1ab1ccb0ee2e2aafcd", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v3-gnu": { @@ -5467,8 +6267,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "7e7ec18893cff4597a6268416baba95ac640644527ae7531e074a787819eff8e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "ab9f5380fe0f3d34bb6c02d29103adf2d3b21f47a5f08613fe4f1d7b69d708b4", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v3-musl": { @@ -5483,8 +6283,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "bed63cf51a8ef135e7a03aa303c7e5ee76934cd845e946a64033be8b4d3ea246", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "11d126eb8cf367b904982d3b7581bd9bf77630774a1c68bb3290c2372f01360c", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v4-gnu": { @@ -5499,8 +6299,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "98d2a0c121042905aa874d2afd442098a99d3e00e16af16d940e9460339c7f73", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "8f0bf5fdfd697d5fdd85f9beca4937e9cf93e213d27f46d782d35c30ca2876f6", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v4-musl": { @@ -5515,8 +6315,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.13.5%2B20250612-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "d0125aa6426294f2b66a5ab39a13e392d93ff2e61d7304814fae937297c0d45f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "6fd687f89251c13df002d6bff0f57cbe199f724679df3b8de9bbabafb735d47b", "variant": "debug" }, "cpython-3.13.4-darwin-aarch64-none": { @@ -9739,8 +10539,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.12.11%2B20250612-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "74dd3b2bbbcb5c87a5044e1f3513fe3b07e72fcfdeb039d0ae83b754911ac31e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "4919401e556a2720e2fd75635485ef5a6bb4caedcaa228b49acdd060c1b89f4e", "variant": null }, "cpython-3.12.11-darwin-x86_64-none": { @@ -9755,8 +10555,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.12.11%2B20250612-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "16797cdee1b879ce0f32d9162f2a3af8b91d8ccb663c75ed3afc2384845c24d7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "4d5d672de6a6f2048f875ea0e6b02c9a4a0248d3d57271c740a1b877442552a1", "variant": null }, "cpython-3.12.11-linux-aarch64-gnu": { @@ -9771,8 +10571,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.12.11%2B20250612-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "df383a0992be93314880232c2ecbe9764ee65caee5f72a13ef672684fc7b8063", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "e6797c50c9ce77904e63b8c15cc97a9ff459006bea726bf2ce41ba2ef317d4af", "variant": null }, "cpython-3.12.11-linux-armv7-gnueabi": { @@ -9787,8 +10587,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.12.11%2B20250612-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "92c9c4bd3e4f8bd086ae7ff234273898e83340e4d65fa5b50b0e87db8197fdff", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "60c8843e9e70610f3d82f26b0ba395312382d36e3078d141896ab4aabd422be8", "variant": null }, "cpython-3.12.11-linux-armv7-gnueabihf": { @@ -9803,8 +10603,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.12.11%2B20250612-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "197e34e0a74504f2700d4e4c11cb0d281aa13c628af8b9ad21532250bda45659", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "d97c4bcb8514bf2f7b202e018a2b99e9a4ab797bf1b0c31990641fe9652cae2e", "variant": null }, "cpython-3.12.11-linux-powerpc64le-gnu": { @@ -9819,8 +10619,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.12.11%2B20250612-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "f4bce8f1dcf7bef91d1ea54af48a45333983a41b83c0b8e33e9b07bb4b4499a0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "bebc3aa45ff2d228f7378faedf2556a681e9c626b8e237d39cf4eb6552438242", "variant": null }, "cpython-3.12.11-linux-riscv64-gnu": { @@ -9835,8 +10635,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.12.11%2B20250612-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "26afb0f604cd9cac7af5e3078bbdcb7f701cd1f4956fba0620cc184bc9b32927", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "e82a07c281bef00d14a6bf65b644a4758ee0c8105e0aa549c49471cc4656046f", "variant": null }, "cpython-3.12.11-linux-s390x-gnu": { @@ -9851,8 +10651,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.12.11%2B20250612-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "1c7327875d7669862fd1627c57a813378d866998c5d5008276c8952af7323d19", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "ccd091fd9a8cc1a57da15d1e8ae523b6363c2ce9a0599ac1b63f5c8b70767268", "variant": null }, "cpython-3.12.11-linux-x86_64-gnu": { @@ -9867,8 +10667,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.12.11%2B20250612-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "15a3c9964e485f04d3c92739aca190616e09b2c4fac29b263432f6f29f00c6cf", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "4ab7c1866d0b072b75d5d7b010f73e6539309c611ecad55852411fc8b0b44541", "variant": null }, "cpython-3.12.11-linux-x86_64-musl": { @@ -9883,8 +10683,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.12.11%2B20250612-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "0b68e1de34febc8c57df3d0bf13e3397493bacc432b4cc3d27a338c2d4b8a428", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "2fe4faa95b605a4616c3c3d986fb8ce9a31c16a68b6e77f56c68468a87dff29e", "variant": null }, "cpython-3.12.11-linux-x86_64_v2-gnu": { @@ -9899,8 +10699,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.12.11%2B20250612-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "351b7a97142bc0539ef67e1ad61961a99df419487af422b2242664702f3d3fde", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "531c60132607239f677d6bb91d33645cd7564f1f563e43fff889c67589d6e092", "variant": null }, "cpython-3.12.11-linux-x86_64_v2-musl": { @@ -9915,8 +10715,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.12.11%2B20250612-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "2a4d4edb63585bbfc4afa4bddd5e3efb20202903925ace6f0797df1ad2a6189d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "c2a86e3077ccda859a8669896a4c41ea167c03b105c4307523e6e0b0bc820c25", "variant": null }, "cpython-3.12.11-linux-x86_64_v3-gnu": { @@ -9931,8 +10731,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.12.11%2B20250612-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "bb742a2e0bc09b0afd4c37056ea0bda16095d8468442eadb6285716d0fcb8ab0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "07015ad91ec3ee89c410b09bae84a5910037766783ae47bfb09469f80e1f2337", "variant": null }, "cpython-3.12.11-linux-x86_64_v3-musl": { @@ -9947,8 +10747,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.12.11%2B20250612-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "5bf4aab9fea91a6daae95f40cd12c7d4127bed98bc5ea4fcbee9f03afc1530ef", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "891ba6c18dd688cf7b633ff2bb40aecf007d439c6732b2a9b8de78ffbb0b9421", "variant": null }, "cpython-3.12.11-linux-x86_64_v4-gnu": { @@ -9963,8 +10763,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.12.11%2B20250612-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "05f1a3f7711aae5c65e4557ea102315a779cbe03e39f16dc405f8fc8ede25e83", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "b7f5456cb566bf05bf1f1818eb2dda4f76a3c6257697f31d01ada063ec324f96", "variant": null }, "cpython-3.12.11-linux-x86_64_v4-musl": { @@ -9979,8 +10779,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.12.11%2B20250612-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "d277d0d6d58436ca6e46b7d5d9e1758a89e6b90a0524d789646a4a589c0be998", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "55f5121910e7c9229890e59407dc447807bee7173dc979af7cab8ea6ddd36881", "variant": null }, "cpython-3.12.11-windows-i686-none": { @@ -9995,8 +10795,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.12.11%2B20250612-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "41d5dd36a6964f37b709061c5c01429579ef3c3e117ed7043d6a3a1f336671d6", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "9ddbc2d11445007c8c44ecc8fb23b60c19143e7ff2bc99f9a6d7ab19cf18825e", "variant": null }, "cpython-3.12.11-windows-x86_64-none": { @@ -10011,8 +10811,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.12.11%2B20250612-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "51bc462f3d6caf4aef3d77209d01cd5f6c8fe8213c1ae739e573e1c2c473cb2b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "429e1cc78f4b7591b4ec2063d334dd0bb85afb41e246af1e2b919acdf99fc1b0", "variant": null }, "cpython-3.12.11+debug-linux-aarch64-gnu": { @@ -10027,8 +10827,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.12.11%2B20250612-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "208f407d3880dc84772d8a322776011abf362ac004540debbd2ea5e5f884f398", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "bc8c87d783ae3685df5a0954da4de6513cd0e82135c115c7d472adbabb9c992d", "variant": "debug" }, "cpython-3.12.11+debug-linux-armv7-gnueabi": { @@ -10043,8 +10843,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.12.11%2B20250612-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "880268f3e83952d5bdb32422da2ce7f24ee24c6251b946514ffcdbaedc4ced37", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "40f70903123cf16fb677190388f1a63e45458c2b2ae106c8ddb3ef32ace4c8d1", "variant": "debug" }, "cpython-3.12.11+debug-linux-armv7-gnueabihf": { @@ -10059,8 +10859,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.12.11%2B20250612-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "d3128aba3c94c46d0f5d15474af6a8b340b08ada33a31ad20387cfa46891752c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "fec4a3348980ecbae93e241946bd825f57f26b5a1b99cc56ee4e6d4a3dd53f3c", "variant": "debug" }, "cpython-3.12.11+debug-linux-powerpc64le-gnu": { @@ -10075,8 +10875,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.12.11%2B20250612-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "3c43d94ef5fc2db0fd937f16616c9aceaf0ebc4846ae9454190ed30b0ad4830c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "dea804e42c48de02e12fda2726dea59aa05091c792a2defe0c42637658711c46", "variant": "debug" }, "cpython-3.12.11+debug-linux-riscv64-gnu": { @@ -10091,8 +10891,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.12.11%2B20250612-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "be24538f1d59620a3229582f7adf9ca0df3129bd6591ff45b6ce42d1bb57602f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "128949afd8c2ead23abf0210faa20cbd46408e66bba1cc8950b4fcdb54cea8fa", "variant": "debug" }, "cpython-3.12.11+debug-linux-s390x-gnu": { @@ -10107,8 +10907,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.12.11%2B20250612-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "fa5095dd99ffa5e15dbe151c3d98dbe534c230ce6b9669eef34e037fc885ed91", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "b025e7f6acad441a17394d0fc24f696717dd9ec93718000379ca376a02d7c4a6", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64-gnu": { @@ -10123,8 +10923,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.12.11%2B20250612-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "b1b114b14624ec9955ea1404908636580280a8537ce85ace2fd48197edf82ee0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "99ab13c789dffdf0b43e0c206fd69c73a6713527bf74c984c7e99bab8362ab45", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64-musl": { @@ -10139,8 +10939,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.12.11%2B20250612-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "8535b0850eab8f8cf6e29a5642f7501f5de0318d7805de6aa2cc69c0b7f4895d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "53e842d33cc5a61ee2e954a431ea100a59fa5ae53e355d5f34c00c1555a810ce", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v2-gnu": { @@ -10155,8 +10955,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.12.11%2B20250612-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "d701b1a19ce8fd94c01a72cbe70dd0b79cb59686a7f2fd28c8d68c38d7603f44", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "790b88b4acfc9f58ce1355aba4c73999adf733ac7f8ef87b92b4b092003a0f05", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v2-musl": { @@ -10171,8 +10971,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.12.11%2B20250612-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "584bfb7e4d2bd9232072ab51fef342ba250d288cb97066a88713de33280332ad", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "88e92e303d1f61f232f15fe3b266abf49981430d4082451dfda3b0c16900f394", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v3-gnu": { @@ -10187,8 +10987,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.12.11%2B20250612-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "b7bdaf17df1f0bb374f2553d80e69bd44e6bbc1776a00253661eddbccffc7194", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "701553f500dc2f525ce6a4c78891db14942b1a58fb1b5fa4c4c63120208e9edb", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v3-musl": { @@ -10203,8 +11003,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.12.11%2B20250612-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "ee50314ad02b40bf613e9247718a77ac6b5c61e09254c772a5ccea4a3b446262", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "8e3c803ed8d3a32800b1d1b5c3279e11aac1ee07bf977288f77d827ab210794f", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v4-gnu": { @@ -10219,8 +11019,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.12.11%2B20250612-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "7e16a050d0d0f91312048cb98a1f06d7e300c4723076fdf28d28fa282c45f8a2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "c66f966c7d32e36851126756ba6ce7cfc6ec0fd2f257deb22420b98fb1790364", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v4-musl": { @@ -10235,8 +11035,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.12.11%2B20250612-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "44458a2a2ef2e1bbc4fb226b479d5d00a2fc15788c8b970d8df843e3535b0595", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "438d8cfb97c881636811adf2bceaac28c756cccf852c460716d5f616efae3698", "variant": "debug" }, "cpython-3.12.10-darwin-aarch64-none": { @@ -14283,8 +15083,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.11.13%2B20250612-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "3cc448659ee0d6aff8a90ca0dcaf00c29974f5d48ccc2c37e7a6e3baa6806005", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "e484c41e7a5c35b2956ac547c4f16fc2f3b4279b480ba3b89c8aef091aa4b51d", "variant": null }, "cpython-3.11.13-darwin-x86_64-none": { @@ -14299,8 +15099,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.11.13%2B20250612-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "6617e8e95ccbbd27fc82f29b0e56e9d9b8a346435c3510374e4410bfd1150421", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "1d18084cfa3347dc5e1a343cfd42d03de7ef69c93bf5ad31b105cfe12d7e502d", "variant": null }, "cpython-3.11.13-linux-aarch64-gnu": { @@ -14315,8 +15115,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.11.13%2B20250612-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "ac7257a5c1c9757ce4aa61d6c9bc443cd8ab052105b0e1c6714040c6e9e50eff", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "8c26b055937a25ccf17b7578bad42ce6c9fb452a6d4b1d72816755e234922668", "variant": null }, "cpython-3.11.13-linux-armv7-gnueabi": { @@ -14331,8 +15131,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.11.13%2B20250612-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "0b01b96e0e4190f64fef6e2c76d0746321fc8cc91c7f3319452a90eaaa376c00", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "17e6023581689c1bd51f37a381e0700770e387d7696bf8d6c7dae3bcea7fdd61", "variant": null }, "cpython-3.11.13-linux-armv7-gnueabihf": { @@ -14347,8 +15147,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.11.13%2B20250612-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "f30cc16c9ea9a2795b5cafef91f3637165781a6a7a54fdc4baf6438a0800e7ce", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "5a26f2dac227f6667724567027a4b502dea2e27b1105110a71d5a5df0b144a88", "variant": null }, "cpython-3.11.13-linux-powerpc64le-gnu": { @@ -14363,8 +15163,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.11.13%2B20250612-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "472ec854f7944528f366b08e8f6efbb4c02ed265eecc259c0e1f7cf12400ea14", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "8273efc027cce7fb3e5ec2a283be0266db76a75dea79bccf24470ff412835305", "variant": null }, "cpython-3.11.13-linux-riscv64-gnu": { @@ -14379,8 +15179,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.11.13%2B20250612-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "024ba68fc755b595f4a21dbc1d8744231e51b76111d8d83e96691fb7acbf37a5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "cf4719c427131c8d716b0642ddfc84d08d0e870f29cc54c230f3db188da44c72", "variant": null }, "cpython-3.11.13-linux-s390x-gnu": { @@ -14395,8 +15195,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.11.13%2B20250612-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "6f404269ce33fe0dd8cc918da001662b6ffdfbe7eb13f906cbc92e238f75f6be", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "2ec2fd56109668b455effb6201c9ab716d66bd59e1d144fa2ab4f727afa61c19", "variant": null }, "cpython-3.11.13-linux-x86_64-gnu": { @@ -14411,8 +15211,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.11.13%2B20250612-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "059cbbfd84bfc9ef8a92605fa8aef645bbb45b792cac8adf865050a5e7d68909", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "c6dd8a6fef05c28710ec62fab10d9343b61bbb9f40d402dad923497d7429fd17", "variant": null }, "cpython-3.11.13-linux-x86_64-musl": { @@ -14427,8 +15227,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.11.13%2B20250612-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "0237b76f625f08683a2e615ae907428240d90898b17a60bdec88a85bf9095799", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "3d4fcdcd36591d80ca3b0affb6f96d5c5f56c5a344efbd013e406c9346053005", "variant": null }, "cpython-3.11.13-linux-x86_64_v2-gnu": { @@ -14443,8 +15243,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.11.13%2B20250612-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "9ae198daf242ffd6ad5b395594aa157aba62a044d007202cb03659fbb94d3132", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "9cc3c80f518031a0f2af702a38e37d706015bf411144ca29e05756eeee0f32b2", "variant": null }, "cpython-3.11.13-linux-x86_64_v2-musl": { @@ -14459,8 +15259,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.11.13%2B20250612-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "a74c87f366001349fcf3297b87698ac879256ed4b73776ff8fa145c457c0cd13", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "1f1453776244baff8ff7a17d88fd68fdd10c08a950c911dd25cc28845c949421", "variant": null }, "cpython-3.11.13-linux-x86_64_v3-gnu": { @@ -14475,8 +15275,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.11.13%2B20250612-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "b24df39e08456e50fc99c43e695a46240b11251e8b43666f978ee98ec1197e05", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "094fef0b6e8d778a61d430a44a784caab0c1be5a2b94d7169b17791c6fdfa2e5", "variant": null }, "cpython-3.11.13-linux-x86_64_v3-musl": { @@ -14491,8 +15291,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.11.13%2B20250612-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "2767b36bb48da971dc5af762b42df2c9d1f4839326d26c5a710b543372dcc640", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "526c2c8088c1a41c4bd362c1d463fccaa37129dfe4f28166eabb726664a7550e", "variant": null }, "cpython-3.11.13-linux-x86_64_v4-gnu": { @@ -14507,8 +15307,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.11.13%2B20250612-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "a1478d014d07e4a56f0c136534931989a93110ab0b15a051a33fbf0c22bec0d2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "27b7bd55029877537242b702badd96846ba1b41346998dfd8c7be191b6b393fa", "variant": null }, "cpython-3.11.13-linux-x86_64_v4-musl": { @@ -14523,8 +15323,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.11.13%2B20250612-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "adb7b67a8522c1ef1b941da9fd47dd7c263c489668a40bc9d0b0e955b0e25b18", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "f8dc02227a0156fd28652148486878ef7a50de09a5b8373555a0573cc2347f18", "variant": null }, "cpython-3.11.13-windows-i686-none": { @@ -14539,8 +15339,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.11.13%2B20250612-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "bdb7c0727af0b0d0d5f0d6b37a3139185a0f9259e4ce70f506c23e29e64fcb0f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "0d1eb6a83db554379555279da7a00ef421306b35f5fd2603087a960c77aef5dc", "variant": null }, "cpython-3.11.13-windows-x86_64-none": { @@ -14555,8 +15355,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.11.13%2B20250612-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "22c2ab8be0d4028ddc115583f2c41c57ee269c115ef48a41ddde9adba5bac15b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "709f7f3a0913226230f50c132ab6a1a517206c9d0b2e30c1779574913f0ac74b", "variant": null }, "cpython-3.11.13+debug-linux-aarch64-gnu": { @@ -14571,8 +15371,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.11.13%2B20250612-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "fbd958ddbe10dd9d9304eb3d3dc5ed233524e1e96746e7703ceafedf2af3b82e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "09a257ac990d66b64f383767154ab8dde452457fd973e403c3ffe73ea2ef6041", "variant": "debug" }, "cpython-3.11.13+debug-linux-armv7-gnueabi": { @@ -14587,8 +15387,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.11.13%2B20250612-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "b03bcfe961888241beefc5648802041764f3914bcb7aadce8d2cbfffd23694d4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "d8dc85bbec1c157e078c66def67ad65157a96ba19fcde8962131a41f4f600e98", "variant": "debug" }, "cpython-3.11.13+debug-linux-armv7-gnueabihf": { @@ -14603,8 +15403,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.11.13%2B20250612-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "3b7d013b8c200194962ce7dd699d628282ae4343ecdfe33ab1e4ac3cb513e5a5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "b27b622310194321853d106f4b344b73592b5ca85b1cc9ed3bdb19bdb3d6f0d0", "variant": "debug" }, "cpython-3.11.13+debug-linux-powerpc64le-gnu": { @@ -14619,8 +15419,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.11.13%2B20250612-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "fc84e9f6c98b76525ae2267d24f27c69da6e1ddd77f1cac2613d8b152fa436f1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "8ba75ecfead212f9f5e9b8e2cbc2ad608f5b6812619da4414fd6b283f2acbf78", "variant": "debug" }, "cpython-3.11.13+debug-linux-riscv64-gnu": { @@ -14635,8 +15435,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.11.13%2B20250612-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "fc963382c154fbb924b05895cc9849e83998a32060083f7167ae53f5792ac145", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "c199f80c7d4502ba3d710c4c7450f81642bdac5524079829b7c7b8002b9747a8", "variant": "debug" }, "cpython-3.11.13+debug-linux-s390x-gnu": { @@ -14651,8 +15451,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.11.13%2B20250612-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "01679e250e8693b9d330967741569822db15693926bcdf8e4875b51a8767e9c1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "23934c0dc86aeb1c5d0f0877ac8d9d6632627a0b60c9f4f9ad9db41278b6745f", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64-gnu": { @@ -14667,8 +15467,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.11.13%2B20250612-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "206917fe06cdeeb76abd30e3dd01a71355fd41b685c1dbbddbfd0ad47371d5b6", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "a405a9b2bc94b564a03f351b549c708f686aeade9aec480108b613332cf9cc48", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64-musl": { @@ -14683,8 +15483,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.11.13%2B20250612-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "5b7a576ba0e99e7cf734b5a0f1f0fb48564c42a04d373e1370b22f2e70995d79", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "2b3fecb1717cf632e789c4b8c03eda825f9c3e112cac922d58b42e7eecb8731f", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v2-gnu": { @@ -14699,8 +15499,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.11.13%2B20250612-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "1b80e41e4620e71adb0418a8bb06ec8999aa0dc69efdec0e44ca28c94543e304", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "ab59a4df1e7d269400116e87daaa25b800ffbb24512b57f8d2aa4adbe3d43577", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v2-musl": { @@ -14715,8 +15515,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.11.13%2B20250612-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "113f457a87a866f42662cf5789eace03b7548382e2dd0a6b539338defe621697", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "1fc167ea607ef98b6d0dbac121a9ae8739fdf958f21cbbd60cac47b7ce52b003", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v3-gnu": { @@ -14731,8 +15531,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.11.13%2B20250612-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "f00ae85aa8d900bf2d4e5711395c13458ff298770281dac908117738491cbe51", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "6140e14e713077169826458a47a7439938b98a73810c5e7a709c9c20161ae186", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v3-musl": { @@ -14747,8 +15547,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.11.13%2B20250612-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "9f10f5b6622b17237163ae8bff9ec926ce9c44229055c9233e59f16aa7d22842", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "77cf2b966c521c1036eb599ab19e3f6e0d987dbb9ba5daa86d0b59d7d1a609a1", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v4-gnu": { @@ -14763,8 +15563,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.11.13%2B20250612-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "6e4de8ff2660b542278c84bf63b0747240da339865123e3a5863de2d60601ba6", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "92583c28fdcbbf716329a6711783e2fb031bb8777e8a99bd0b4d3eed03ec0551", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v4-musl": { @@ -14779,8 +15579,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.11.13%2B20250612-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "f973507e2c54a75af1583ae7e5a6bf6ba909c5d0e372f306aa6a4d6be8eb92f9", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "dda82882159f45a926486bc221739c2ff5c0b1d9fa705a51d0f3f729f736162c", "variant": "debug" }, "cpython-3.11.12-darwin-aarch64-none": { @@ -18571,8 +19371,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.10.18%2B20250612-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "d588e367ad0ccc96080f02a6e534b272e1769aeddc0a2ce46da818c42893ebfd", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "e610b8bb397513908b814cb2a1c9f95e97aac33855840bf69f1442ff040ddd33", "variant": null }, "cpython-3.10.18-darwin-x86_64-none": { @@ -18587,8 +19387,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.10.18%2B20250612-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "2758bbee1709eb355cf0c84a098cf51807a94e2f818eb15a735b71c366b80f9b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "5c4d02b1626fd677ee3bc432d77e6dca5bbb9b1f03b258edd4c8cf6902eba902", "variant": null }, "cpython-3.10.18-linux-aarch64-gnu": { @@ -18603,8 +19403,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.10.18%2B20250612-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "89a01966d48e36f5ba206f3861ad41b6246160c3feae98a2ffe0c4ce611acfeb", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "12519960127a5082410496f59928c3ed1c2d37076d6400b22e52ee962e315522", "variant": null }, "cpython-3.10.18-linux-armv7-gnueabi": { @@ -18619,8 +19419,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.10.18%2B20250612-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "a095eaeac162f0a57d85b7f7502621d9b9572a272563a59b89b69ae4217e031e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "6dd0d28f7229d044ec1560e11dcbb5452166921c4a931aeae9b9f08f61451eb9", "variant": null }, "cpython-3.10.18-linux-armv7-gnueabihf": { @@ -18635,8 +19435,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.10.18%2B20250612-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "559799c5b87d742b3b71dc1e7b015a9cd6d130f7b6afcf6ad8716c75c204d47e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "810089263699a427f8610070ba7ebad2a78dac894e93a6c055ec28f3303c704e", "variant": null }, "cpython-3.10.18-linux-powerpc64le-gnu": { @@ -18651,8 +19451,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.10.18%2B20250612-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "740a2e5f935c6d7799f959731b7cd8f855c1e572ad43f0ec16417c3390f4551d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "263befb4049ed1a272b9299ce41f5d5ef855998462a620dd6b62ecfde47d5154", "variant": null }, "cpython-3.10.18-linux-riscv64-gnu": { @@ -18667,8 +19467,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.10.18%2B20250612-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "00a436a8f8ad236d084a6d6a1308543755d9175e042b89ea3c06cc1b1651e6aa", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "0999d7be0eb75b096791f2f57369dd1a6f4cd9dc44eb9720886268e3a3adddd7", "variant": null }, "cpython-3.10.18-linux-s390x-gnu": { @@ -18683,8 +19483,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.10.18%2B20250612-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "d23284bc718fb049dd26056efc632042824409599cae9a4c2362277761b50e94", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "2ac9a0791f893b96c4a48942aa2f19b16ddbf60977db63de513beef279599760", "variant": null }, "cpython-3.10.18-linux-x86_64-gnu": { @@ -18699,8 +19499,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.10.18%2B20250612-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "e884283a9a3bb97e9432bbda0bf274608d8fce2f27795485e4a93bbaef66e5a1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "9d1a87e52e2be1590a6a5148f1b874ba4155095c11e5afad7cb9618e7a4a1912", "variant": null }, "cpython-3.10.18-linux-x86_64-musl": { @@ -18715,8 +19515,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.10.18%2B20250612-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "cea06c4409e889945c448ec223f906e9e996994d6b64f05e9d92dc1b6b46a5f8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "c85cac7c12bb3c6bf09801f9b3f95d77acb5aa5de10a85aeceafb026d641c62c", "variant": null }, "cpython-3.10.18-linux-x86_64_v2-gnu": { @@ -18731,8 +19531,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.10.18%2B20250612-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "4636b6756eb1a9f6db254aac8ae0007c39614fcbf065daf8dc547530ac77c685", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "d5f70ab75fc24ab3efa4b2edb14163bb145c1f9d297d03dde6c2a40ccb0eb9ac", "variant": null }, "cpython-3.10.18-linux-x86_64_v2-musl": { @@ -18747,8 +19547,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.10.18%2B20250612-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "6e0a68e53059d1ccf5a0efc17d60927e53c13e40b670425c121f35cd3fd10981", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "7800a067ffb6ebd9c226c075da8c57ff843f9b9e7b89b9c14e013bc33f042e4e", "variant": null }, "cpython-3.10.18-linux-x86_64_v3-gnu": { @@ -18763,8 +19563,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.10.18%2B20250612-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "1c164e2eeb586d930a427e58db57b161b8ec4b9adf4d31797fdccf78d373a290", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "ab36f3e99a1fb89cb225fe20a585068a5b4323d084b4441ce0034177b8d8c3bf", "variant": null }, "cpython-3.10.18-linux-x86_64_v3-musl": { @@ -18779,8 +19579,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.10.18%2B20250612-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "4ae64d7a6ba3c3cb056c2e17c18087b1052e13045e4fbb2e62e20947de78c916", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "7e91185f8d1e614c85447561752476b7c079e63df9a707bb9b4c0f1649446e00", "variant": null }, "cpython-3.10.18-linux-x86_64_v4-gnu": { @@ -18795,8 +19595,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.10.18%2B20250612-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "cd64acdb527382c7791188f8b5947d1a9e80375ad933e325fb89a128af60234d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "65cfa877bc7899f0a458ff5327311e77e0b70fa1c871aeb6dfa582d23422337e", "variant": null }, "cpython-3.10.18-linux-x86_64_v4-musl": { @@ -18811,8 +19611,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.10.18%2B20250612-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "8d300050b13d3674f5d19bf402780ec2fb19bf594dd75fd488b83856ed104def", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "869b5a0919558fe8bbdac4d75a9e9a114a4aa7ca0905da4243ec1b7e4ff99006", "variant": null }, "cpython-3.10.18-windows-i686-none": { @@ -18827,8 +19627,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.10.18%2B20250612-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "3df494fd91ccc55ea0b512f70d4b49b4bee781b6e31bfa65c8d859150b6d3715", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "a0a5edc7cff851a59dca8471858262a2bb3708b00ad443dd908c3b67384b8ee4", "variant": null }, "cpython-3.10.18-windows-x86_64-none": { @@ -18843,8 +19643,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.10.18%2B20250612-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "4d6337bfafdb6af5c4c6fdb54fd983ead0c4d23cf40fb6b70fce0bd8b3b46b59", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "d6e97e2984ff157d3b5caf9078eb45613684a39368da2fbac8dd364080322e82", "variant": null }, "cpython-3.10.18+debug-linux-aarch64-gnu": { @@ -18859,8 +19659,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.10.18%2B20250612-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "2808899e02b96a4ed8cfe7a4e9e42372c0d8746f7cdbf52d21645bd4da1f9f9f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "e42a95657c84804f0edf02db89e12e907e1fb9d8c92b707214be7c1b3dc0f4d5", "variant": "debug" }, "cpython-3.10.18+debug-linux-armv7-gnueabi": { @@ -18875,8 +19675,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.10.18%2B20250612-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "e327ab463c630396b9a571393adfce4d63b546a2af280c894fef1c74dbb21223", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "e733a7aa76137d5a795ec5db08d5c37d06e111e2343d70308f09789ced7b4d32", "variant": "debug" }, "cpython-3.10.18+debug-linux-armv7-gnueabihf": { @@ -18891,8 +19691,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.10.18%2B20250612-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "031a467de049990d3133e6ff26612e5d227abda4decfa12ea8340ca8ec7e55d4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "06ec2977ec2d01f3225272926ab43d66f976f1481d069d9a618a0541b2644451", "variant": "debug" }, "cpython-3.10.18+debug-linux-powerpc64le-gnu": { @@ -18907,8 +19707,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.10.18%2B20250612-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "9b48f5108b419e4371142ec5a65a388473e4990181c82343c3dfaf3d87f02a5a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "2ae4ed1805b647f105db8c41d9ac55fcda2672526b63c2e1aa9d0eb16a537594", "variant": "debug" }, "cpython-3.10.18+debug-linux-riscv64-gnu": { @@ -18923,8 +19723,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.10.18%2B20250612-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "8215a063192a64fad2b5838b34d20adcd30da5cc2e9598f469eea8d3f0de09f5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "c9fdef27a8295c29c4ba6fd2daff344c12471f97ca7a19280c3e0f7955438668", "variant": "debug" }, "cpython-3.10.18+debug-linux-s390x-gnu": { @@ -18939,8 +19739,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.10.18%2B20250612-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "77b9f02331df8acf41784648f75cc77c5ab854546a405b389067f61ded68a5c6", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "eac2aab5eaea5e8df9c24918c0ade7a003abe6d25390d4a85e7dd8eea6eee1e3", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64-gnu": { @@ -18955,8 +19755,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.10.18%2B20250612-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "bba5598c6fc8f85e68b590950b5e871143647921197be208a94349d7656eafdf", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "1d651aebc511e100a80142b78d2ea4655a6309f5a87a6cbd557fed5afda28f33", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64-musl": { @@ -18971,8 +19771,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.10.18%2B20250612-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "6c006d1daae48ba176bb85fd0c444914d9e2ee20b58e8131c158bb30fe9097c9", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "c84b94373b1704d5d3a2f4c997c3462d1a03ebbd141eeeaec58da411e1cd62c1", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v2-gnu": { @@ -18987,8 +19787,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.10.18%2B20250612-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "d93af68181616ae0f2403ac74d1cc2ea6ebced63394da5b3a3234398748ce4cf", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "72eacef9b8752ad823dbc009aa9be2b3199f7556218a63a4de0f1cb1b6bd08ec", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v2-musl": { @@ -19003,8 +19803,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.10.18%2B20250612-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "af25b4aed5f7cdeb133c19c61f31038020315313eadbc4481493c8efce885194", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "01a9ddcf418a74805c17ceaa9ecdcbe0871f309e0649b43e6cd6f0fe883d8a14", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v3-gnu": { @@ -19019,8 +19819,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.10.18%2B20250612-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "3da0ce9cd97415c86cdb8556e64883678e6b4684f74600b3bc9c90424db787af", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "06d979ecd8256d978f5a76f90063b198f08bb6833ead81e52a32f0213337f333", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v3-musl": { @@ -19035,8 +19835,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.10.18%2B20250612-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "65fe2745075de7290c58b283af48acb6ab403396792a9682d24523bd025d7b01", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "f910de7dcd093e74d45b349bcd7a0cf291602979a90ef682fcf2b90a6bd4e301", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v4-gnu": { @@ -19051,8 +19851,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.10.18%2B20250612-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "9b91c5309cbfee08636d405fce497b371e69787e9042be62dd8e262fc3800422", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "c3516587af46e3e38115b282f8585960e5050ad9a822e8d85c4159f1a209960f", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v4-musl": { @@ -19067,8 +19867,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.10.18%2B20250612-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "aa146828d807b8c206541cc8b0bf2b5e7cecd1a9cf5f03b248e73b69b8ef5190", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "827cbcaad8cb5e16192f2f89f2e62a48ee3be89ec924d83e526f213806433d4a", "variant": "debug" }, "cpython-3.10.17-darwin-aarch64-none": { @@ -24011,8 +24811,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.9.23%2B20250612-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "05741156232cc28facaefbda2d037605dd71614d343c7702e0d9ab52c156945e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "dbb161ced093b9b3b7f93ae7064fe20821471c2a4ac884e2bc94a216a2e19cba", "variant": null }, "cpython-3.9.23-darwin-x86_64-none": { @@ -24027,8 +24827,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.9.23%2B20250612-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "3e6cf3c8c717f82f2b06442e0b78ececa7e7c67376262e48bf468b03e525ef31", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "b7dbbec3f74512ce7282ffeb9d262c3605e824f564c49c698a83d8ad9a9810df", "variant": null }, "cpython-3.9.23-linux-aarch64-gnu": { @@ -24043,8 +24843,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.9.23%2B20250612-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "96ec244baeaf57a921da7797da41e49e9902a2a6b24c45072a8acee7ff9e881d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "f8886fd05163ff181bc162399815e2c74b2dc85831a05ce76472fe10a0e509d6", "variant": null }, "cpython-3.9.23-linux-armv7-gnueabi": { @@ -24059,8 +24859,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.9.23%2B20250612-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "9c58d7126db2a66b81bff94b0e15a60974687f9ef74985d928e44397a10986cb", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "f05ac5a425eaf4ae61bfb8981627fb6b6a6224733d9bfbe79f1c9cd89694fa1a", "variant": null }, "cpython-3.9.23-linux-armv7-gnueabihf": { @@ -24075,8 +24875,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.9.23%2B20250612-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "929fbed568da7325b7db351d32cd003ee77c4798f0f946a6840935054a07174f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "c5cb4da17943d74c1a94b6894d9960d96e8e4afc83e629a5788adbd2f8f4d51f", "variant": null }, "cpython-3.9.23-linux-powerpc64le-gnu": { @@ -24091,8 +24891,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.9.23%2B20250612-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "b52371b6485ab4603da77ff49b8092d470586a2330e56d58d9f8683a5590ae68", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "815e88f67598ebb039e1735a5d5f4f9facf61481111657a3ecefb69993a6a8ab", "variant": null }, "cpython-3.9.23-linux-riscv64-gnu": { @@ -24107,8 +24907,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.9.23%2B20250612-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "ef4e83f827377bdb4656130ee953b442a33837ca31ef71547ac997d9979b91e4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "595c1ca3dbad46cc2370e17a5d415da82b75140a057449fc7de5d41faa2cc87a", "variant": null }, "cpython-3.9.23-linux-s390x-gnu": { @@ -24123,8 +24923,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.9.23%2B20250612-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "c8f5d2951900e41db551b692f2aa368e89449d3a4cf2761a80eb350fbd25bc0b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "e6b2c9ca1962a069959f70d304e15ba0e63506db6e1b6bc3c75d03ca7012ac9f", "variant": null }, "cpython-3.9.23-linux-x86_64-gnu": { @@ -24139,8 +24939,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.9.23%2B20250612-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "461a5ef25341b2e9f62f21d3fa4184ac51af59cebb5fb9112fe64e338851109f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "e6707a3a40fc68f37bad9b7ad9e018715af3be057b62bc80fee830a161c775f3", "variant": null }, "cpython-3.9.23-linux-x86_64-musl": { @@ -24155,8 +24955,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.9.23%2B20250612-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "f5f3b7e7be74209fa76a9eed4645458e82bec3533d8aad1c45770506b1870571", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "9ce6c722a5436f4adc1b25c01c5ada2c230f0089302a544d35b5b80702c0a7db", "variant": null }, "cpython-3.9.23-linux-x86_64_v2-gnu": { @@ -24171,8 +24971,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.9.23%2B20250612-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "302ae70b497361840e6f62e233f058ea0a41b91e4cc01050da940419b6b4b3d6", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "d6d6329699af5d463cfecef304e739347b1e485d959a63dc54b39a818dd0c5dd", "variant": null }, "cpython-3.9.23-linux-x86_64_v2-musl": { @@ -24187,8 +24987,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.9.23%2B20250612-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "740f1f6e1c1a70e7e7aaa47c0274ee6208b16bd1fe8d0c3600ecccb2481a5881", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "eb082839a9dd585e151395041f7832bb725a3bfc4e153e3f41949d43163902ab", "variant": null }, "cpython-3.9.23-linux-x86_64_v3-gnu": { @@ -24203,8 +25003,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.9.23%2B20250612-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "d4689ad72cb22973d12c4f7128588ed257e4976d69d92f361da4ddbcec8ce193", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "2a0080b853d55ae9db6b20209d63fbd4cacc794be647e7f9cf1a342dfd7e5796", "variant": null }, "cpython-3.9.23-linux-x86_64_v3-musl": { @@ -24219,8 +25019,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.9.23%2B20250612-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "5c094d6d91bf0e424c12b26627e0095f43879fefc8cf6050201b39b950163861", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "3eb47ce30946e30415538acf0e7a3efbac4679d5d16900c5c379f77ebef3321d", "variant": null }, "cpython-3.9.23-linux-x86_64_v4-gnu": { @@ -24235,8 +25035,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.9.23%2B20250612-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "2933a49ca7292e5ed1849aeb59057ec75ea9e018295d1bb8a3c155a7e46b3dde", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "2a81c218277a16515531df2568b8dc72cd1b2a2c22268882d57881a6f0f931d4", "variant": null }, "cpython-3.9.23-linux-x86_64_v4-musl": { @@ -24251,8 +25051,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.9.23%2B20250612-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "b8e92fe27a41d7d3751cdbffff7b4de3c84576fd960668555c20630d0860675e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "a30191e1ecd58075909c5f28f21ddc290687afaa216b5cdd8c1f5a97963d1a95", "variant": null }, "cpython-3.9.23-windows-i686-none": { @@ -24267,8 +25067,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.9.23%2B20250612-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "d40486ddf29d6a70dda7ea8d56666733943de19ff8308b713175cba00d0f6a0f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "f0482615c924623c3441ea96bfa1ea748b597db9099b8ad2e7c51526d799020b", "variant": null }, "cpython-3.9.23-windows-x86_64-none": { @@ -24283,8 +25083,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.9.23%2B20250612-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "5c4edcc6861f7fc72807c600666b060f3177a934577a0b1653f1ab511fdac4a0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "d73d4aee5f2097cd1f5cc16a0f345c7cb53c3e8b94f1ca007b918a3152185c4b", "variant": null }, "cpython-3.9.23+debug-linux-aarch64-gnu": { @@ -24299,8 +25099,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.9.23%2B20250612-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "c74a865c4e3848b38f4e1e96b24ba4e839c5776cb0ea72abe7b652a1524a1a51", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "140645dbd801afa90101806401cb2f855cd243e7b88a6c3bce665e39c252c6e1", "variant": "debug" }, "cpython-3.9.23+debug-linux-armv7-gnueabi": { @@ -24315,8 +25115,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.9.23%2B20250612-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "5833d757964b5fe81f07183def651556ccd2c5cc9054373a6483b4ffb140ea72", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "b6e8223142843640e8e5acbf96eaea72f2e7218e9da61de1d2215a626ebe207b", "variant": "debug" }, "cpython-3.9.23+debug-linux-armv7-gnueabihf": { @@ -24331,8 +25131,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.9.23%2B20250612-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "f5880944d32da9b4b65e88a21e4e2c3410ca299886a86db19566936b36fc445a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "1b9cc881bcf52e9dd22da45782a11beda7de63e5d0644092412542ec8c3c2ce8", "variant": "debug" }, "cpython-3.9.23+debug-linux-powerpc64le-gnu": { @@ -24347,8 +25147,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.9.23%2B20250612-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "5ef02120a1e82c3d2c60f04380c8cac171cea59bc647e6089d4b2971e70a4b06", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "93cdb8c7bc5a13c273855b97e827a3b9b12522b7d7ed14e5110a7fa5043f7654", "variant": "debug" }, "cpython-3.9.23+debug-linux-riscv64-gnu": { @@ -24363,8 +25163,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.9.23%2B20250612-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "b480c9dd67c1c2bccce609f145f562958e1235d294f8b5be385d3b5daca76e23", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "fb04b810e7980a211ab926f0db3430919790b86dee304682e2453669063fff34", "variant": "debug" }, "cpython-3.9.23+debug-linux-s390x-gnu": { @@ -24379,8 +25179,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.9.23%2B20250612-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "c2f3433e9820f2c8a70363d8faa7e881079e5b9e50a4764702c470add3c899ee", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "394cd53c4c012d7c32950600462a68fe0ed52f5204f745a7ebbc19f2473121b3", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64-gnu": { @@ -24395,8 +25195,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.9.23%2B20250612-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "120bc8aafdc5dcb828c27301776ccab4204feb3ad38fe03d7b0c8321617762f4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "a772240194dd997da21e53029fde922da7e1893af1356eb47d710b2fbf144b0e", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64-musl": { @@ -24411,8 +25211,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.9.23%2B20250612-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "c79452c6ac6b7287b4119ba7a94cdaaa7edd50dbb489c76a2b3f1e3d0563b35a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "8153da9228087fc1ed655a83eb304318cc646333966ccb9ccd75ab9589b8585f", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v2-gnu": { @@ -24427,8 +25227,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.9.23%2B20250612-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "75d93b474294a74cbe8b2445f5267299cf9db5e4fa0c6820408c5ac054549ff2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "79d6c604c491c02cff2e1ffd632baf4d2418a85fe10dd1260ec860dde92322c1", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v2-musl": { @@ -24443,8 +25243,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.9.23%2B20250612-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "a34fcea0dd98f275da0cb90e49df2d2bb6280fd010422fbe4f9234fabfc0f74d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "7e222a1a6333e1e10bf476ac37ca2734885cb2cf92a7a8d3dc4f7fb81f69683b", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v3-gnu": { @@ -24459,8 +25259,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.9.23%2B20250612-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "0415c91da8059adc423689961d5cf435c658efdca4f5a2036c65b9b7190ab26f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "a03b38980fb6578be1a7c8e78f7219662e72ac1dc095b246d5a0b06d27e61ece", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v3-musl": { @@ -24475,8 +25275,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.9.23%2B20250612-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "2bb65e171d6428d549162b5b305c8ab6e6877713a33009107d5f2934a13d547e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "93df57f432ad2741e8edeefb1caf7a3d538f8c90cac732c6865d914d17577aed", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v4-gnu": { @@ -24491,8 +25291,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.9.23%2B20250612-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "1b40bf44d9eddf38d2e0e3a20178358ece16fc940c5ee1e3cac15ae3d2d6c70e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "2ba7ac2755c52728882cf187e8f2127b0b89cade6eaa2d42dd379d1bcd01a0a9", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v4-musl": { @@ -24507,8 +25307,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250612/cpython-3.9.23%2B20250612-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "13b3c21d113b4e796dba7f52b93dfa97b7e658a910a90ab0433477db200c40ee", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "5253927e725888dae7a39caca1a88dcbfa002b2a115cb6e06000db04e133b51d", "variant": "debug" }, "cpython-3.9.22-darwin-aarch64-none": { diff --git a/crates/uv/tests/it/python_install.rs b/crates/uv/tests/it/python_install.rs index 817e71052..7fd596cd8 100644 --- a/crates/uv/tests/it/python_install.rs +++ b/crates/uv/tests/it/python_install.rs @@ -1444,8 +1444,8 @@ fn python_install_314() { ----- stdout ----- ----- stderr ----- - Installed Python 3.14.0b2 in [TIME] - + cpython-3.14.0b2-[PLATFORM] + Installed Python 3.14.0b3 in [TIME] + + cpython-3.14.0b3-[PLATFORM] "); // Install a specific pre-release @@ -1465,7 +1465,7 @@ fn python_install_314() { success: true exit_code: 0 ----- stdout ----- - [TEMP_DIR]/managed/cpython-3.14.0b2-[PLATFORM]/[INSTALL-BIN]/python + [TEMP_DIR]/managed/cpython-3.14.0b3-[PLATFORM]/[INSTALL-BIN]/python ----- stderr ----- "); @@ -1475,7 +1475,7 @@ fn python_install_314() { success: true exit_code: 0 ----- stdout ----- - [TEMP_DIR]/managed/cpython-3.14.0b2-[PLATFORM]/[INSTALL-BIN]/python + [TEMP_DIR]/managed/cpython-3.14.0b3-[PLATFORM]/[INSTALL-BIN]/python ----- stderr ----- "); @@ -1484,7 +1484,7 @@ fn python_install_314() { success: true exit_code: 0 ----- stdout ----- - [TEMP_DIR]/managed/cpython-3.14.0b2-[PLATFORM]/[INSTALL-BIN]/python + [TEMP_DIR]/managed/cpython-3.14.0b3-[PLATFORM]/[INSTALL-BIN]/python ----- stderr ----- "); From f892b8564fc3dc2e9c6db4453d8d8860c4a8ba65 Mon Sep 17 00:00:00 2001 From: John Mumm Date: Fri, 27 Jun 2025 19:54:52 +0200 Subject: [PATCH 079/349] Return `Cow` from `UrlString::with_` methods (#14319) A minor performance improvement as a follow-up to #14245 (and an accompanying test). --- crates/uv-distribution-types/src/file.rs | 53 ++++++++++++++---------- crates/uv-resolver/src/lock/mod.rs | 8 +++- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/crates/uv-distribution-types/src/file.rs b/crates/uv-distribution-types/src/file.rs index 2d30eb0f2..948378c0c 100644 --- a/crates/uv-distribution-types/src/file.rs +++ b/crates/uv-distribution-types/src/file.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::fmt::{self, Display, Formatter}; use std::str::FromStr; @@ -160,27 +161,22 @@ impl UrlString { .unwrap_or(self.as_ref()) } - /// Return the [`UrlString`] with any fragments removed. + /// Return the [`UrlString`] (as a [`Cow`]) with any fragments removed. #[must_use] - pub fn without_fragment(&self) -> Self { - Self( - self.as_ref() - .split_once('#') - .map(|(path, _)| path) - .map(SmallString::from) - .unwrap_or_else(|| self.0.clone()), - ) + pub fn without_fragment(&self) -> Cow<'_, Self> { + self.as_ref() + .split_once('#') + .map(|(path, _)| Cow::Owned(UrlString(SmallString::from(path)))) + .unwrap_or(Cow::Borrowed(self)) } - /// Return the [`UrlString`] with trailing slash removed. + /// Return the [`UrlString`] (as a [`Cow`]) with trailing slash removed. #[must_use] - pub fn without_trailing_slash(&self) -> Self { - Self( - self.as_ref() - .strip_suffix('/') - .map(SmallString::from) - .unwrap_or_else(|| self.0.clone()), - ) + pub fn without_trailing_slash(&self) -> Cow<'_, Self> { + self.as_ref() + .strip_suffix('/') + .map(|path| Cow::Owned(UrlString(SmallString::from(path)))) + .unwrap_or(Cow::Borrowed(self)) } } @@ -263,16 +259,29 @@ mod tests { #[test] fn without_fragment() { + // Borrows a URL without a fragment + let url = UrlString("https://example.com/path".into()); + assert_eq!(url.without_fragment(), Cow::Borrowed(&url)); + + // Removes the fragment if present on the URL let url = UrlString("https://example.com/path?query#fragment".into()); assert_eq!( url.without_fragment(), - UrlString("https://example.com/path?query".into()) + Cow::Owned(UrlString("https://example.com/path?query".into())) ); + } - let url = UrlString("https://example.com/path#fragment".into()); - assert_eq!(url.base_str(), "https://example.com/path"); - + #[test] + fn without_trailing_slash() { + // Borrows a URL without a slash let url = UrlString("https://example.com/path".into()); - assert_eq!(url.base_str(), "https://example.com/path"); + assert_eq!(url.without_trailing_slash(), Cow::Borrowed(&url)); + + // Removes the trailing slash if present on the URL + let url = UrlString("https://example.com/path/".into()); + assert_eq!( + url.without_trailing_slash(), + Cow::Owned(UrlString("https://example.com/path".into())) + ); } } diff --git a/crates/uv-resolver/src/lock/mod.rs b/crates/uv-resolver/src/lock/mod.rs index 8ff3097de..beeadc912 100644 --- a/crates/uv-resolver/src/lock/mod.rs +++ b/crates/uv-resolver/src/lock/mod.rs @@ -1490,7 +1490,11 @@ impl Lock { .version .as_ref() .expect("version for registry source"); - return Ok(SatisfiesResult::MissingRemoteIndex(name, version, url)); + return Ok(SatisfiesResult::MissingRemoteIndex( + name, + version, + url.into_owned(), + )); } } RegistrySource::Path(path) => { @@ -4692,7 +4696,7 @@ impl From for Hashes { /// Convert a [`FileLocation`] into a normalized [`UrlString`]. fn normalize_file_location(location: &FileLocation) -> Result { match location { - FileLocation::AbsoluteUrl(absolute) => Ok(absolute.without_fragment()), + FileLocation::AbsoluteUrl(absolute) => Ok(absolute.without_fragment().into_owned()), FileLocation::RelativeUrl(_, _) => Ok(normalize_url(location.to_url()?)), } } From 4eef79e5e83a0b980a0cd97781fbf4930dded582 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Fri, 27 Jun 2025 14:06:19 -0400 Subject: [PATCH 080/349] Avoid rendering desugared prefix matches in error messages (#14195) ## Summary When the user provides a requirement like `==2.4.*`, we desugar that to `>=2.4.dev0,<2.5.dev0`. These bounds then appear in error messages, and worse, they also trick the error message reporter into thinking that the user asked for a pre-release. This PR adds logic to convert to the more-concise `==2.4.*` representation when possible. We could probably do a similar thing for the compatible release operator (`~=`). Closes https://github.com/astral-sh/uv/issues/14177. Co-authored-by: Zanie Blue --- crates/uv-resolver/src/error.rs | 63 ++++++++++++++++++++++++ crates/uv-resolver/src/pubgrub/report.rs | 29 +++++++++-- crates/uv/tests/it/lock.rs | 28 +++++++++++ 3 files changed, 115 insertions(+), 5 deletions(-) diff --git a/crates/uv-resolver/src/error.rs b/crates/uv-resolver/src/error.rs index adbdc3cc7..2033ed0c0 100644 --- a/crates/uv-resolver/src/error.rs +++ b/crates/uv-resolver/src/error.rs @@ -1213,6 +1213,69 @@ impl SentinelRange<'_> { } } +/// A prefix match, e.g., `==2.4.*`, which is desugared to a range like `>=2.4.dev0,<2.5.dev0`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct PrefixMatch<'a> { + version: &'a Version, +} + +impl<'a> PrefixMatch<'a> { + /// Determine whether a given range is equivalent to a prefix match (e.g., `==2.4.*`). + /// + /// Prefix matches are desugared to (e.g.) `>=2.4.dev0,<2.5.dev0`, but we want to render them + /// as `==2.4.*` in error messages. + pub(crate) fn from_range(lower: &'a Bound, upper: &'a Bound) -> Option { + let Bound::Included(lower) = lower else { + return None; + }; + let Bound::Excluded(upper) = upper else { + return None; + }; + if lower.is_pre() || lower.is_post() || lower.is_local() { + return None; + } + if upper.is_pre() || upper.is_post() || upper.is_local() { + return None; + } + if lower.dev() != Some(0) { + return None; + } + if upper.dev() != Some(0) { + return None; + } + if lower.release().len() != upper.release().len() { + return None; + } + + // All segments should be the same, except the last one, which should be incremented. + let num_segments = lower.release().len(); + for (i, (lower, upper)) in lower + .release() + .iter() + .zip(upper.release().iter()) + .enumerate() + { + if i == num_segments - 1 { + if lower + 1 != *upper { + return None; + } + } else { + if lower != upper { + return None; + } + } + } + + Some(PrefixMatch { version: lower }) + } +} + +impl std::fmt::Display for PrefixMatch<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "=={}.*", self.version.only_release()) + } +} + #[derive(Debug)] pub struct NoSolutionHeader { /// The [`ResolverEnvironment`] that caused the failure. diff --git a/crates/uv-resolver/src/pubgrub/report.rs b/crates/uv-resolver/src/pubgrub/report.rs index 91f8d4baa..5c62f0b1f 100644 --- a/crates/uv-resolver/src/pubgrub/report.rs +++ b/crates/uv-resolver/src/pubgrub/report.rs @@ -18,7 +18,7 @@ use uv_pep440::{Version, VersionSpecifiers}; use uv_platform_tags::{AbiTag, IncompatibleTag, LanguageTag, PlatformTag, Tags}; use crate::candidate_selector::CandidateSelector; -use crate::error::ErrorTree; +use crate::error::{ErrorTree, PrefixMatch}; use crate::fork_indexes::ForkIndexes; use crate::fork_urls::ForkUrls; use crate::prerelease::AllowPrerelease; @@ -944,17 +944,30 @@ impl PubGrubReportFormatter<'_> { hints: &mut IndexSet, ) { let any_prerelease = set.iter().any(|(start, end)| { + // Ignore, e.g., `>=2.4.dev0,<2.5.dev0`, which is the desugared form of `==2.4.*`. + if PrefixMatch::from_range(start, end).is_some() { + return false; + } + let is_pre1 = match start { Bound::Included(version) => version.any_prerelease(), Bound::Excluded(version) => version.any_prerelease(), Bound::Unbounded => false, }; + if is_pre1 { + return true; + } + let is_pre2 = match end { Bound::Included(version) => version.any_prerelease(), Bound::Excluded(version) => version.any_prerelease(), Bound::Unbounded => false, }; - is_pre1 || is_pre2 + if is_pre2 { + return true; + } + + false }); if any_prerelease { @@ -1928,11 +1941,11 @@ impl std::fmt::Display for PackageRange<'_> { PackageRangeKind::Available => write!(f, "are available:")?, } } - for segment in &segments { + for (lower, upper) in &segments { if segments.len() > 1 { write!(f, "\n ")?; } - match segment { + match (lower, upper) { (Bound::Unbounded, Bound::Unbounded) => match self.kind { PackageRangeKind::Dependency => write!(f, "{package}")?, PackageRangeKind::Compatibility => write!(f, "all versions of {package}")?, @@ -1948,7 +1961,13 @@ impl std::fmt::Display for PackageRange<'_> { write!(f, "{package}>={v},<={b}")?; } } - (Bound::Included(v), Bound::Excluded(b)) => write!(f, "{package}>={v},<{b}")?, + (Bound::Included(v), Bound::Excluded(b)) => { + if let Some(prefix) = PrefixMatch::from_range(lower, upper) { + write!(f, "{package}{prefix}")?; + } else { + write!(f, "{package}>={v},<{b}")?; + } + } (Bound::Excluded(v), Bound::Unbounded) => write!(f, "{package}>{v}")?, (Bound::Excluded(v), Bound::Included(b)) => write!(f, "{package}>{v},<={b}")?, (Bound::Excluded(v), Bound::Excluded(b)) => write!(f, "{package}>{v},<{b}")?, diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index e0fc8749d..b1d6c5327 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -28452,3 +28452,31 @@ fn lock_with_index_trailing_slashes_in_lockfile() -> Result<()> { Ok(()) } + +#[test] +fn lock_prefix_match() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["anyio==5.4.*"] + "#, + )?; + + uv_snapshot!(context.filters(), context.lock(), @r" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + × No solution found when resolving dependencies: + ╰─▶ Because only anyio<=4.3.0 is available and your project depends on anyio==5.4.*, we can conclude that your project's requirements are unsatisfiable. + "); + + Ok(()) +} From 6a5d2f1ec4b8a43a38fb29cf31d57fd9f2bc362f Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Fri, 27 Jun 2025 14:48:40 -0400 Subject: [PATCH 081/349] Share workspace cache between lock and sync operations (#14321) ## Summary Closes #14316. --- crates/uv/src/commands/project/add.rs | 3 +++ crates/uv/src/commands/project/export.rs | 1 + crates/uv/src/commands/project/lock.rs | 11 ++++++++--- crates/uv/src/commands/project/remove.rs | 2 ++ crates/uv/src/commands/project/run.rs | 7 +++++-- crates/uv/src/commands/project/sync.rs | 7 +++++-- crates/uv/src/commands/project/tree.rs | 1 + crates/uv/src/commands/project/version.rs | 4 ++++ 8 files changed, 29 insertions(+), 7 deletions(-) diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index ae20b31d4..bd3b49a3f 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -853,6 +853,7 @@ async fn lock_and_sync( Box::new(DefaultResolveLogger), concurrency, cache, + &WorkspaceCache::default(), printer, preview, ) @@ -974,6 +975,7 @@ async fn lock_and_sync( Box::new(SummaryResolveLogger), concurrency, cache, + &WorkspaceCache::default(), printer, preview, ) @@ -1021,6 +1023,7 @@ async fn lock_and_sync( installer_metadata, concurrency, cache, + WorkspaceCache::default(), DryRun::Disabled, printer, preview, diff --git a/crates/uv/src/commands/project/export.rs b/crates/uv/src/commands/project/export.rs index 88a847d04..c14bfd904 100644 --- a/crates/uv/src/commands/project/export.rs +++ b/crates/uv/src/commands/project/export.rs @@ -193,6 +193,7 @@ pub(crate) async fn export( Box::new(DefaultResolveLogger), concurrency, cache, + &workspace_cache, printer, preview, ) diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index 97ee01767..2bcb68eb3 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -199,6 +199,7 @@ pub(crate) async fn lock( Box::new(DefaultResolveLogger), concurrency, cache, + &workspace_cache, printer, preview, ) @@ -263,6 +264,7 @@ pub(super) struct LockOperation<'env> { logger: Box, concurrency: Concurrency, cache: &'env Cache, + workspace_cache: &'env WorkspaceCache, printer: Printer, preview: PreviewMode, } @@ -277,6 +279,7 @@ impl<'env> LockOperation<'env> { logger: Box, concurrency: Concurrency, cache: &'env Cache, + workspace_cache: &'env WorkspaceCache, printer: Printer, preview: PreviewMode, ) -> Self { @@ -289,6 +292,7 @@ impl<'env> LockOperation<'env> { logger, concurrency, cache, + workspace_cache, printer, preview, } @@ -334,6 +338,7 @@ impl<'env> LockOperation<'env> { self.logger, self.concurrency, self.cache, + self.workspace_cache, self.printer, self.preview, ) @@ -372,6 +377,7 @@ impl<'env> LockOperation<'env> { self.logger, self.concurrency, self.cache, + self.workspace_cache, self.printer, self.preview, ) @@ -402,6 +408,7 @@ async fn do_lock( logger: Box, concurrency: Concurrency, cache: &Cache, + workspace_cache: &WorkspaceCache, printer: Printer, preview: PreviewMode, ) -> Result { @@ -654,8 +661,6 @@ async fn do_lock( FlatIndex::from_entries(entries, None, &hasher, build_options) }; - let workspace_cache = WorkspaceCache::default(); - // Create a build dispatch. let build_dispatch = BuildDispatch::new( &client, @@ -674,7 +679,7 @@ async fn do_lock( &build_hasher, *exclude_newer, *sources, - workspace_cache, + workspace_cache.clone(), concurrency, preview, ); diff --git a/crates/uv/src/commands/project/remove.rs b/crates/uv/src/commands/project/remove.rs index 7fd02277e..29b5f0bc0 100644 --- a/crates/uv/src/commands/project/remove.rs +++ b/crates/uv/src/commands/project/remove.rs @@ -302,6 +302,7 @@ pub(crate) async fn remove( Box::new(DefaultResolveLogger), concurrency, cache, + &WorkspaceCache::default(), printer, preview, ) @@ -357,6 +358,7 @@ pub(crate) async fn remove( installer_metadata, concurrency, cache, + WorkspaceCache::default(), DryRun::Disabled, printer, preview, diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index ee3caeafa..6ece28eaf 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -264,6 +264,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl }, concurrency, cache, + &workspace_cache, printer, preview, ) @@ -309,6 +310,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl installer_metadata, concurrency, cache, + workspace_cache.clone(), DryRun::Disabled, printer, preview, @@ -407,7 +409,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl installer_metadata, concurrency, cache, - workspace_cache, + workspace_cache.clone(), DryRun::Disabled, printer, preview, @@ -465,7 +467,6 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl }; // Discover and sync the base environment. - let workspace_cache = WorkspaceCache::default(); let temp_dir; let base_interpreter = if let Some(script_interpreter) = script_interpreter { // If we found a PEP 723 script and the user provided a project-only setting, warn. @@ -721,6 +722,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl }, concurrency, cache, + &workspace_cache, printer, preview, ) @@ -807,6 +809,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl installer_metadata, concurrency, cache, + workspace_cache.clone(), DryRun::Disabled, printer, preview, diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index f1a73b8c8..19848ee02 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -324,7 +324,7 @@ pub(crate) async fn sync( installer_metadata, concurrency, cache, - workspace_cache, + workspace_cache.clone(), dry_run, printer, preview, @@ -371,6 +371,7 @@ pub(crate) async fn sync( Box::new(DefaultResolveLogger), concurrency, cache, + &workspace_cache, printer, preview, ) @@ -452,6 +453,7 @@ pub(crate) async fn sync( installer_metadata, concurrency, cache, + workspace_cache, dry_run, printer, preview, @@ -587,6 +589,7 @@ pub(super) async fn do_sync( installer_metadata: bool, concurrency: Concurrency, cache: &Cache, + workspace_cache: WorkspaceCache, dry_run: DryRun, printer: Printer, preview: PreviewMode, @@ -748,7 +751,7 @@ pub(super) async fn do_sync( &build_hasher, exclude_newer, sources, - WorkspaceCache::default(), + workspace_cache.clone(), concurrency, preview, ); diff --git a/crates/uv/src/commands/project/tree.rs b/crates/uv/src/commands/project/tree.rs index 2ff6ad98e..d401940d9 100644 --- a/crates/uv/src/commands/project/tree.rs +++ b/crates/uv/src/commands/project/tree.rs @@ -146,6 +146,7 @@ pub(crate) async fn tree( Box::new(DefaultResolveLogger), concurrency, cache, + &WorkspaceCache::default(), printer, preview, ) diff --git a/crates/uv/src/commands/project/version.rs b/crates/uv/src/commands/project/version.rs index fdba41978..102d91af6 100644 --- a/crates/uv/src/commands/project/version.rs +++ b/crates/uv/src/commands/project/version.rs @@ -315,6 +315,7 @@ async fn print_frozen_version( Box::new(DefaultResolveLogger), concurrency, cache, + &WorkspaceCache::default(), printer, preview, ) @@ -443,6 +444,7 @@ async fn lock_and_sync( // Initialize any shared state. let state = UniversalState::default(); + let workspace_cache = WorkspaceCache::default(); // Lock and sync the environment, if necessary. let lock = match project::lock::LockOperation::new( @@ -453,6 +455,7 @@ async fn lock_and_sync( Box::new(DefaultResolveLogger), concurrency, cache, + &workspace_cache, printer, preview, ) @@ -510,6 +513,7 @@ async fn lock_and_sync( installer_metadata, concurrency, cache, + workspace_cache, DryRun::Disabled, printer, preview, From eab938b7b4a9ce49e1eb5ffdcfec799e5538ccd8 Mon Sep 17 00:00:00 2001 From: Aaron Ang <67321817+aaron-ang@users.noreply.github.com> Date: Fri, 27 Jun 2025 13:48:41 -0700 Subject: [PATCH 082/349] Warn users on `~=` python version specifier (#14008) Close #7426 ## Summary Picking up on #8284, I noticed that the `requires_python` object already has its specifiers canonicalized in the `intersection` method, meaning `~=3.12` is converted to `>=3.12, <4`. To fix this, we check and warn in `intersection`. ## Test Plan Used the same tests from #8284. --- .../src/requires_python.rs | 32 +++++++--- crates/uv-pep440/src/version_ranges.rs | 9 ++- crates/uv-pep440/src/version_specifier.rs | 7 +- crates/uv/tests/it/lock.rs | 64 +++++++++++++++++++ 4 files changed, 97 insertions(+), 15 deletions(-) diff --git a/crates/uv-distribution-types/src/requires_python.rs b/crates/uv-distribution-types/src/requires_python.rs index ae9fee7fe..786aed83a 100644 --- a/crates/uv-distribution-types/src/requires_python.rs +++ b/crates/uv-distribution-types/src/requires_python.rs @@ -5,10 +5,11 @@ use version_ranges::Ranges; use uv_distribution_filename::WheelFilename; use uv_pep440::{ LowerBound, UpperBound, Version, VersionSpecifier, VersionSpecifiers, - release_specifiers_to_ranges, + release_specifier_to_range, release_specifiers_to_ranges, }; use uv_pep508::{MarkerExpression, MarkerTree, MarkerValueVersion}; use uv_platform_tags::{AbiTag, LanguageTag}; +use uv_warnings::warn_user_once; /// The `Requires-Python` requirement specifier. /// @@ -66,15 +67,28 @@ impl RequiresPython { ) -> Option { // Convert to PubGrub range and perform an intersection. let range = specifiers - .into_iter() - .map(|specifier| release_specifiers_to_ranges(specifier.clone())) - .fold(None, |range: Option>, requires_python| { - if let Some(range) = range { - Some(range.intersection(&requires_python)) - } else { - Some(requires_python) + .map(|specs| { + // Warn if there’s exactly one `~=` specifier without a patch. + if let [spec] = &specs[..] { + if spec.is_tilde_without_patch() { + if let Some((lo_b, hi_b)) = release_specifier_to_range(spec.clone()) + .bounding_range() + .map(|(l, u)| (l.cloned(), u.cloned())) + { + let lo_spec = LowerBound::new(lo_b).specifier().unwrap(); + let hi_spec = UpperBound::new(hi_b).specifier().unwrap(); + warn_user_once!( + "The release specifier (`{spec}`) contains a compatible release \ + match without a patch version. This will be interpreted as \ + `{lo_spec}, {hi_spec}`. Did you mean `{spec}.0` to freeze the minor \ + version?" + ); + } + } } - })?; + release_specifiers_to_ranges(specs.clone()) + }) + .reduce(|acc, r| acc.intersection(&r))?; // If the intersection is empty, return `None`. if range.is_empty() { diff --git a/crates/uv-pep440/src/version_ranges.rs b/crates/uv-pep440/src/version_ranges.rs index 2bd7dcd4d..26cd048d3 100644 --- a/crates/uv-pep440/src/version_ranges.rs +++ b/crates/uv-pep440/src/version_ranges.rs @@ -130,11 +130,10 @@ impl From for Ranges { /// /// See: pub fn release_specifiers_to_ranges(specifiers: VersionSpecifiers) -> Ranges { - let mut range = Ranges::full(); - for specifier in specifiers { - range = range.intersection(&release_specifier_to_range(specifier)); - } - range + specifiers + .into_iter() + .map(release_specifier_to_range) + .fold(Ranges::full(), |acc, range| acc.intersection(&range)) } /// Convert the [`VersionSpecifier`] to a PubGrub-compatible version range, using release-only diff --git a/crates/uv-pep440/src/version_specifier.rs b/crates/uv-pep440/src/version_specifier.rs index 4255c13fa..19acff2eb 100644 --- a/crates/uv-pep440/src/version_specifier.rs +++ b/crates/uv-pep440/src/version_specifier.rs @@ -416,7 +416,7 @@ impl VersionSpecifier { &self.operator } - /// Get the version, e.g. `<=` in `<= 2.0.0` + /// Get the version, e.g. `2.0.0` in `<= 2.0.0` pub fn version(&self) -> &Version { &self.version } @@ -615,6 +615,11 @@ impl VersionSpecifier { | Operator::NotEqual => false, } } + + /// Returns true if this is a `~=` specifier without a patch version (e.g. `~=3.11`). + pub fn is_tilde_without_patch(&self) -> bool { + self.operator == Operator::TildeEqual && self.version.release().len() == 2 + } } impl FromStr for VersionSpecifier { diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index b1d6c5327..5fb0fcd27 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -4535,6 +4535,70 @@ fn lock_requires_python_exact() -> Result<()> { Ok(()) } +/// Lock a requirement from PyPI with a compatible release Python bound. +#[cfg(feature = "python-patch")] +#[test] +fn lock_requires_python_compatible_specifier() -> Result<()> { + let context = TestContext::new("3.13.0"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "warehouse" + version = "1.0.0" + requires-python = "~=3.13" + "#, + )?; + + uv_snapshot!(context.filters(), context.lock(), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: The release specifier (`~=3.13`) contains a compatible release match without a patch version. This will be interpreted as `>=3.13, <4`. Did you mean `~=3.13.0` to freeze the minor version? + Resolved 1 package in [TIME] + "###); + + pyproject_toml.write_str( + r#" + [project] + name = "warehouse" + version = "1.0.0" + requires-python = "~=3.13, <3.14" + "#, + )?; + + uv_snapshot!(context.filters(), context.lock(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + "); + + pyproject_toml.write_str( + r#" + [project] + name = "warehouse" + version = "1.0.0" + requires-python = "~=3.13.0" + "#, + )?; + + uv_snapshot!(context.filters(), context.lock(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + "); + Ok(()) +} + /// Fork, even with a single dependency, if the minimum Python version is increased. #[test] fn lock_requires_python_fork() -> Result<()> { From b6b7409d13e53b7f0752dc053bce9bd3b233269b Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Fri, 27 Jun 2025 16:46:36 -0500 Subject: [PATCH 083/349] Bump version to 0.7.16 (#14334) --- CHANGELOG.md | 82 +++++++++++++++++++-------- Cargo.lock | 6 +- crates/uv-build/Cargo.toml | 2 +- crates/uv-build/pyproject.toml | 2 +- crates/uv-version/Cargo.toml | 2 +- crates/uv/Cargo.toml | 2 +- docs/concepts/build-backend.md | 2 +- 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/pre-commit.md | 10 ++-- pyproject.toml | 2 +- 13 files changed, 83 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ef35e9a3..f2d261a73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,42 @@ +## 0.7.16 + +### Python + +- Add Python 3.14.0b3 + +See the +[`python-build-standalone` release notes](https://github.com/astral-sh/python-build-standalone/releases/tag/20250626) +for more details. + +### Enhancements + +- Include path or URL when failing to convert in lockfile ([#14292](https://github.com/astral-sh/uv/pull/14292)) +- Warn when `~=` is used as a Python version specifier without a patch version ([#14008](https://github.com/astral-sh/uv/pull/14008)) + +### Preview features + +- Ensure preview default Python installs are upgradeable ([#14261](https://github.com/astral-sh/uv/pull/14261)) + +### Performance + +- Share workspace cache between lock and sync operations ([#14321](https://github.com/astral-sh/uv/pull/14321)) + +### Bug fixes + +- Allow local indexes to reference remote files ([#14294](https://github.com/astral-sh/uv/pull/14294)) +- Avoid rendering desugared prefix matches in error messages ([#14195](https://github.com/astral-sh/uv/pull/14195)) +- Avoid using path URL for workspace Git dependencies in `requirements.txt` ([#14288](https://github.com/astral-sh/uv/pull/14288)) +- Normalize index URLs to remove trailing slash ([#14245](https://github.com/astral-sh/uv/pull/14245)) +- Respect URL-encoded credentials in redirect location ([#14315](https://github.com/astral-sh/uv/pull/14315)) +- Lock the source tree when running setuptools, to protect concurrent builds ([#14174](https://github.com/astral-sh/uv/pull/14174)) + +### Documentation + +- Note that GCP Artifact Registry download URLs must have `/simple` component ([#14251](https://github.com/astral-sh/uv/pull/14251)) + ## 0.7.15 ### Enhancements @@ -408,11 +444,11 @@ This release contains various changes that improve correctness and user experien ### Breaking changes - **Update `uv version` to display and update project versions ([#12349](https://github.com/astral-sh/uv/pull/12349))** - + Previously, `uv version` displayed uv's version. Now, `uv version` will display or update the project's version. This interface was [heavily requested](https://github.com/astral-sh/uv/issues/6298) and, after much consideration, we decided that transitioning the top-level command was the best option. - + Here's a brief example: - + ```console $ uv init example Initialized project `example` at `./example` @@ -424,72 +460,72 @@ This release contains various changes that improve correctness and user experien $ uv version --short 1.0.0 ``` - + If used outside of a project, uv will fallback to showing its own version still: - + ```console $ uv version warning: failed to read project: No `pyproject.toml` found in current directory or any parent directory running `uv self version` for compatibility with old `uv version` command. this fallback will be removed soon, pass `--preview` to make this an error. - + uv 0.7.0 (4433f41c9 2025-04-29) ``` - + As described in the warning, `--preview` can be used to error instead: - + ```console $ uv version --preview error: No `pyproject.toml` found in current directory or any parent directory ``` - + The previous functionality of `uv version` was moved to `uv self version`. - **Avoid fallback to subsequent indexes on authentication failure ([#12805](https://github.com/astral-sh/uv/pull/12805))** - + When using the `first-index` strategy (the default), uv will stop searching indexes for a package once it is found on a single index. Previously, uv considered a package as "missing" from an index during authentication failures, such as an HTTP 401 or HTTP 403 (normally, missing packages are represented by an HTTP 404). This behavior was motivated by unusual responses from some package indexes, but reduces the safety of uv's index strategy when authentication fails. Now, uv will consider an authentication failure as a stop-point when searching for a package across indexes. The `index.ignore-error-codes` option can be used to recover the existing behavior, e.g.: - + ```toml [[tool.uv.index]] name = "pytorch" url = "https://download.pytorch.org/whl/cpu" ignore-error-codes = [401, 403] ``` - + Since PyTorch's indexes always return a HTTP 403 for missing packages, uv special-cases indexes on the `pytorch.org` domain to ignore that error code by default. - **Require the command in `uvx ` to be available in the Python environment ([#11603](https://github.com/astral-sh/uv/pull/11603))** - + Previously, `uvx` would attempt to execute a command even if it was not provided by a Python package. For example, if we presume `foo` is an empty Python package which provides no command, `uvx foo` would invoke the `foo` command on the `PATH` (if present). Now, uv will error early if the `foo` executable is not provided by the requested Python package. This check is not enforced when `--from` is used, so patterns like `uvx --from foo bash -c "..."` are still valid. uv also still allows `uvx foo` where the `foo` executable is provided by a dependency of `foo` instead of `foo` itself, as this is fairly common for packages which depend on a dedicated package for their command-line interface. - **Use index URL instead of package URL for keyring credential lookups ([#12651](https://github.com/astral-sh/uv/pull/12651))** - + When determining credentials for querying a package URL, uv previously sent the full URL to the `keyring` command. However, some keyring plugins expect to receive the *index URL* (which is usually a parent of the package URL). Now, uv requests credentials for the index URL instead. This behavior matches `pip`. - **Remove `--version` from subcommands ([#13108](https://github.com/astral-sh/uv/pull/13108))** - + Previously, uv allowed the `--version` flag on arbitrary subcommands, e.g., `uv run --version`. However, the `--version` flag is useful for other operations since uv is a package manager. Consequently, we've removed the `--version` flag from subcommands — it is only available as `uv --version`. - **Omit Python 3.7 downloads from managed versions ([#13022](https://github.com/astral-sh/uv/pull/13022))** - + Python 3.7 is EOL and not formally supported by uv; however, Python 3.7 was previously available for download on a subset of platforms. - **Reject non-PEP 751 TOML files in install, compile, and export commands ([#13120](https://github.com/astral-sh/uv/pull/13120), [#13119](https://github.com/astral-sh/uv/pull/13119))** - + Previously, uv treated arbitrary `.toml` files passed to commands (e.g., `uv pip install -r foo.toml` or `uv pip compile -o foo.toml`) as `requirements.txt`-formatted files. Now, uv will error instead. If using PEP 751 lockfiles, use the standardized format for custom names instead, e.g., `pylock.foo.toml`. - **Ignore arbitrary Python requests in version files ([#12909](https://github.com/astral-sh/uv/pull/12909))** - + uv allows arbitrary strings to be used for Python version requests, in which they are treated as an executable name to search for in the `PATH`. However, using this form of request in `.python-version` files is non-standard and conflicts with `pyenv-virtualenv` which writes environment names to `.python-version` files. In this release, uv will now ignore requests that are arbitrary strings when found in `.python-version` files. - **Error on unknown dependency object specifiers ([12811](https://github.com/astral-sh/uv/pull/12811))** - + The `[dependency-groups]` entries can include "object specifiers", e.g. `set-phasers-to = ...` in: - + ```toml [dependency-groups] foo = ["pyparsing"] bar = [{set-phasers-to = "stun"}] ``` - + However, the only current spec-compliant object specifier is `include-group`. Previously, uv would ignore unknown object specifiers. Now, uv will error. - **Make `--frozen` and `--no-sources` conflicting options ([#12671](https://github.com/astral-sh/uv/pull/12671))** - + Using `--no-sources` always requires a new resolution and `--frozen` will always fail when used with it. Now, this conflict is encoded in the CLI options for clarity. - **Treat empty `UV_PYTHON_INSTALL_DIR` and `UV_TOOL_DIR` as unset ([#12907](https://github.com/astral-sh/uv/pull/12907), [#12905](https://github.com/astral-sh/uv/pull/12905))** - + Previously, these variables were treated as set to the current working directory when set to an empty string. Now, uv will ignore these variables when empty. This matches uv's behavior for other environment variables which configure directories. ### Enhancements diff --git a/Cargo.lock b/Cargo.lock index d25d7d252..ef9f5b97a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4570,7 +4570,7 @@ dependencies = [ [[package]] name = "uv" -version = "0.7.15" +version = "0.7.16" dependencies = [ "anstream", "anyhow", @@ -4734,7 +4734,7 @@ dependencies = [ [[package]] name = "uv-build" -version = "0.7.15" +version = "0.7.16" dependencies = [ "anyhow", "uv-build-backend", @@ -5923,7 +5923,7 @@ dependencies = [ [[package]] name = "uv-version" -version = "0.7.15" +version = "0.7.16" [[package]] name = "uv-virtualenv" diff --git a/crates/uv-build/Cargo.toml b/crates/uv-build/Cargo.toml index 94b4adbc3..6c3ab09d9 100644 --- a/crates/uv-build/Cargo.toml +++ b/crates/uv-build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-build" -version = "0.7.15" +version = "0.7.16" edition.workspace = true rust-version.workspace = true homepage.workspace = true diff --git a/crates/uv-build/pyproject.toml b/crates/uv-build/pyproject.toml index 4b8c522d6..5687497ae 100644 --- a/crates/uv-build/pyproject.toml +++ b/crates/uv-build/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uv-build" -version = "0.7.15" +version = "0.7.16" description = "The uv build backend" authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }] requires-python = ">=3.8" diff --git a/crates/uv-version/Cargo.toml b/crates/uv-version/Cargo.toml index 9eef680a2..5d3be8a9d 100644 --- a/crates/uv-version/Cargo.toml +++ b/crates/uv-version/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-version" -version = "0.7.15" +version = "0.7.16" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml index 9b73a9ebd..5f93c3ef6 100644 --- a/crates/uv/Cargo.toml +++ b/crates/uv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv" -version = "0.7.15" +version = "0.7.16" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } diff --git a/docs/concepts/build-backend.md b/docs/concepts/build-backend.md index 2e93cc546..260680d4c 100644 --- a/docs/concepts/build-backend.md +++ b/docs/concepts/build-backend.md @@ -19,7 +19,7 @@ existing project, add it to the `[build-system]` section in your `pyproject.toml ```toml [build-system] -requires = ["uv_build>=0.7.15,<0.8.0"] +requires = ["uv_build>=0.7.16,<0.8.0"] build-backend = "uv_build" ``` diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 33281a0c8..d2235fc71 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.7.15/install.sh | sh + $ curl -LsSf https://astral.sh/uv/0.7.16/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.7.15/install.ps1 | iex" + PS> powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/0.7.16/install.ps1 | iex" ``` !!! tip diff --git a/docs/guides/integration/aws-lambda.md b/docs/guides/integration/aws-lambda.md index b48dba1b1..25be2ae38 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.7.15 AS uv +FROM ghcr.io/astral-sh/uv:0.7.16 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.7.15 AS uv +FROM ghcr.io/astral-sh/uv:0.7.16 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 d8e6d69a3..72d730362 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.7.15` +- `ghcr.io/astral-sh/uv:{major}.{minor}.{patch}`, e.g., `ghcr.io/astral-sh/uv:0.7.16` - `ghcr.io/astral-sh/uv:{major}.{minor}`, e.g., `ghcr.io/astral-sh/uv:0.7` (the latest patch version) @@ -75,7 +75,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.7.15-alpine`. +`ghcr.io/astral-sh/uv:{major}.{minor}-{base}`, e.g., `ghcr.io/astral-sh/uv:0.7.16-alpine`. For more details, see the [GitHub Container](https://github.com/astral-sh/uv/pkgs/container/uv) page. @@ -113,7 +113,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.7.15 /uv /uvx /bin/ +COPY --from=ghcr.io/astral-sh/uv:0.7.16 /uv /uvx /bin/ ``` !!! tip @@ -131,7 +131,7 @@ COPY --from=ghcr.io/astral-sh/uv:0.7.15 /uv /uvx /bin/ Or, with the installer: ```dockerfile -ADD https://astral.sh/uv/0.7.15/install.sh /uv-installer.sh +ADD https://astral.sh/uv/0.7.16/install.sh /uv-installer.sh ``` ### Installing a project @@ -557,5 +557,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.7.15`, or (even better) the specific image digest, + version tag, e.g., `ghcr.io/astral-sh/uv:0.7.16`, 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 ea96199a2..56fc51e21 100644 --- a/docs/guides/integration/github.md +++ b/docs/guides/integration/github.md @@ -47,7 +47,7 @@ jobs: uses: astral-sh/setup-uv@v5 with: # Install a specific version of uv. - version: "0.7.15" + version: "0.7.16" ``` ## Setting up Python diff --git a/docs/guides/integration/pre-commit.md b/docs/guides/integration/pre-commit.md index 9e78d1595..105d6f053 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.7.15 + rev: 0.7.16 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.7.15 + rev: 0.7.16 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.7.15 + rev: 0.7.16 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.7.15 + rev: 0.7.16 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.7.15 + rev: 0.7.16 hooks: # Compile requirements - id: pip-compile diff --git a/pyproject.toml b/pyproject.toml index 5ede7780c..32a928124 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "uv" -version = "0.7.15" +version = "0.7.16" 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" From 731689e503b31b3f6028d9bf8c416deadbd2c125 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Fri, 27 Jun 2025 21:39:35 -0400 Subject: [PATCH 084/349] Apply build constraints when resolving `--with` dependencies (#14340) ## Summary We were applying these at install time, but not resolve time. --- crates/uv/src/commands/project/environment.rs | 1 + crates/uv/src/commands/project/mod.rs | 2 +- crates/uv/src/commands/tool/install.rs | 2 ++ crates/uv/src/commands/tool/upgrade.rs | 1 + crates/uv/tests/it/run.rs | 5 ++--- crates/uv/tests/it/tool_install.rs | 1 - crates/uv/tests/it/tool_run.rs | 1 - 7 files changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/uv/src/commands/project/environment.rs b/crates/uv/src/commands/project/environment.rs index da3dc7f63..f5a9713d2 100644 --- a/crates/uv/src/commands/project/environment.rs +++ b/crates/uv/src/commands/project/environment.rs @@ -51,6 +51,7 @@ impl CachedEnvironment { resolve_environment( spec, &interpreter, + build_constraints.clone(), &settings.resolver, network_settings, state, diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index a3249b11a..378c50aa2 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -1746,6 +1746,7 @@ impl<'lock> EnvironmentSpecification<'lock> { pub(crate) async fn resolve_environment( spec: EnvironmentSpecification<'_>, interpreter: &Interpreter, + build_constraints: Constraints, settings: &ResolverSettings, network_settings: &NetworkSettings, state: &PlatformState, @@ -1842,7 +1843,6 @@ pub(crate) async fn resolve_environment( let extras = ExtrasSpecification::default(); let groups = BTreeMap::new(); let hasher = HashStrategy::default(); - let build_constraints = Constraints::default(); let build_hasher = HashStrategy::default(); // When resolving from an interpreter, we assume an empty environment, so reinstalls and diff --git a/crates/uv/src/commands/tool/install.rs b/crates/uv/src/commands/tool/install.rs index e816e771e..5ced211b3 100644 --- a/crates/uv/src/commands/tool/install.rs +++ b/crates/uv/src/commands/tool/install.rs @@ -477,6 +477,7 @@ pub(crate) async fn install( let resolution = resolve_environment( spec.clone(), &interpreter, + Constraints::from_requirements(build_constraints.iter().cloned()), &settings.resolver, &network_settings, &state, @@ -530,6 +531,7 @@ pub(crate) async fn install( match resolve_environment( spec, &interpreter, + Constraints::from_requirements(build_constraints.iter().cloned()), &settings.resolver, &network_settings, &state, diff --git a/crates/uv/src/commands/tool/upgrade.rs b/crates/uv/src/commands/tool/upgrade.rs index 166e00349..95b7d1e2d 100644 --- a/crates/uv/src/commands/tool/upgrade.rs +++ b/crates/uv/src/commands/tool/upgrade.rs @@ -298,6 +298,7 @@ async fn upgrade_tool( let resolution = resolve_environment( spec.into(), interpreter, + build_constraints.clone(), &settings.resolver, network_settings, &state, diff --git a/crates/uv/tests/it/run.rs b/crates/uv/tests/it/run.rs index c2c9bc7a4..82f3c2b0b 100644 --- a/crates/uv/tests/it/run.rs +++ b/crates/uv/tests/it/run.rs @@ -1344,7 +1344,7 @@ fn run_with_build_constraints() -> Result<()> { })?; // Installing requests with incompatible build constraints should fail. - uv_snapshot!(context.filters(), context.run().arg("--with").arg("requests==1.2").arg("main.py"), @r###" + uv_snapshot!(context.filters(), context.run().arg("--with").arg("requests==1.2").arg("main.py"), @r" success: false exit_code: 1 ----- stdout ----- @@ -1358,12 +1358,11 @@ fn run_with_build_constraints() -> Result<()> { + idna==3.6 + sniffio==1.3.1 + typing-extensions==4.10.0 - Resolved 1 package in [TIME] × Failed to download and build `requests==1.2.0` ├─▶ Failed to resolve requirements from `setup.py` build ├─▶ No solution found when resolving: `setuptools>=40.8.0` ╰─▶ Because you require setuptools>=40.8.0 and setuptools==1, we can conclude that your requirements are unsatisfiable. - "###); + "); // Change the build constraint to be compatible with `requests==1.2`. pyproject_toml.write_str(indoc! { r#" diff --git a/crates/uv/tests/it/tool_install.rs b/crates/uv/tests/it/tool_install.rs index 88e73406f..0da627552 100644 --- a/crates/uv/tests/it/tool_install.rs +++ b/crates/uv/tests/it/tool_install.rs @@ -420,7 +420,6 @@ fn tool_install_with_incompatible_build_constraints() -> Result<()> { ----- stdout ----- ----- stderr ----- - Resolved [N] packages in [TIME] × Failed to download and build `requests==1.2.0` ├─▶ Failed to resolve requirements from `setup.py` build ├─▶ No solution found when resolving: `setuptools>=40.8.0` diff --git a/crates/uv/tests/it/tool_run.rs b/crates/uv/tests/it/tool_run.rs index a8bcd5a05..d4dcb216c 100644 --- a/crates/uv/tests/it/tool_run.rs +++ b/crates/uv/tests/it/tool_run.rs @@ -2537,7 +2537,6 @@ fn tool_run_with_incompatible_build_constraints() -> Result<()> { ----- stdout ----- ----- stderr ----- - Resolved [N] packages in [TIME] × Failed to download and build `requests==1.2.0` ├─▶ Failed to resolve requirements from `setup.py` build ├─▶ No solution found when resolving: `setuptools>=40.8.0` From db14cc3005d2cd53802cb04c2f1e177a22c934ac Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 28 Jun 2025 03:08:53 +0000 Subject: [PATCH 085/349] Sync latest Python releases (#14339) 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 632d8f6c1..2e67f8e17 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/20250612/cpython-unix/targets.yml"; +const TARGETS_YML_URL: &str = "https://raw.githubusercontent.com/astral-sh/python-build-standalone/refs/tags/20250626/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 32a432b54..1ae689833 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 692667cbb055c441871395eacbcc3b1eafe46dea Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Sat, 28 Jun 2025 09:42:18 -0500 Subject: [PATCH 086/349] Use the canonical `ImplementationName` -> `&str` implementation (#14337) Motivated by some code duplication highlighted in https://github.com/astral-sh/uv/pull/14201, I noticed we weren't taking advantage of the existing implementation for casting to a str here. Unfortunately, we do need a special case for CPython still. --- crates/uv-python/src/implementation.rs | 14 ++++++++++++++ crates/uv-python/src/managed.rs | 6 +----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/crates/uv-python/src/implementation.rs b/crates/uv-python/src/implementation.rs index ffc61dac7..4393d56f4 100644 --- a/crates/uv-python/src/implementation.rs +++ b/crates/uv-python/src/implementation.rs @@ -44,6 +44,13 @@ impl ImplementationName { Self::GraalPy => "GraalPy", } } + + pub fn executable_name(self) -> &'static str { + match self { + Self::CPython => "python", + Self::PyPy | Self::GraalPy => self.into(), + } + } } impl LenientImplementationName { @@ -53,6 +60,13 @@ impl LenientImplementationName { Self::Unknown(name) => name, } } + + pub fn executable_name(&self) -> &str { + match self { + Self::Known(implementation) => implementation.executable_name(), + Self::Unknown(name) => name, + } + } } impl From<&ImplementationName> for &'static str { diff --git a/crates/uv-python/src/managed.rs b/crates/uv-python/src/managed.rs index e7287fe72..ad1dacac6 100644 --- a/crates/uv-python/src/managed.rs +++ b/crates/uv-python/src/managed.rs @@ -362,11 +362,7 @@ impl ManagedPythonInstallation { /// If windowed is true, `pythonw.exe` is selected over `python.exe` on windows, with no changes /// on non-windows. pub fn executable(&self, windowed: bool) -> PathBuf { - let implementation = match self.implementation() { - ImplementationName::CPython => "python", - ImplementationName::PyPy => "pypy", - ImplementationName::GraalPy => "graalpy", - }; + let implementation = self.implementation().executable_name(); let version = match self.implementation() { ImplementationName::CPython => { From 608a1020c662936bce01cd32d43975e154812168 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Sat, 28 Jun 2025 09:42:23 -0500 Subject: [PATCH 087/349] Update the Python query cache comment (#14330) --- crates/uv-python/src/interpreter.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/uv-python/src/interpreter.rs b/crates/uv-python/src/interpreter.rs index b62633283..0cf65da10 100644 --- a/crates/uv-python/src/interpreter.rs +++ b/crates/uv-python/src/interpreter.rs @@ -977,7 +977,8 @@ impl InterpreterInfo { sys_info::os_release().unwrap_or_default(), )), // We use the absolute path for the cache entry to avoid cache collisions for relative - // paths. But we don't to query the executable with symbolic links resolved. + // paths. But we don't want to query the executable with symbolic links resolved because + // that can change reported values, e.g., `sys.executable`. format!("{}.msgpack", cache_digest(&absolute)), ); From 0cfbdcec09deb9170d062f5029a99a5979f25d6a Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Sat, 28 Jun 2025 09:42:27 -0500 Subject: [PATCH 088/349] Ignore `UV_PYTHON_CACHE_DIR` when empty (#14336) To match our semantics elsewhere --- crates/uv-python/src/downloads.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/uv-python/src/downloads.rs b/crates/uv-python/src/downloads.rs index 51f6f1d45..2b0a7e669 100644 --- a/crates/uv-python/src/downloads.rs +++ b/crates/uv-python/src/downloads.rs @@ -772,7 +772,9 @@ impl ManagedPythonDownload { let temp_dir = tempfile::tempdir_in(scratch_dir).map_err(Error::DownloadDirError)?; - if let Some(python_builds_dir) = env::var_os(EnvVars::UV_PYTHON_CACHE_DIR) { + if let Some(python_builds_dir) = + env::var_os(EnvVars::UV_PYTHON_CACHE_DIR).filter(|s| !s.is_empty()) + { let python_builds_dir = PathBuf::from(python_builds_dir); fs_err::create_dir_all(&python_builds_dir)?; let hash_prefix = match self.sha256 { From ec18f4813a31e9c9de6f20ec87ea3038bfe1ae7e Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Sat, 28 Jun 2025 11:32:03 -0500 Subject: [PATCH 089/349] Fix typo (#14341) --- docs/concepts/projects/dependencies.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/concepts/projects/dependencies.md b/docs/concepts/projects/dependencies.md index 42a579695..22d030637 100644 --- a/docs/concepts/projects/dependencies.md +++ b/docs/concepts/projects/dependencies.md @@ -37,7 +37,7 @@ dependencies = ["httpx>=0.27.2"] ``` The [`--dev`](#development-dependencies), [`--group`](#dependency-groups), or -[`--optional`](#optional-dependencies) flags can be used to add a dependencies to an alternative +[`--optional`](#optional-dependencies) flags can be used to add dependencies to an alternative field. The dependency will include a constraint, e.g., `>=0.27.2`, for the most recent, compatible version From f9d3f8ea3bf432cd2cf9c8b613be99b90568a313 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Sun, 29 Jun 2025 08:19:05 -0500 Subject: [PATCH 090/349] Fix error message ordering for `pyvenv.cfg` version conflict (#14329) These were reversed, and we're missing an "a". --- crates/uv/src/commands/project/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index 378c50aa2..c84253eaa 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -769,7 +769,7 @@ pub(crate) enum EnvironmentIncompatibilityError { RequiresPython(EnvironmentKind, RequiresPython), #[error( - "The interpreter in the {0} environment has different version ({1}) than it was created with ({2})" + "The interpreter in the {0} environment has a different version ({1}) than it was created with ({2})" )] PyenvVersionConflict(EnvironmentKind, Version, Version), } @@ -785,8 +785,8 @@ fn environment_is_usable( if let Some((cfg_version, int_version)) = environment.get_pyvenv_version_conflict() { return Err(EnvironmentIncompatibilityError::PyenvVersionConflict( kind, - cfg_version, int_version, + cfg_version, )); } From 734b228edf53984686dbb48db57aff8505695473 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sun, 29 Jun 2025 09:36:13 -0400 Subject: [PATCH 091/349] Drop trailing slashes when converting index URL from URL (#14346) ## Summary In #14245, we started normalizing index URLs by dropping the trailing slash in the lockfile. We added tests to ensure that this didn't cause existing lockfiles to be invalidated, but we missed one of the constructors (specifically, the path that's used with `tool.uv.sources`). --- crates/uv-distribution-types/src/index_url.rs | 28 +-- crates/uv-pep508/src/verbatim_url.rs | 5 + crates/uv/tests/it/lock.rs | 183 +++++++++++++++++- 3 files changed, 201 insertions(+), 15 deletions(-) diff --git a/crates/uv-distribution-types/src/index_url.rs b/crates/uv-distribution-types/src/index_url.rs index 90b5ad809..eb91e852e 100644 --- a/crates/uv-distribution-types/src/index_url.rs +++ b/crates/uv-distribution-types/src/index_url.rs @@ -44,16 +44,9 @@ impl IndexUrl { let url = match split_scheme(path) { Some((scheme, ..)) => { match Scheme::parse(scheme) { - Some(scheme) => { - if scheme.is_file() { - // Ex) `file:///path/to/something/` - VerbatimUrl::parse_url(path)? - } else { - // Ex) `https://pypi.org/simple/` - // Remove a trailing slash if it exists. - let normalized_path = path.strip_suffix('/').unwrap_or(path); - VerbatimUrl::parse_url(normalized_path)? - } + Some(_) => { + // Ex) `https://pypi.org/simple` + VerbatimUrl::parse_url(path)? } None => { // Ex) `C:\Users\user\index` @@ -265,13 +258,20 @@ impl<'de> serde::de::Deserialize<'de> for IndexUrl { } impl From for IndexUrl { - fn from(url: VerbatimUrl) -> Self { + fn from(mut url: VerbatimUrl) -> Self { if url.scheme() == "file" { Self::Path(Arc::new(url)) - } else if *url.raw() == *PYPI_URL { - Self::Pypi(Arc::new(url)) } else { - Self::Url(Arc::new(url)) + // Remove trailing slashes for consistency. They'll be re-added if necessary when + // querying the Simple API. + if let Ok(mut path_segments) = url.raw_mut().path_segments_mut() { + path_segments.pop_if_empty(); + } + if *url.raw() == *PYPI_URL { + Self::Pypi(Arc::new(url)) + } else { + Self::Url(Arc::new(url)) + } } } } diff --git a/crates/uv-pep508/src/verbatim_url.rs b/crates/uv-pep508/src/verbatim_url.rs index 988bebc5e..8b914eb74 100644 --- a/crates/uv-pep508/src/verbatim_url.rs +++ b/crates/uv-pep508/src/verbatim_url.rs @@ -166,6 +166,11 @@ impl VerbatimUrl { &self.url } + /// Return a mutable reference to the underlying [`DisplaySafeUrl`]. + pub fn raw_mut(&mut self) -> &mut DisplaySafeUrl { + &mut self.url + } + /// Convert a [`VerbatimUrl`] into a [`DisplaySafeUrl`]. pub fn to_url(&self) -> DisplaySafeUrl { self.url.clone() diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index 5fb0fcd27..ff5465042 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -28448,6 +28448,9 @@ fn lock_with_index_trailing_slashes_in_lockfile() -> Result<()> { [[tool.uv.index]] name = "pypi-proxy" url = "https://pypi-proxy.fly.dev/simple" + + [tool.uv.sources] + anyio = { index = "pypi-proxy" } "#, )?; @@ -28491,7 +28494,185 @@ fn lock_with_index_trailing_slashes_in_lockfile() -> Result<()> { ] [package.metadata] - requires-dist = [{ name = "anyio" }] + requires-dist = [{ name = "anyio", index = "https://pypi-proxy.fly.dev/simple/" }] + + [[package]] + name = "sniffio" + version = "1.3.1" + source = { registry = "https://pypi-proxy.fly.dev/simple/" } + sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } + wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, + ] + "# + )?; + + // Run `uv lock --locked`. + uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 4 packages in [TIME] + "); + + Ok(()) +} + +/// Run `uv lock --locked` with a lockfile with trailing slashes on index URLs. +#[test] +fn lock_with_index_trailing_slashes_in_pyproject_toml() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["anyio"] + + [[tool.uv.index]] + name = "pypi-proxy" + url = "https://pypi-proxy.fly.dev/simple/" + + [tool.uv.sources] + anyio = { index = "pypi-proxy" } + "#, + )?; + + let lock = context.temp_dir.child("uv.lock"); + lock.write_str(r#" + version = 1 + revision = 2 + requires-python = ">=3.12" + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [[package]] + name = "anyio" + version = "4.3.0" + source = { registry = "https://pypi-proxy.fly.dev/simple" } + dependencies = [ + { name = "idna" }, + { name = "sniffio" }, + ] + sdist = { url = "https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6", size = 159642, upload-time = "2024-02-19T08:36:28.641Z" } + wheels = [ + { url = "https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8", size = 85584, upload-time = "2024-02-19T08:36:26.842Z" }, + ] + + [[package]] + name = "idna" + version = "3.6" + source = { registry = "https://pypi-proxy.fly.dev/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 = "anyio" }, + ] + + [package.metadata] + requires-dist = [{ name = "anyio", index = "https://pypi-proxy.fly.dev/simple" }] + + [[package]] + name = "sniffio" + version = "1.3.1" + source = { registry = "https://pypi-proxy.fly.dev/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } + wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, + ] + "# + )?; + + // Run `uv lock --locked`. + uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 4 packages in [TIME] + "); + + Ok(()) +} + +/// Run `uv lock --locked` with a lockfile with trailing slashes on index URLs. +#[test] +fn lock_with_index_trailing_slashes_in_lockfile_and_pyproject_toml() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["anyio"] + + [[tool.uv.index]] + name = "pypi-proxy" + url = "https://pypi-proxy.fly.dev/simple/" + + [tool.uv.sources] + anyio = { index = "pypi-proxy" } + "#, + )?; + + let lock = context.temp_dir.child("uv.lock"); + lock.write_str(r#" + version = 1 + revision = 2 + requires-python = ">=3.12" + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [[package]] + name = "anyio" + version = "4.3.0" + source = { registry = "https://pypi-proxy.fly.dev/simple/" } + dependencies = [ + { name = "idna" }, + { name = "sniffio" }, + ] + sdist = { url = "https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6", size = 159642, upload-time = "2024-02-19T08:36:28.641Z" } + wheels = [ + { url = "https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8", size = 85584, upload-time = "2024-02-19T08:36:26.842Z" }, + ] + + [[package]] + name = "idna" + version = "3.6" + source = { registry = "https://pypi-proxy.fly.dev/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 = "anyio" }, + ] + + [package.metadata] + requires-dist = [{ name = "anyio", index = "https://pypi-proxy.fly.dev/simple/" }] [[package]] name = "sniffio" From 41c218a89b53a32ee51b3b069ba6407eae984ad0 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sun, 29 Jun 2025 09:58:33 -0400 Subject: [PATCH 092/349] Bump version to 0.7.17 (#14347) --- CHANGELOG.md | 8 ++++++++ Cargo.lock | 6 +++--- crates/uv-build/Cargo.toml | 2 +- crates/uv-build/pyproject.toml | 2 +- crates/uv-version/Cargo.toml | 2 +- crates/uv/Cargo.toml | 2 +- docs/concepts/build-backend.md | 2 +- 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/pre-commit.md | 10 +++++----- pyproject.toml | 2 +- 13 files changed, 32 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2d261a73..fc982ad71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ +## 0.7.17 + +### Bug fixes + +- Apply build constraints when resolving `--with` dependencies ([#14340](https://github.com/astral-sh/uv/pull/14340)) +- Drop trailing slashes when converting index URL from URL ([#14346](https://github.com/astral-sh/uv/pull/14346)) +- Ignore `UV_PYTHON_CACHE_DIR` when empty ([#14336](https://github.com/astral-sh/uv/pull/14336)) +- Fix error message ordering for `pyvenv.cfg` version conflict ([#14329](https://github.com/astral-sh/uv/pull/14329)) ## 0.7.16 diff --git a/Cargo.lock b/Cargo.lock index ef9f5b97a..0c6578998 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4570,7 +4570,7 @@ dependencies = [ [[package]] name = "uv" -version = "0.7.16" +version = "0.7.17" dependencies = [ "anstream", "anyhow", @@ -4734,7 +4734,7 @@ dependencies = [ [[package]] name = "uv-build" -version = "0.7.16" +version = "0.7.17" dependencies = [ "anyhow", "uv-build-backend", @@ -5923,7 +5923,7 @@ dependencies = [ [[package]] name = "uv-version" -version = "0.7.16" +version = "0.7.17" [[package]] name = "uv-virtualenv" diff --git a/crates/uv-build/Cargo.toml b/crates/uv-build/Cargo.toml index 6c3ab09d9..8897a421d 100644 --- a/crates/uv-build/Cargo.toml +++ b/crates/uv-build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-build" -version = "0.7.16" +version = "0.7.17" edition.workspace = true rust-version.workspace = true homepage.workspace = true diff --git a/crates/uv-build/pyproject.toml b/crates/uv-build/pyproject.toml index 5687497ae..ae0ecde47 100644 --- a/crates/uv-build/pyproject.toml +++ b/crates/uv-build/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uv-build" -version = "0.7.16" +version = "0.7.17" description = "The uv build backend" authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }] requires-python = ">=3.8" diff --git a/crates/uv-version/Cargo.toml b/crates/uv-version/Cargo.toml index 5d3be8a9d..2d0c3d096 100644 --- a/crates/uv-version/Cargo.toml +++ b/crates/uv-version/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-version" -version = "0.7.16" +version = "0.7.17" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml index 5f93c3ef6..5f0ea848e 100644 --- a/crates/uv/Cargo.toml +++ b/crates/uv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv" -version = "0.7.16" +version = "0.7.17" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } diff --git a/docs/concepts/build-backend.md b/docs/concepts/build-backend.md index 260680d4c..78bc84636 100644 --- a/docs/concepts/build-backend.md +++ b/docs/concepts/build-backend.md @@ -19,7 +19,7 @@ existing project, add it to the `[build-system]` section in your `pyproject.toml ```toml [build-system] -requires = ["uv_build>=0.7.16,<0.8.0"] +requires = ["uv_build>=0.7.17,<0.8.0"] build-backend = "uv_build" ``` diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index d2235fc71..62f9f50ae 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.7.16/install.sh | sh + $ curl -LsSf https://astral.sh/uv/0.7.17/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.7.16/install.ps1 | iex" + PS> powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/0.7.17/install.ps1 | iex" ``` !!! tip diff --git a/docs/guides/integration/aws-lambda.md b/docs/guides/integration/aws-lambda.md index 25be2ae38..a0ee99671 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.7.16 AS uv +FROM ghcr.io/astral-sh/uv:0.7.17 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.7.16 AS uv +FROM ghcr.io/astral-sh/uv:0.7.17 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 72d730362..48d7b6399 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.7.16` +- `ghcr.io/astral-sh/uv:{major}.{minor}.{patch}`, e.g., `ghcr.io/astral-sh/uv:0.7.17` - `ghcr.io/astral-sh/uv:{major}.{minor}`, e.g., `ghcr.io/astral-sh/uv:0.7` (the latest patch version) @@ -75,7 +75,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.7.16-alpine`. +`ghcr.io/astral-sh/uv:{major}.{minor}-{base}`, e.g., `ghcr.io/astral-sh/uv:0.7.17-alpine`. For more details, see the [GitHub Container](https://github.com/astral-sh/uv/pkgs/container/uv) page. @@ -113,7 +113,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.7.16 /uv /uvx /bin/ +COPY --from=ghcr.io/astral-sh/uv:0.7.17 /uv /uvx /bin/ ``` !!! tip @@ -131,7 +131,7 @@ COPY --from=ghcr.io/astral-sh/uv:0.7.16 /uv /uvx /bin/ Or, with the installer: ```dockerfile -ADD https://astral.sh/uv/0.7.16/install.sh /uv-installer.sh +ADD https://astral.sh/uv/0.7.17/install.sh /uv-installer.sh ``` ### Installing a project @@ -557,5 +557,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.7.16`, or (even better) the specific image digest, + version tag, e.g., `ghcr.io/astral-sh/uv:0.7.17`, 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 56fc51e21..f58fe2782 100644 --- a/docs/guides/integration/github.md +++ b/docs/guides/integration/github.md @@ -47,7 +47,7 @@ jobs: uses: astral-sh/setup-uv@v5 with: # Install a specific version of uv. - version: "0.7.16" + version: "0.7.17" ``` ## Setting up Python diff --git a/docs/guides/integration/pre-commit.md b/docs/guides/integration/pre-commit.md index 105d6f053..71fe7394f 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.7.16 + rev: 0.7.17 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.7.16 + rev: 0.7.17 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.7.16 + rev: 0.7.17 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.7.16 + rev: 0.7.17 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.7.16 + rev: 0.7.17 hooks: # Compile requirements - id: pip-compile diff --git a/pyproject.toml b/pyproject.toml index 32a928124..078bd4ebd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "uv" -version = "0.7.16" +version = "0.7.17" 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" From c0ebe6871d46b9cb1f4a2dcaf2e409beeaf7a4eb Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Sun, 29 Jun 2025 09:40:29 -0500 Subject: [PATCH 093/349] Improve trace message for cached Python interpreter query (#14328) --- crates/uv-python/src/interpreter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/uv-python/src/interpreter.rs b/crates/uv-python/src/interpreter.rs index 0cf65da10..df27b497b 100644 --- a/crates/uv-python/src/interpreter.rs +++ b/crates/uv-python/src/interpreter.rs @@ -1016,7 +1016,7 @@ impl InterpreterInfo { Ok(cached) => { if cached.timestamp == modified { trace!( - "Cached interpreter info for Python {}, skipping probing: {}", + "Found cached interpreter info for Python {}, skipping query of: {}", cached.data.markers.python_full_version(), executable.user_display() ); From 17b7eec287572214676f926ef1ee5d4fc502abf3 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Sun, 29 Jun 2025 12:25:10 -0500 Subject: [PATCH 094/349] Consistently normalize trailing slashes on URLs with no path segments (#14349) Alternative to https://github.com/astral-sh/uv/pull/14348 --- crates/uv-distribution-types/src/file.rs | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/crates/uv-distribution-types/src/file.rs b/crates/uv-distribution-types/src/file.rs index 948378c0c..81e3e2878 100644 --- a/crates/uv-distribution-types/src/file.rs +++ b/crates/uv-distribution-types/src/file.rs @@ -171,10 +171,21 @@ impl UrlString { } /// Return the [`UrlString`] (as a [`Cow`]) with trailing slash removed. + /// + /// This matches the semantics of [`Url::pop_if_empty`], which will not trim a trailing slash if + /// it's the only path segment, e.g., `https://example.com/` would be unchanged. #[must_use] pub fn without_trailing_slash(&self) -> Cow<'_, Self> { self.as_ref() .strip_suffix('/') + .filter(|path| { + // Only strip the trailing slash if there's _another_ trailing slash that isn't a + // part of the scheme. + path.split_once("://") + .map(|(_scheme, rest)| rest) + .unwrap_or(path) + .contains('/') + }) .map(|path| Cow::Owned(UrlString(SmallString::from(path)))) .unwrap_or(Cow::Borrowed(self)) } @@ -283,5 +294,20 @@ mod tests { url.without_trailing_slash(), Cow::Owned(UrlString("https://example.com/path".into())) ); + + // Does not remove a trailing slash if it's the only path segment + let url = UrlString("https://example.com/".into()); + assert_eq!(url.without_trailing_slash(), Cow::Borrowed(&url)); + + // Does not remove a trailing slash if it's the only path segment with a missing scheme + let url = UrlString("example.com/".into()); + assert_eq!(url.without_trailing_slash(), Cow::Borrowed(&url)); + + // Removes the trailing slash when the scheme is missing + let url = UrlString("example.com/path/".into()); + assert_eq!( + url.without_trailing_slash(), + Cow::Owned(UrlString("example.com/path".into())) + ); } } From d15efb7d918960473eed4ca4b12fd07afc8b77dd Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sun, 29 Jun 2025 15:07:07 -0400 Subject: [PATCH 095/349] Add an `IntoIterator` for `FormMetadata` (#14351) ## Summary Clippy would lint for this if the symbol were public as a matter of API hygiene, so adding it. --- crates/uv-publish/src/lib.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/uv-publish/src/lib.rs b/crates/uv-publish/src/lib.rs index 51f9bb472..f3dc768c6 100644 --- a/crates/uv-publish/src/lib.rs +++ b/crates/uv-publish/src/lib.rs @@ -758,6 +758,14 @@ impl FormMetadata { } } +impl<'a> IntoIterator for &'a FormMetadata { + type Item = &'a (&'a str, String); + type IntoIter = std::slice::Iter<'a, (&'a str, String)>; + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + /// Build the upload request. /// /// Returns the request and the reporter progress bar id. From 7603153f5b1ecfdd81e921b3915bb7b75d817ef7 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sun, 29 Jun 2025 15:30:52 -0400 Subject: [PATCH 096/349] Allow `alpha`, `beta`, and `rc` prefixes in tests (#14352) ## Summary A bunch of tests currently fail if you try to use a pre-release version. This PR makes the regular expressions more lenient. --- crates/uv/tests/it/common/mod.rs | 4 ++-- crates/uv/tests/it/version.rs | 14 ++++++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/crates/uv/tests/it/common/mod.rs b/crates/uv/tests/it/common/mod.rs index 9ead0a7c2..4c411899c 100644 --- a/crates/uv/tests/it/common/mod.rs +++ b/crates/uv/tests/it/common/mod.rs @@ -66,7 +66,7 @@ pub const INSTA_FILTERS: &[(&str, &str)] = &[ (r"uv\.exe", "uv"), // uv version display ( - r"uv(-.*)? \d+\.\d+\.\d+(\+\d+)?( \(.*\))?", + r"uv(-.*)? \d+\.\d+\.\d+(-(alpha|beta|rc)\.\d+)?(\+\d+)?( \([^)]*\))?", r"uv [VERSION] ([COMMIT] DATE)", ), // Trim end-of-line whitespaces, to allow removing them on save. @@ -254,7 +254,7 @@ impl TestContext { let added_filters = [ (r"home = .+".to_string(), "home = [PYTHON_HOME]".to_string()), ( - r"uv = \d+\.\d+\.\d+".to_string(), + r"uv = \d+\.\d+\.\d+(-(alpha|beta|rc)\.\d+)?(\+\d+)?".to_string(), "uv = [UV_VERSION]".to_string(), ), ( diff --git a/crates/uv/tests/it/version.rs b/crates/uv/tests/it/version.rs index 1fda42705..152cedaec 100644 --- a/crates/uv/tests/it/version.rs +++ b/crates/uv/tests/it/version.rs @@ -905,7 +905,7 @@ fn version_get_fallback_unmanaged_short() -> Result<()> { .filters() .into_iter() .chain([( - r"\d+\.\d+\.\d+(\+\d+)?( \(.*\))?", + r"\d+\.\d+\.\d+(-alpha\.\d+)?(\+\d+)?( \(.*\))?", r"[VERSION] ([COMMIT] DATE)", )]) .collect::>(); @@ -972,7 +972,10 @@ fn version_get_fallback_unmanaged_json() -> Result<()> { .filters() .into_iter() .chain([ - (r#"version": "\d+.\d+.\d+""#, r#"version": "[VERSION]""#), + ( + r#"version": "\d+\.\d+\.\d+(-(alpha|beta|rc)\.\d+)?(\+\d+)?""#, + r#"version": "[VERSION]""#, + ), ( r#"short_commit_hash": ".*""#, r#"short_commit_hash": "[HASH]""#, @@ -1175,7 +1178,7 @@ fn self_version_short() -> Result<()> { .filters() .into_iter() .chain([( - r"\d+\.\d+\.\d+(\+\d+)?( \(.*\))?", + r"\d+\.\d+\.\d+(-alpha\.\d+)?(\+\d+)?( \(.*\))?", r"[VERSION] ([COMMIT] DATE)", )]) .collect::>(); @@ -1220,7 +1223,10 @@ fn self_version_json() -> Result<()> { .filters() .into_iter() .chain([ - (r#"version": "\d+.\d+.\d+""#, r#"version": "[VERSION]""#), + ( + r#"version": "\d+\.\d+\.\d+(-(alpha|beta|rc)\.\d+)?(\+\d+)?""#, + r#"version": "[VERSION]""#, + ), ( r#"short_commit_hash": ".*""#, r#"short_commit_hash": "[HASH]""#, From d7e1fced434c85fb040f58b5eae74bdf61f78607 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 22:17:41 -0400 Subject: [PATCH 097/349] Update Rust crate cargo-util to v0.2.21 (#14356) --- Cargo.lock | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0c6578998..87429b1a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -512,9 +512,9 @@ dependencies = [ [[package]] name = "cargo-util" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767bc85f367f6483a6072430b56f5c0d6ee7636751a21a800526d0711753d76" +checksum = "c95ec8b2485b20aed818bd7460f8eecc6c87c35c84191b353a3aba9aa1736c36" dependencies = [ "anyhow", "core-foundation", @@ -738,7 +738,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1115,7 +1115,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1930,7 +1930,7 @@ checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" dependencies = [ "hermit-abi 0.4.0", "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1990,7 +1990,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2849,7 +2849,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3286,7 +3286,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3299,7 +3299,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.2", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3881,7 +3881,7 @@ dependencies = [ "getrandom 0.3.1", "once_cell", "rustix 1.0.7", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -6244,7 +6244,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] From a8b838dee903c97cb15de55e19d691ff0b0f6ded Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 22:17:48 -0400 Subject: [PATCH 098/349] Update astral-sh/setup-uv action to v6.3.1 (#14360) --- .github/workflows/ci.yml | 10 +++++----- .github/workflows/publish-pypi.yml | 4 ++-- .github/workflows/sync-python-releases.yml | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0ef7244d5..014c12c3a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,7 +82,7 @@ jobs: run: rustup component add rustfmt - name: "Install uv" - uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0 + uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1 - name: "rustfmt" run: cargo fmt --all --check @@ -213,7 +213,7 @@ jobs: - name: "Install Rust toolchain" run: rustup show - - uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0 + - uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1 - name: "Install required Python versions" run: uv python install @@ -245,7 +245,7 @@ jobs: - name: "Install Rust toolchain" run: rustup show - - uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0 + - uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1 - name: "Install required Python versions" run: uv python install @@ -279,7 +279,7 @@ jobs: run: | Copy-Item -Path "${{ github.workspace }}" -Destination "${{ env.UV_WORKSPACE }}" -Recurse - - uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0 + - uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1 - name: "Install required Python versions" run: uv python install @@ -430,7 +430,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 - - uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0 + - uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1 - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 - name: "Add SSH key" if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }} diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 84c8e44d9..e4435ff17 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -22,7 +22,7 @@ jobs: id-token: write steps: - name: "Install uv" - uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0 + uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1 - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: pattern: wheels_uv-* @@ -43,7 +43,7 @@ jobs: id-token: write steps: - name: "Install uv" - uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0 + uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1 - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: pattern: wheels_uv_build-* diff --git a/.github/workflows/sync-python-releases.yml b/.github/workflows/sync-python-releases.yml index 14b572e08..166458507 100644 --- a/.github/workflows/sync-python-releases.yml +++ b/.github/workflows/sync-python-releases.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0 + - uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1 with: version: "latest" enable-cache: true From 40386e438ff7ed8c1d0792981f24882a5b074a8b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 22:17:59 -0400 Subject: [PATCH 099/349] Update Rust crate owo-colors to v4.2.2 (#14357) --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 87429b1a7..21d767adb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2474,9 +2474,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "owo-colors" -version = "4.2.1" +version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26995317201fa17f3656c36716aed4a7c81743a9634ac4c99c0eeda495db0cec" +checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" [[package]] name = "parking" From e9533a0e296255162736c165927ba289f3fcaaf8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 22:18:12 -0400 Subject: [PATCH 100/349] Update aws-actions/configure-aws-credentials digest to 3d8cba3 (#14354) --- .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 014c12c3a..7ca7d9b6b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1443,7 +1443,7 @@ jobs: run: chmod +x ./uv - name: "Configure AWS credentials" - uses: aws-actions/configure-aws-credentials@3bb878b6ab43ba8717918141cd07a0ea68cfe7ea + uses: aws-actions/configure-aws-credentials@3d8cba388a057b13744d61818a337e40a119b1a7 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} From e44a64ee133fe4c3c4fecb1c7a4c60043f965470 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 22:18:26 -0400 Subject: [PATCH 101/349] Update Rust crate windows-registry to v0.5.3 (#14359) --- Cargo.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 21d767adb..416bb3606 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5602,7 +5602,7 @@ dependencies = [ "uv-trampoline-builder", "uv-warnings", "which", - "windows-registry 0.5.2", + "windows-registry 0.5.3", "windows-result 0.3.4", "windows-sys 0.59.0", ] @@ -5806,7 +5806,7 @@ dependencies = [ "tracing", "uv-fs", "uv-static", - "windows-registry 0.5.2", + "windows-registry 0.5.3", "windows-result 0.3.4", "windows-sys 0.59.0", ] @@ -6330,7 +6330,7 @@ dependencies = [ "windows-interface 0.59.1", "windows-link", "windows-result 0.3.4", - "windows-strings 0.4.1", + "windows-strings 0.4.2", ] [[package]] @@ -6400,9 +6400,9 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-numerics" @@ -6427,13 +6427,13 @@ dependencies = [ [[package]] name = "windows-registry" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3bab093bdd303a1240bb99b8aba8ea8a69ee19d34c9e2ef9594e708a4878820" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" dependencies = [ "windows-link", "windows-result 0.3.4", - "windows-strings 0.4.1", + "windows-strings 0.4.2", ] [[package]] @@ -6465,9 +6465,9 @@ dependencies = [ [[package]] name = "windows-strings" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a7ab927b2637c19b3dbe0965e75d8f2d30bdd697a1516191cad2ec4df8fb28a" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ "windows-link", ] From b2979d25a81423c69e31ad59892a3b584a99191d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 22:19:38 -0400 Subject: [PATCH 102/349] Update acj/freebsd-firecracker-action action to v0.5.1 (#14355) --- .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 7ca7d9b6b..797ccd303 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -661,7 +661,7 @@ jobs: cross build --target x86_64-unknown-freebsd - name: Test in Firecracker VM - uses: acj/freebsd-firecracker-action@6c57bda7113c2f137ef00d54512d61ae9d64365b # v0.5.0 + uses: acj/freebsd-firecracker-action@136ca0bce2adade21e526ceb07db643ad23dd2dd # v0.5.1 with: verbose: false checkout: false From 61482da31924407ca004b73610600bf9137c3e02 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 30 Jun 2025 12:13:33 +0200 Subject: [PATCH 103/349] Update pre-commit hook astral-sh/ruff-pre-commit to v0.12.1 (#14362) 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 | |---|---|---|---| | [astral-sh/ruff-pre-commit](https://redirect.github.com/astral-sh/ruff-pre-commit) | repository | minor | `v0.11.13` -> `v0.12.1` | --- > [!WARNING] > Some dependencies could not be looked up. Check the Dependency Dashboard for more information. Note: The `pre-commit` manager in Renovate is not supported by the `pre-commit` maintainers or community. Please do not report any problems there, instead [create a Discussion in the Renovate repository](https://redirect.github.com/renovatebot/renovate/discussions/new) if you have any questions. --- ### Release Notes
    astral-sh/ruff-pre-commit (astral-sh/ruff-pre-commit) ### [`v0.12.1`](https://redirect.github.com/astral-sh/ruff-pre-commit/releases/tag/v0.12.1) [Compare Source](https://redirect.github.com/astral-sh/ruff-pre-commit/compare/v0.12.0...v0.12.1) See: https://github.com/astral-sh/ruff/releases/tag/0.12.1 ### [`v0.12.0`](https://redirect.github.com/astral-sh/ruff-pre-commit/releases/tag/v0.12.0) [Compare Source](https://redirect.github.com/astral-sh/ruff-pre-commit/compare/v0.11.13...v0.12.0) See: https://github.com/astral-sh/ruff/releases/tag/0.12.0
    --- ### Configuration 📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC), 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> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 982d8f296..2280f337b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -42,7 +42,7 @@ repos: types_or: [yaml, json5] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.13 + rev: v0.12.1 hooks: - id: ruff-format - id: ruff From 15551a0201c616c005f605c973772c38b423e7d4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 30 Jun 2025 12:24:54 +0200 Subject: [PATCH 104/349] Update Swatinem/rust-cache action to v2.8.0 (#14366) 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 | |---|---|---|---| | [Swatinem/rust-cache](https://redirect.github.com/Swatinem/rust-cache) | action | minor | `v2.7.8` -> `v2.8.0` | --- > [!WARNING] > Some dependencies could not be looked up. Check the Dependency Dashboard for more information. --- ### Release Notes
    Swatinem/rust-cache (Swatinem/rust-cache) ### [`v2.8.0`](https://redirect.github.com/Swatinem/rust-cache/releases/tag/v2.8.0) [Compare Source](https://redirect.github.com/Swatinem/rust-cache/compare/v2.7.8...v2.8.0) ##### What's Changed - Add cache-workspace-crates feature by [@​jbransen](https://redirect.github.com/jbransen) in [https://github.com/Swatinem/rust-cache/pull/246](https://redirect.github.com/Swatinem/rust-cache/pull/246) - Feat: support warpbuild cache provider by [@​stegaBOB](https://redirect.github.com/stegaBOB) in [https://github.com/Swatinem/rust-cache/pull/247](https://redirect.github.com/Swatinem/rust-cache/pull/247) ##### New Contributors - [@​jbransen](https://redirect.github.com/jbransen) made their first contribution in [https://github.com/Swatinem/rust-cache/pull/246](https://redirect.github.com/Swatinem/rust-cache/pull/246) - [@​stegaBOB](https://redirect.github.com/stegaBOB) made their first contribution in [https://github.com/Swatinem/rust-cache/pull/247](https://redirect.github.com/Swatinem/rust-cache/pull/247) **Full Changelog**: https://github.com/Swatinem/rust-cache/compare/v2.7.8...v2.8.0
    --- ### Configuration 📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC), 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 | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 797ccd303..870171cdc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -126,7 +126,7 @@ jobs: name: "cargo clippy | ubuntu" steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 + - uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 with: save-if: ${{ github.ref == 'refs/heads/main' }} - name: "Check uv_build dependencies" @@ -156,7 +156,7 @@ jobs: run: | Copy-Item -Path "${{ github.workspace }}" -Destination "${{ env.UV_WORKSPACE }}" -Recurse - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 + - uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 with: workspaces: ${{ env.UV_WORKSPACE }} @@ -175,7 +175,7 @@ jobs: name: "cargo dev generate-all" steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 + - uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 with: save-if: ${{ github.ref == 'refs/heads/main' }} - name: "Generate all" @@ -208,7 +208,7 @@ jobs: - uses: rui314/setup-mold@v1 - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 + - uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 - name: "Install Rust toolchain" run: rustup show @@ -240,7 +240,7 @@ jobs: - uses: rui314/setup-mold@v1 - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 + - uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 - name: "Install Rust toolchain" run: rustup show @@ -283,7 +283,7 @@ jobs: - name: "Install required Python versions" run: uv python install - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 + - uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 with: workspaces: ${{ env.UV_WORKSPACE }} @@ -332,7 +332,7 @@ jobs: run: | Copy-Item -Path "${{ github.workspace }}" -Destination "${{ env.UV_WORKSPACE }}" -Recurse - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 + - uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 with: workspaces: ${{ env.UV_WORKSPACE }}/crates/uv-trampoline @@ -388,7 +388,7 @@ jobs: - name: Copy Git Repo to Dev Drive run: | Copy-Item -Path "${{ github.workspace }}" -Destination "${{ env.UV_WORKSPACE }}" -Recurse - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 + - uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 with: workspaces: ${{ env.UV_WORKSPACE }}/crates/uv-trampoline - name: "Install Rust toolchain" @@ -456,7 +456,7 @@ jobs: - uses: rui314/setup-mold@v1 - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 + - uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 - name: "Build" run: cargo build @@ -486,7 +486,7 @@ jobs: sudo apt-get install musl-tools rustup target add x86_64-unknown-linux-musl - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 + - uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 - name: "Build" run: cargo build --target x86_64-unknown-linux-musl --bin uv --bin uvx @@ -511,7 +511,7 @@ jobs: - uses: rui314/setup-mold@v1 - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 + - uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 - name: "Build" run: cargo build --bin uv --bin uvx @@ -535,7 +535,7 @@ jobs: - uses: rui314/setup-mold@v1 - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 + - uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 - name: "Build" run: cargo build --bin uv --bin uvx @@ -565,7 +565,7 @@ jobs: run: | Copy-Item -Path "${{ github.workspace }}" -Destination "${{ env.UV_WORKSPACE }}" -Recurse - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 + - uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 with: workspaces: ${{ env.UV_WORKSPACE }} @@ -600,7 +600,7 @@ jobs: run: | Copy-Item -Path "${{ github.workspace }}" -Destination "${{ env.UV_WORKSPACE }}" -Recurse - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 + - uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 with: workspaces: ${{ env.UV_WORKSPACE }} @@ -637,7 +637,7 @@ jobs: run: rustup default ${{ steps.msrv.outputs.value }} - name: "Install mold" uses: rui314/setup-mold@v1 - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 + - uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 - run: cargo +${{ steps.msrv.outputs.value }} build - run: ./target/debug/uv --version @@ -650,7 +650,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 + - uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 - name: "Cross build" run: | # Install cross from `freebsd-firecracker` @@ -2337,7 +2337,7 @@ jobs: - name: "Checkout Branch" uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 + - uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 - name: "Install Rust toolchain" run: rustup show @@ -2374,7 +2374,7 @@ jobs: - name: "Checkout Branch" uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 + - uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 - name: "Install Rust toolchain" run: rustup show From 5cfabd7085bd265fcc74bd227edf521a53a0aac3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 30 Jun 2025 13:39:20 +0000 Subject: [PATCH 105/349] Update Rust crate schemars to v1.0.3 (#14358) 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 | |---|---|---|---| | [schemars](https://graham.cool/schemars/) ([source](https://redirect.github.com/GREsau/schemars)) | workspace.dependencies | patch | `1.0.0` -> `1.0.3` | --- > [!WARNING] > Some dependencies could not be looked up. Check the Dependency Dashboard for more information. --- ### Release Notes
    GREsau/schemars (schemars) ### [`v1.0.3`](https://redirect.github.com/GREsau/schemars/blob/HEAD/CHANGELOG.md#103---2025-06-28) [Compare Source](https://redirect.github.com/GREsau/schemars/compare/v1.0.2...v1.0.3) ##### Fixed - Fix compile error when a doc comment is set on both a `transparent` (or newtype) struct and its field ([https://github.com/GREsau/schemars/issues/446](https://redirect.github.com/GREsau/schemars/issues/446)) - Fix `json_schema!()` macro compatibility when used from pre-2021 rust editions ([https://github.com/GREsau/schemars/pull/447](https://redirect.github.com/GREsau/schemars/pull/447)) ### [`v1.0.2`](https://redirect.github.com/GREsau/schemars/blob/HEAD/CHANGELOG.md#102---2025-06-26) [Compare Source](https://redirect.github.com/GREsau/schemars/compare/v1.0.1...v1.0.2) ##### Fixed - Fix schema properties being incorrectly reordered during serialization ([https://github.com/GREsau/schemars/issues/444](https://redirect.github.com/GREsau/schemars/issues/444)) ### [`v1.0.1`](https://redirect.github.com/GREsau/schemars/blob/HEAD/CHANGELOG.md#101---2025-06-24) [Compare Source](https://redirect.github.com/GREsau/schemars/compare/v1.0.0...v1.0.1) ##### Fixed - Deriving `JsonSchema` with `no_std` broken due to `std::borrow::ToOwned` trait not being in scope ([https://github.com/GREsau/schemars/issues/441](https://redirect.github.com/GREsau/schemars/issues/441))
    --- ### Configuration 📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC), 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> Co-authored-by: konstin --- Cargo.lock | 8 ++++---- uv.schema.json | 18 +++++++++--------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 416bb3606..eb2faac26 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3405,9 +3405,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.0.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "febc07c7e70b5db4f023485653c754d76e1bbe8d9dbfa20193ce13da9f9633f4" +checksum = "1375ba8ef45a6f15d83fa8748f1079428295d403d6ea991d09ab100155fbc06d" dependencies = [ "dyn-clone", "ref-cast", @@ -3419,9 +3419,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "1.0.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1eeedaab7b1e1d09b5b4661121f4d27f9e7487089b0117833ccd7a882ee1ecc" +checksum = "2b13ed22d6d49fe23712e068770b5c4df4a693a2b02eeff8e7ca3135627a24f6" dependencies = [ "proc-macro2", "quote", diff --git a/uv.schema.json b/uv.schema.json index b00463ee9..26d6ac7a3 100644 --- a/uv.schema.json +++ b/uv.schema.json @@ -894,15 +894,6 @@ "Index": { "type": "object", "properties": { - "format": { - "description": "The format used by the index.\n\nIndexes can either be PEP 503-compliant (i.e., a PyPI-style registry implementing the Simple\nAPI) or structured as a flat list of distributions (e.g., `--find-links`). In both cases,\nindexes can point to either local or remote resources.", - "allOf": [ - { - "$ref": "#/definitions/IndexFormat" - } - ], - "default": "simple" - }, "authenticate": { "description": "When uv should use authentication for requests to the index.\n\n```toml\n[[tool.uv.index]]\nname = \"my-index\"\nurl = \"https:///simple\"\nauthenticate = \"always\"\n```", "allOf": [ @@ -922,6 +913,15 @@ "type": "boolean", "default": false }, + "format": { + "description": "The format used by the index.\n\nIndexes can either be PEP 503-compliant (i.e., a PyPI-style registry implementing the Simple\nAPI) or structured as a flat list of distributions (e.g., `--find-links`). In both cases,\nindexes can point to either local or remote resources.", + "allOf": [ + { + "$ref": "#/definitions/IndexFormat" + } + ], + "default": "simple" + }, "ignore-error-codes": { "description": "Status codes that uv should ignore when deciding whether\nto continue searching in the next index after a failure.\n\n```toml\n[[tool.uv.index]]\nname = \"my-index\"\nurl = \"https:///simple\"\nignore-error-codes = [401, 403]\n```", "type": [ From ae500c95d20df9b372be4c946cc143115ba79755 Mon Sep 17 00:00:00 2001 From: Ondrej Profant Date: Mon, 30 Jun 2025 15:58:55 +0200 Subject: [PATCH 106/349] Docs: add instructions for publishing to JFrog's Artifactory (#14253) ## Summary Add instructions for publishing to JFrog's Artifactory into [documentation](https://docs.astral.sh/uv/guides/integration/alternative-indexes/). Related issues: https://github.com/astral-sh/uv/issues/9845 https://github.com/astral-sh/uv/issues/10193 ## Test Plan I ran the documentation locally and use npx prettier. --------- Co-authored-by: Ondrej Profant Co-authored-by: Zanie Blue --- .../guides/integration/alternative-indexes.md | 66 ++++++++++++++++++- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/docs/guides/integration/alternative-indexes.md b/docs/guides/integration/alternative-indexes.md index 0258e4a74..3e73efff0 100644 --- a/docs/guides/integration/alternative-indexes.md +++ b/docs/guides/integration/alternative-indexes.md @@ -368,6 +368,68 @@ $ uv publish Note this method is not preferable because uv cannot check if the package is already published before uploading artifacts. -## Other package indexes +## JFrog Artifactory -uv is also known to work with JFrog's Artifactory. +uv can install packages from JFrog Artifactory, either by using a username and password or a JWT +token. + +To use it, add the index to your project: + +```toml title="pyproject.toml" +[[tool.uv.index]] +name = "private-registry" +url = "https://.jfrog.io/artifactory/api/pypi//simple" +``` + +### Authenticate with username and password + +```console +$ export UV_INDEX_PRIVATE_REGISTRY_USERNAME="" +$ export UV_INDEX_PRIVATE_REGISTRY_PASSWORD="" +``` + +### Authenticate with JWT token + +```console +$ export UV_INDEX_PRIVATE_REGISTRY_USERNAME="" +$ export UV_INDEX_PRIVATE_REGISTRY_PASSWORD="$JFROG_JWT_TOKEN" +``` + +!!! note + + Replace `PRIVATE_REGISTRY` in the environment variable names with the actual index name defined in your `pyproject.toml`. + +### Publishing packages to JFrog Artifactory + +Add a `publish-url` to your index definition: + +```toml title="pyproject.toml" +[[tool.uv.index]] +name = "private-registry" +url = "https://.jfrog.io/artifactory/api/pypi//simple" +publish-url = "https://.jfrog.io/artifactory/api/pypi/" +``` + +!!! important + + If you use `--token "$JFROG_TOKEN"` or `UV_PUBLISH_TOKEN` with JFrog, you will receive a + 401 Unauthorized error as JFrog requires an empty username but uv passes `__token__` for as + the username when `--token` is used. + +To authenticate, pass your token as the password and set the username to an empty string: + +```console +$ uv publish --index -u "" -p "$JFROG_TOKEN" +``` + +Alternatively, you can set environment variables: + +```console +$ export UV_PUBLISH_USERNAME="" +$ export UV_PUBLISH_PASSWORD="$JFROG_TOKEN" +$ uv publish --index private-registry +``` + +!!! note + + The publish environment variables (`UV_PUBLISH_USERNAME` and `UV_PUBLISH_PASSWORD`) do not include the index name. From 0372a5b05d21052a337fcbd64957223f869fbed2 Mon Sep 17 00:00:00 2001 From: konsti Date: Mon, 30 Jun 2025 16:32:28 +0200 Subject: [PATCH 107/349] Ignore invalid build backend settings when not building (#14372) Fixes #14323 --- crates/uv-workspace/src/pyproject.rs | 44 +++++++++++++++++++++++++++- crates/uv/tests/it/build_backend.rs | 37 +++++++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/crates/uv-workspace/src/pyproject.rs b/crates/uv-workspace/src/pyproject.rs index dabbe33fb..41e20914f 100644 --- a/crates/uv-workspace/src/pyproject.rs +++ b/crates/uv-workspace/src/pyproject.rs @@ -24,6 +24,7 @@ use uv_fs::{PortablePathBuf, relative_to}; use uv_git_types::GitReference; use uv_macros::OptionsMetadata; use uv_normalize::{DefaultGroups, ExtraName, GroupName, PackageName}; +use uv_options_metadata::{OptionSet, OptionsMetadata, Visit}; use uv_pep440::{Version, VersionSpecifiers}; use uv_pep508::MarkerTree; use uv_pypi_types::{ @@ -610,7 +611,7 @@ pub struct ToolUv { /// Note that those settings only apply when using the `uv_build` backend, other build backends /// (such as hatchling) have their own configuration. #[option_group] - pub build_backend: Option, + pub build_backend: Option, } #[derive(Default, Debug, Clone, PartialEq, Eq)] @@ -1684,3 +1685,44 @@ pub enum DependencyType { /// A dependency in `dependency-groups.{0}`. Group(GroupName), } + +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(test, derive(Serialize))] +pub struct BuildBackendSettingsSchema; + +impl<'de> Deserialize<'de> for BuildBackendSettingsSchema { + fn deserialize(_deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(BuildBackendSettingsSchema) + } +} + +#[cfg(feature = "schemars")] +impl schemars::JsonSchema for BuildBackendSettingsSchema { + fn schema_name() -> Cow<'static, str> { + BuildBackendSettings::schema_name() + } + + fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema { + BuildBackendSettings::json_schema(generator) + } +} + +impl OptionsMetadata for BuildBackendSettingsSchema { + fn record(visit: &mut dyn Visit) { + BuildBackendSettings::record(visit); + } + + fn documentation() -> Option<&'static str> { + BuildBackendSettings::documentation() + } + + fn metadata() -> OptionSet + where + Self: Sized + 'static, + { + BuildBackendSettings::metadata() + } +} diff --git a/crates/uv/tests/it/build_backend.rs b/crates/uv/tests/it/build_backend.rs index 3dd38278f..84b0ed8fe 100644 --- a/crates/uv/tests/it/build_backend.rs +++ b/crates/uv/tests/it/build_backend.rs @@ -858,3 +858,40 @@ fn symlinked_file() -> Result<()> { Ok(()) } + +/// Ignore invalid build backend settings when not building. +/// +/// They may be from another `uv_build` version that has a different schema. +#[test] +fn invalid_build_backend_settings_are_ignored() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str(indoc! {r#" + [project] + name = "built-by-uv" + version = "0.1.0" + requires-python = ">=3.12" + + [tool.uv.build-backend] + # Error: `source-include` must be a list + source-include = "data/build-script.py" + + [build-system] + requires = ["uv_build>=10000,<10001"] + build-backend = "uv_build" + "#})?; + + // Since we are not building, this must pass without complaining about the error in + // `tool.uv.build-backend`. + uv_snapshot!(context.filters(), context.lock(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + "); + + Ok(()) +} From 1c7c174bc80e9209bb52f975743c0cf55bc78e6d Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Mon, 30 Jun 2025 10:39:47 -0500 Subject: [PATCH 108/349] Include the canonical path in the interpreter query cache key (#14331) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes an obscure cache collision in Python interpreter queries, which we believe to be the root cause of CI flakes we've been seeing where a project environment is invalidated and recreated. This work follows from the logs in [this CI run](https://github.com/astral-sh/uv/actions/runs/15934322410/job/44950599993?pr=14326) which captured one of the flakes with tracing enabled. There, we can see that the project environment is invalidated because the Python interpreter in the environment has a different version than expected: ``` DEBUG Checking for Python environment at `.venv` TRACE Cached interpreter info for Python 3.12.9, skipping probing: .venv/bin/python3 DEBUG The interpreter in the project environment has different version (3.12.9) than it was created with (3.9.21) ``` (this message is updated to reflect #14329) The flow is roughly: - We create an environment with 3.12.9 - We query the environment, and cache the interpreter version for `.venv/bin/python` - We create an environment for 3.9.12, replacing the existing one - We query the environment, and read the cached information The Python cache entries are keyed by the absolute path to the interpreter, and rely on the modification time (ctime, nsec resolution) of the canonicalized path to determine if the cache entry should be invalidated. The key is a hex representation of a u64 sea hasher output — which is very unlikely to collide. After an audit of the Python query caching logic, we determined that the most likely cause of a collision in cache entries is that the modification times of underlying interpreters are identical. This seems pretty feasible, especially if the file system does not support nanosecond precision — though it appears that the GitHub runners do support it. The fix here is to include the canonicalized path in the cache key, which ensures we're looking at the modification time of the _same_ underlying interpreter. This will "invalidate" all existing interpreter cache entries but that's not a big deal. This should also have the effect of reducing cache churn for interpreters in virtual environments. Now, when you change Python versions, we won't invalidate the previous cache entry so if you change _back_ to the old version we can re-use our cached information. It's a bit speculative, since we don't have a deterministic reproduction in CI, but this is the strongest candidate given the logs and should increase correctness regardless. Closes https://github.com/astral-sh/uv/issues/14160 Closes https://github.com/astral-sh/uv/issues/13744 Closes https://github.com/astral-sh/uv/issues/13745 Once it's confirmed the flakes are resolved, we should revert - https://github.com/astral-sh/uv/pull/14275 - #13817 --- crates/uv-python/src/interpreter.rs | 55 +++++++++++++++++------------ 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/crates/uv-python/src/interpreter.rs b/crates/uv-python/src/interpreter.rs index df27b497b..0f074ebb6 100644 --- a/crates/uv-python/src/interpreter.rs +++ b/crates/uv-python/src/interpreter.rs @@ -967,6 +967,31 @@ impl InterpreterInfo { pub(crate) fn query_cached(executable: &Path, cache: &Cache) -> Result { let absolute = std::path::absolute(executable)?; + // Provide a better error message if the link is broken or the file does not exist. Since + // `canonicalize_executable` does not resolve the file on Windows, we must re-use this logic + // for the subsequent metadata read as we may not have actually resolved the path. + let handle_io_error = |err: io::Error| -> Error { + if err.kind() == io::ErrorKind::NotFound { + // Check if it looks like a venv interpreter where the underlying Python + // installation was removed. + if absolute + .symlink_metadata() + .is_ok_and(|metadata| metadata.is_symlink()) + { + Error::BrokenSymlink(BrokenSymlink { + path: executable.to_path_buf(), + venv: uv_fs::is_virtualenv_executable(executable), + }) + } else { + Error::NotFound(executable.to_path_buf()) + } + } else { + err.into() + } + }; + + let canonical = canonicalize_executable(&absolute).map_err(handle_io_error)?; + let cache_entry = cache.entry( CacheBucket::Interpreter, // Shard interpreter metadata by host architecture, operating system, and version, to @@ -978,33 +1003,17 @@ impl InterpreterInfo { )), // We use the absolute path for the cache entry to avoid cache collisions for relative // paths. But we don't want to query the executable with symbolic links resolved because - // that can change reported values, e.g., `sys.executable`. - format!("{}.msgpack", cache_digest(&absolute)), + // that can change reported values, e.g., `sys.executable`. We include the canonical + // path in the cache entry as well, otherwise we can have cache collisions if an + // absolute path refers to different interpreters with matching ctimes, e.g., if you + // have a `.venv/bin/python` pointing to both Python 3.12 and Python 3.13 that were + // modified at the same time. + format!("{}.msgpack", cache_digest(&(&absolute, &canonical))), ); // We check the timestamp of the canonicalized executable to check if an underlying // interpreter has been modified. - let modified = canonicalize_executable(&absolute) - .and_then(Timestamp::from_path) - .map_err(|err| { - if err.kind() == io::ErrorKind::NotFound { - // Check if it looks like a venv interpreter where the underlying Python - // installation was removed. - if absolute - .symlink_metadata() - .is_ok_and(|metadata| metadata.is_symlink()) - { - Error::BrokenSymlink(BrokenSymlink { - path: executable.to_path_buf(), - venv: uv_fs::is_virtualenv_executable(executable), - }) - } else { - Error::NotFound(executable.to_path_buf()) - } - } else { - err.into() - } - })?; + let modified = Timestamp::from_path(canonical).map_err(handle_io_error)?; // Read from the cache. if cache From 317ce6e24551f05e6b78b0ce46c5afdeb0645b32 Mon Sep 17 00:00:00 2001 From: Aria Desires Date: Mon, 30 Jun 2025 17:42:00 -0400 Subject: [PATCH 109/349] disfavor aarch64 windows in its own house (#13724) and prefer emulated x64 windows in its stead. This is preparatory work for shipping support for uv downloading and installing aarch64 (arm64) windows Pythons. We've [had builds for this platform ready for a while](https://github.com/astral-sh/python-build-standalone/pull/387), but have held back on shipping them due to a fundamental problem: **The Python packaging ecosystem does not have strong support for aarch64 windows**, e.g., not many projects build aarch64 wheels yet. The net effect of this is that, if we handed you an aarch64 python interpreter on windows, you would have to build a lot more sdists, and there's a high chance you will simply fail to build that sdist and be sad. Yes unfortunately, in this case a non-native Python interpreter simply *works better* than the native one... in terms of working at all, today. Of course, if the native interpreter works for your project, it should presumably have better performance and platform compatibility. We do not want to stand in the way of progress, as ideally this situation is a temporary state of affairs as the ecosystem grows to support aarch64 windows. To enable progress, on aarch64 Windows builds of uv: * We will still use a native python interpreter, e.g., if it's at the front of your `PATH` or the only installed version. * If we are choosing between equally good interpreters that differ in architecture, x64 will be preferred. * If the aarch64 version is newer, we will prefer the aarch64 one. * We will emit a diagnostic on installation, and show the python request to pass to uv to force aarch64 windows to be used. * Will be shipping [aarch64 Windows Python downloads](https://github.com/astral-sh/python-build-standalone/pull/387) * Will probably add some kind of global override setting/env-var to disable this behaviour. * Will be shipping this behaviour in [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) We're coordinating with Microsoft, GitHub (for the `setup-python` action), and the CPython team (for the `python.org` installers), to ensure we're aligned on this default and the timing of toggling to prefer native distributions in the future. See discussion in - https://github.com/astral-sh/uv/issues/12906 --- This is an alternative to * #13719 which uses sorting rather than filtering, as discussed in * #13721 --- .github/workflows/ci.yml | 22 ++++++++++ crates/uv-python/src/platform.rs | 29 +++++++++++-- crates/uv/src/commands/python/install.rs | 53 +++++++++++++++++++++++- 3 files changed, 98 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 870171cdc..a0baa96c8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2090,6 +2090,28 @@ jobs: - name: "Validate global Python install" run: py -3.13 ./scripts/check_system_python.py --uv ./uv.exe + system-test-windows-aarch64-aarch64-python-313: + timeout-minutes: 10 + needs: build-binary-windows-aarch64 + name: "check system | aarch64 python3.13 on windows aarch64" + runs-on: github-windows-11-aarch64-4 + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: "3.13" + architecture: "arm64" + allow-prereleases: true + + - name: "Download binary" + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: uv-windows-aarch64-${{ github.sha }} + + - name: "Validate global Python install" + run: py -3.13 ./scripts/check_system_python.py --uv ./uv.exe + # Test our PEP 514 integration that installs Python into the Windows registry. system-test-windows-registry: timeout-minutes: 10 diff --git a/crates/uv-python/src/platform.rs b/crates/uv-python/src/platform.rs index 025592c37..ce8620ae2 100644 --- a/crates/uv-python/src/platform.rs +++ b/crates/uv-python/src/platform.rs @@ -43,15 +43,36 @@ impl Ord for Arch { return self.variant.cmp(&other.variant); } - let native = Arch::from_env(); + // For the time being, manually make aarch64 windows disfavored + // on its own host platform, because most packages don't have wheels for + // aarch64 windows, making emulation more useful than native execution! + // + // The reason we do this in "sorting" and not "supports" is so that we don't + // *refuse* to use an aarch64 windows pythons if they happen to be installed + // and nothing else is available. + // + // Similarly if someone manually requests an aarch64 windows install, we + // should respect that request (this is the way users should "override" + // this behaviour). + let preferred = if cfg!(all(windows, target_arch = "aarch64")) { + Arch { + family: target_lexicon::Architecture::X86_64, + variant: None, + } + } else { + // Prefer native architectures + Arch::from_env() + }; - // Prefer native architectures - match (self.family == native.family, other.family == native.family) { + match ( + self.family == preferred.family, + other.family == preferred.family, + ) { (true, true) => unreachable!(), (true, false) => std::cmp::Ordering::Less, (false, true) => std::cmp::Ordering::Greater, (false, false) => { - // Both non-native, fallback to lexicographic order + // Both non-preferred, fallback to lexicographic order self.family.to_string().cmp(&other.family.to_string()) } } diff --git a/crates/uv/src/commands/python/install.rs b/crates/uv/src/commands/python/install.rs index 08f95003e..3df0cf91d 100644 --- a/crates/uv/src/commands/python/install.rs +++ b/crates/uv/src/commands/python/install.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::collections::BTreeMap; use std::fmt::Write; use std::io::ErrorKind; use std::path::{Path, PathBuf}; @@ -15,7 +16,9 @@ use tracing::{debug, trace}; use uv_configuration::PreviewMode; use uv_fs::Simplified; -use uv_python::downloads::{self, DownloadResult, ManagedPythonDownload, PythonDownloadRequest}; +use uv_python::downloads::{ + self, ArchRequest, DownloadResult, ManagedPythonDownload, PythonDownloadRequest, +}; use uv_python::managed::{ ManagedPythonInstallation, ManagedPythonInstallations, PythonMinorVersionLink, create_link_to_executable, python_executable_dir, @@ -401,6 +404,7 @@ pub(crate) async fn install( let mut errors = vec![]; let mut downloaded = Vec::with_capacity(downloads.len()); + let mut requests_by_new_installation = BTreeMap::new(); while let Some((download, result)) = tasks.next().await { match result { Ok(download_result) => { @@ -412,10 +416,19 @@ pub(crate) async fn install( let installation = ManagedPythonInstallation::new(path, download); changelog.installed.insert(installation.key().clone()); + for request in &requests { + // Take note of which installations satisfied which requests + if request.matches_installation(&installation) { + requests_by_new_installation + .entry(installation.key().clone()) + .or_insert(Vec::new()) + .push(request); + } + } if changelog.existing.contains(installation.key()) { changelog.uninstalled.insert(installation.key().clone()); } - downloaded.push(installation); + downloaded.push(installation.clone()); } Err(err) => { errors.push((download.key().clone(), anyhow::Error::new(err))); @@ -529,6 +542,42 @@ pub(crate) async fn install( } if !changelog.installed.is_empty() { + for install_key in &changelog.installed { + // Make a note if the selected python is non-native for the architecture, + // if none of the matching user requests were explicit + let native_arch = Arch::from_env(); + if install_key.arch().family() != native_arch.family() { + let not_explicit = + requests_by_new_installation + .get(install_key) + .and_then(|requests| { + let all_non_explicit = requests.iter().all(|request| { + if let PythonRequest::Key(key) = &request.request { + !matches!(key.arch(), Some(ArchRequest::Explicit(_))) + } else { + true + } + }); + if all_non_explicit { + requests.iter().next() + } else { + None + } + }); + if let Some(not_explicit) = not_explicit { + let native_request = + not_explicit.download_request.clone().with_arch(native_arch); + writeln!( + printer.stderr(), + "{} uv selected a Python distribution with an emulated architecture ({}) for your platform because support for the native architecture ({}) is not yet mature; to override this behaviour, request the native architecture explicitly with: {}", + "note:".bold(), + install_key.arch(), + native_arch, + native_request + )?; + } + } + } if changelog.installed.len() == 1 { let installed = changelog.installed.iter().next().unwrap(); // Ex) "Installed Python 3.9.7 in 1.68s" From 2f9061dcd08a797072bc806a5c736fd7be3acfe3 Mon Sep 17 00:00:00 2001 From: Aria Desires Date: Mon, 30 Jun 2025 18:02:19 -0400 Subject: [PATCH 110/349] Update python, add support for installing arm windows pythons (#14374) --- .github/workflows/ci.yml | 96 ++- crates/uv-python/download-metadata.json | 1008 +++++++++++++---------- 2 files changed, 645 insertions(+), 459 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a0baa96c8..6c6d47ce2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -852,7 +852,7 @@ jobs: timeout-minutes: 10 needs: build-binary-windows-aarch64 name: "smoke test | windows aarch64" - runs-on: github-windows-11-aarch64-4 + runs-on: windows-11-arm steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -1000,6 +1000,96 @@ jobs: ./uv run python -c "" ./uv run -p 3.13t python -c "" + integration-test-windows-aarch64-implicit: + timeout-minutes: 10 + needs: build-binary-windows-aarch64 + name: "integration test | aarch64 windows implicit" + runs-on: windows-11-arm + + steps: + - name: "Download binary" + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: uv-windows-aarch64-${{ github.sha }} + + - name: "Install Python via uv (implicitly select x64)" + run: | + ./uv python install -v 3.13 + + - name: "Create a virtual environment (stdlib)" + run: | + & (./uv python find 3.13) -m venv .venv + + - name: "Check version (stdlib)" + run: | + .venv/Scripts/python --version + + - name: "Create a virtual environment (uv)" + run: | + ./uv venv -p 3.13 --managed-python + + - name: "Check version (uv)" + run: | + .venv/Scripts/python --version + + - name: "Check is x64" + run: | + .venv/Scripts/python -c "import sys; exit(1) if 'AMD64' not in sys.version else exit(0)" + + - name: "Check install" + run: | + ./uv pip install -v anyio + + - name: "Check uv run" + run: | + ./uv run python -c "" + ./uv run -p 3.13 python -c "" + + integration-test-windows-aarch64-explicit: + timeout-minutes: 10 + needs: build-binary-windows-aarch64 + name: "integration test | aarch64 windows explicit" + runs-on: windows-11-arm + + steps: + - name: "Download binary" + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: uv-windows-aarch64-${{ github.sha }} + + - name: "Install Python via uv (explicitly select aarch64)" + run: | + ./uv python install -v cpython-3.13-windows-aarch64-none + + - name: "Create a virtual environment (stdlib)" + run: | + & (./uv python find 3.13) -m venv .venv + + - name: "Check version (stdlib)" + run: | + .venv/Scripts/python --version + + - name: "Create a virtual environment (uv)" + run: | + ./uv venv -p 3.13 --managed-python + + - name: "Check version (uv)" + run: | + .venv/Scripts/python --version + + - name: "Check is NOT x64" + run: | + .venv/Scripts/python -c "import sys; exit(1) if 'AMD64' in sys.version else exit(0)" + + - name: "Check install" + run: | + ./uv pip install -v anyio + + - name: "Check uv run" + run: | + ./uv run python -c "" + ./uv run -p 3.13 python -c "" + integration-test-pypy-linux: timeout-minutes: 10 needs: build-binary-linux-libc @@ -2072,7 +2162,7 @@ jobs: timeout-minutes: 10 needs: build-binary-windows-aarch64 name: "check system | x86-64 python3.13 on windows aarch64" - runs-on: github-windows-11-aarch64-4 + runs-on: windows-11-arm steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -2094,7 +2184,7 @@ jobs: timeout-minutes: 10 needs: build-binary-windows-aarch64 name: "check system | aarch64 python3.13 on windows aarch64" - runs-on: github-windows-11-aarch64-4 + runs-on: windows-11-arm steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 diff --git a/crates/uv-python/download-metadata.json b/crates/uv-python/download-metadata.json index 367efbd64..4035a6a48 100644 --- a/crates/uv-python/download-metadata.json +++ b/crates/uv-python/download-metadata.json @@ -11,8 +11,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "93e38ced4feb2fe93d342d75918a1fb107949ce8378b6cb16c0fc78ab0c24d20", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "56f9d76823f75e6853d23dc893dbe9c5e8b4d120ba1e568ceb62f7aff7285625", "variant": null }, "cpython-3.14.0b3-darwin-x86_64-none": { @@ -27,8 +27,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "ea08efdd3b518139ed8b42aa5416e96e01b254aa78b409c6701163f2210fb37b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "135c7119d5ce56c97171769f51fee3be0e6ebfdab13c34dc28af46dbf00dffd1", "variant": null }, "cpython-3.14.0b3-linux-aarch64-gnu": { @@ -43,8 +43,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "3444de5a35a41a162df647eaec4d758eca966c4243ee22d0d1f8597edf48e5b8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "046cfd0b5132d1ac441eaa81d20d4e71f113c0f4ca1831be35fc2581171a2e47", "variant": null }, "cpython-3.14.0b3-linux-armv7-gnueabi": { @@ -59,8 +59,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "ac1ce5cd8aa09f6584636e3bda2a14a8df46aaf435b73511cc0e57e237d77211", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "7922cceb24b1c7bbff5da8fa663ea3d75f0622cca862806d7e06080051d87919", "variant": null }, "cpython-3.14.0b3-linux-armv7-gnueabihf": { @@ -75,8 +75,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "9058c9d70434693f5d6fa052c28dc7d7850e77efee6d32495d9d2605421b656d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "4b3450a8ae7f19c682563f44105f9af946bd5504f585fc135f78b066c11290b4", "variant": null }, "cpython-3.14.0b3-linux-powerpc64le-gnu": { @@ -91,8 +91,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "ad7e0d08638de2af6c70c13990b65d1bf412c95c28063de3252b186b48e5f29f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "34c66ea418df6bff2f144a8317a8940827bf5545bb41e21ebf4ae991bc0d0fa7", "variant": null }, "cpython-3.14.0b3-linux-riscv64-gnu": { @@ -107,8 +107,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "e95f10b21b149dc8fa59198d4cc0ff3720ec814aed9d03f33932e31cf8c17bf8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "952a785c85ae9b19ef5933bc406ed2ae622f535862f31f1d9d83579ebd7f1ab5", "variant": null }, "cpython-3.14.0b3-linux-s390x-gnu": { @@ -123,8 +123,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "7887516bf789785ce5cb7fa2eb2b4cffc1c454efe89a8eacfa6c66b2e054c9f8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "09a6f67c1c8c1a056ac873c99b8dc9b393013fc06bee46b9a4f841686ac39948", "variant": null }, "cpython-3.14.0b3-linux-x86_64-gnu": { @@ -139,8 +139,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "014a2e7c96957cc3ac3925b8b1c06e6068cab3b20faf5f9f329f7e8d95d41bfd", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "771f78fb170b706c769742712143c6e3d0ed23e6b906bfd6e0bd7e5f253a8c15", "variant": null }, "cpython-3.14.0b3-linux-x86_64-musl": { @@ -155,8 +155,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "72e15bb93e93cc4abcdd8ed44a666d12267ecd048d6e3b1bfeda3cc08187e659", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "b5d645032acef89e5a112746bd0d7db5da2c04c72eebb5b7ca2dcd627c66502f", "variant": null }, "cpython-3.14.0b3-linux-x86_64_v2-gnu": { @@ -171,8 +171,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "fff629607727d50f04deca03056259f9aee607969fb2bf7db969587e53853302", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "bba4f53b8f9d93957bacecceb70e39199fe235836d5f7898f10c061fe4f21a19", "variant": null }, "cpython-3.14.0b3-linux-x86_64_v2-musl": { @@ -187,8 +187,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "acb6aeebf0c6bc6903600ce99d112754cc830a89224d0d63ef3c5c107f01685c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "7692fedeff8c378c2fd046204296d4e9c921151176bcae80bff51d9cff6217d6", "variant": null }, "cpython-3.14.0b3-linux-x86_64_v3-gnu": { @@ -203,8 +203,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "df49f1a8e5c5aefc28ebc169fff77047878d0ae7fb7bf00221e10fc59f67f5fc", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "7d220528664f7b86555fed946ce29e9522db63b49f7c952c7df9bdee0a002466", "variant": null }, "cpython-3.14.0b3-linux-x86_64_v3-musl": { @@ -219,8 +219,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "d4672c060c5d8963af6355daaca40bb53938eee47a7346cab0259abff8e4f724", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "8188269487389fe6b1c441593257e5bd8bf6b8877a27ea0388d1a0282a2206f9", "variant": null }, "cpython-3.14.0b3-linux-x86_64_v4-gnu": { @@ -235,8 +235,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "925a47c3385ed2e2d498cd8f7a0a9434242b4b42d80ba45149d35da34bc9953b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "da2699921d0b1af5527fbe7484534a5afbc40bfd50b2b1d4c296ced849ae3c3f", "variant": null }, "cpython-3.14.0b3-linux-x86_64_v4-musl": { @@ -251,8 +251,24 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "3e3b57bd9b3e821bc26107afaa29457ef8e96a50c8254ca89796feb9386928a9", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "059cab36f0a7fc71d3bf401ba73b41dc2df40b8f593e46c983cc5f2cd531ad19", + "variant": null + }, + "cpython-3.14.0b3-windows-aarch64-none": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "windows", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "fa9c906275b89bf2068945394edb75be580ba8a3a868d647bcde2a3b958cf7ad", "variant": null }, "cpython-3.14.0b3-windows-i686-none": { @@ -267,8 +283,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "9009cd7a90b1ab3e7325278fbaa5363f1c160c92edef05be5c9d0a5c67ede59e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "e1d5ae8b19492bd7520dd5d377740afcfebe222b5dc52452a69b06678df02deb", "variant": null }, "cpython-3.14.0b3-windows-x86_64-none": { @@ -283,8 +299,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "b50dd04ffb2fdc051964cc733ed624c5ea7cae85ec51060b1b97a406dd00c176", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "d01bb3382f9d4e7e956914df3c9efef6817aaf8915afa3a51f965a41d32bf8de", "variant": null }, "cpython-3.14.0b3+freethreaded-darwin-aarch64-none": { @@ -299,8 +315,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "0ad27d76b4a5ebe3fac67bf928ea07bea5335fe6f0f33880277db73640a96df1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "475280f8fe3882e89cc4fa944a74dfaa5e4cfa5303b40803f4d782a08bf983a3", "variant": "freethreaded" }, "cpython-3.14.0b3+freethreaded-darwin-x86_64-none": { @@ -315,8 +331,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "26e5c3e51de17455ed4c7f2b81702b175cf230728e4fdd93b3c426d21df09df2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "79f93e3f673400764f00f5eb5f9d7c7c8ed20889a9a05f068b1cdc45141821c5", "variant": "freethreaded" }, "cpython-3.14.0b3+freethreaded-linux-aarch64-gnu": { @@ -331,8 +347,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-aarch64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "15abb894679fafa47e71b37fb722de526cad5a55b42998d9ba7201023299631b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-aarch64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "14ea3ce113f8eff97282c917c29dda2b2e9d9add07100b0e9b48377f1b5691cf", "variant": "freethreaded" }, "cpython-3.14.0b3+freethreaded-linux-armv7-gnueabi": { @@ -347,8 +363,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-armv7-unknown-linux-gnueabi-freethreaded%2Blto-full.tar.zst", - "sha256": "a2e1f3628beec26b88eb5d5139fcc95a296d163a5a02910337a6a309450e340f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-armv7-unknown-linux-gnueabi-freethreaded%2Blto-full.tar.zst", + "sha256": "57d60ebe0a930faf07c7d2a59eadf3d5e59cf663492d0cadb91b4a936ec77319", "variant": "freethreaded" }, "cpython-3.14.0b3+freethreaded-linux-armv7-gnueabihf": { @@ -363,8 +379,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-armv7-unknown-linux-gnueabihf-freethreaded%2Blto-full.tar.zst", - "sha256": "30896f01c54a99e34d7d91a893568c32f99c597ecba8767ab83f501b770a3831", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-armv7-unknown-linux-gnueabihf-freethreaded%2Blto-full.tar.zst", + "sha256": "966e2ec436d34cfb5c485c8a45029de8c7ab2501e97717a361c10bdf464e2287", "variant": "freethreaded" }, "cpython-3.14.0b3+freethreaded-linux-powerpc64le-gnu": { @@ -379,8 +395,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-ppc64le-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "2127b36c4b16da76a713fb4c2e8a6a2757a6d73a07a6ee4fa14d2a02633e0605", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-ppc64le-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "9a036e9fff85e35dc7d9488604675bd139434e34020738b6e61a904e47567a50", "variant": "freethreaded" }, "cpython-3.14.0b3+freethreaded-linux-riscv64-gnu": { @@ -395,8 +411,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-riscv64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "dca86f8df4d6a65a69e8deb65e60ed0b27a376f2d677ec9150138d4e3601f4f7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-riscv64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "875b77a12acf56f5b4349995271f387f0dd82e11c97b70d268070855b39b4bc4", "variant": "freethreaded" }, "cpython-3.14.0b3+freethreaded-linux-s390x-gnu": { @@ -411,8 +427,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-s390x-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "5f119f34846d6f150c8de3b8ce81418f5cf60f90b51fcc594cb54d6ab4db030d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-s390x-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "328fb2c96de92f38c69f5031ed0dd72e4d317593ac13dde0c1daadb2e554317b", "variant": "freethreaded" }, "cpython-3.14.0b3+freethreaded-linux-x86_64-gnu": { @@ -427,8 +443,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "ac5373d3b945298f34f1ebd5b03ce35ce92165638443ef65f8ca2d2eba07e39d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "fff6d39b2b03ea19fe191b1c64e67732b3f2d8f374bc6c6fd9bd94954d6287f2", "variant": "freethreaded" }, "cpython-3.14.0b3+freethreaded-linux-x86_64-musl": { @@ -443,8 +459,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "e08e0202777269916295cf570e78bfb160250f88c20bd6f27fd1a72dcb03c8b9", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "b227cf474974ea4a92bc2a633e288456403f564251bf07b1a1ae288e0fac5551", "variant": "freethreaded" }, "cpython-3.14.0b3+freethreaded-linux-x86_64_v2-gnu": { @@ -459,8 +475,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v2-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "5a36083ac0df9c72416a9cde665c6f86dfe78ebb348fc6a7b4b155ef0112fec9", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v2-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "b9b4a72b211d97cafb37b66cb4a918b72af24bb4d5b957a96e978cad22a5cd67", "variant": "freethreaded" }, "cpython-3.14.0b3+freethreaded-linux-x86_64_v2-musl": { @@ -475,8 +491,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v2-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "70297d1edad54eea78c8cd5174e21ab7e2ed2d754f512896ae0f4006517b101c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v2-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "f7898473617e67d3d6fd428a8c340ae340eb043d35e1d1584ed0497b3b933cb6", "variant": "freethreaded" }, "cpython-3.14.0b3+freethreaded-linux-x86_64_v3-gnu": { @@ -491,8 +507,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v3-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "8cce11843eae8c78f0045227f7637c451943406aa04c1dc693ca7bf4874b2cbd", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v3-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "7844c24cec06402fd6e437c62c347ea37ff685e2e1fda0fbc3076a70b2963296", "variant": "freethreaded" }, "cpython-3.14.0b3+freethreaded-linux-x86_64_v3-musl": { @@ -507,8 +523,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v3-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "8da62b2823fb28d8d34093b20ac0d55034a47715afa35ed3a0fab49f2cc74e49", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v3-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "f39381f98e18a06fdc35137471f267d6de018bf0f6209e993a769c494b3e7fcd", "variant": "freethreaded" }, "cpython-3.14.0b3+freethreaded-linux-x86_64_v4-gnu": { @@ -523,8 +539,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v4-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "ded53979b407e350ad4c9e87646f9521762c0769aa26d9450ba02ec6801961a2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v4-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "c19941d7c0d7002ebad3d13947ad0231215ed1d6a9e93c8318e71dbaf6a9bbdd", "variant": "freethreaded" }, "cpython-3.14.0b3+freethreaded-linux-x86_64_v4-musl": { @@ -539,8 +555,24 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v4-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "d90c08a393ee21fd9c4d3d7f4dcf5b14cb6251d3cb77df15cccc233e67fd85c1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v4-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "e7d6c051de697ed6f95a0560fde3eb79c20b2ea9ca94ef02c4f7ec406f813ec4", + "variant": "freethreaded" + }, + "cpython-3.14.0b3+freethreaded-windows-aarch64-none": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "windows", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-aarch64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "986456a0e936f332a78a4130ea1114684d10b42b92b92d93b63da974767ae426", "variant": "freethreaded" }, "cpython-3.14.0b3+freethreaded-windows-i686-none": { @@ -555,8 +587,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-i686-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", - "sha256": "97b59f1087c7f05c088a84998c0babf1b824358d28759e70c8090ec9a45e36aa", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-i686-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "dc630b0715cd1c7b23291715ce4fa62506ccd0578c4b186c5fd0a92a34cc6f0e", "variant": "freethreaded" }, "cpython-3.14.0b3+freethreaded-windows-x86_64-none": { @@ -571,8 +603,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", - "sha256": "5c864084d8b8d5b5e9d4d5005f92ec2f7bdb65c13bc9b95a9ac52b2bcb4db8e0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "d136ddc9fcf1f27e5791dd3dd5edf755830f1eef65066af749760f350cea41c8", "variant": "freethreaded" }, "cpython-3.14.0b3+debug-linux-aarch64-gnu": { @@ -587,8 +619,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "6ad6bcadb2c1b862f3dd8c3a56c90eac2349a54dcf14628385d0d3bf5e993173", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "9bef0ad9e9c8c477c5a632074cb34d8ab3c25acaff95d7d7052dbabb952459c7", "variant": "debug" }, "cpython-3.14.0b3+debug-linux-armv7-gnueabi": { @@ -603,8 +635,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "bd88ac4f905609bc5ee3ec6fc9d3482ce16f05b14052946aec3ed7f9ba8f83d2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "28324fddb6699ce11ae62dc6458e963c44e82f045550ced84ad383b12a810472", "variant": "debug" }, "cpython-3.14.0b3+debug-linux-armv7-gnueabihf": { @@ -619,8 +651,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "4d94559a5ae00bf849f5204a2f66eee119dd979cc0da8089edd1b57bce5a165f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "74e896ddc8ec4b34f2f5c274132a96bb191b1524bc1627929084bec64ce2db62", "variant": "debug" }, "cpython-3.14.0b3+debug-linux-powerpc64le-gnu": { @@ -635,8 +667,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "11a6c51fe6c0e9dfc9fdd7439151b34f5e4896f82f1894fd1aee32052e5ca4d2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "127450fe5cb01a313870aa32d97740b0a5b3eaac2119a1590cabf66409fb31a4", "variant": "debug" }, "cpython-3.14.0b3+debug-linux-riscv64-gnu": { @@ -651,8 +683,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "4e84e69007d047e1f6a76ea894a8b919e82e7f86860525eb3a42a9cb30ce7044", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "7289b088a22731a0530c25395dd0617fe3d21fddc0c806206daa35b33d16f4ac", "variant": "debug" }, "cpython-3.14.0b3+debug-linux-s390x-gnu": { @@ -667,8 +699,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "f2f8dde10c119bbb3313a7ba4db59dd09124e283f2a65217f50504d18e9c511e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "356b05d8d5c612bbd29acaed04dc3c54d8ea42af0d31608370b04cce5093c4e7", "variant": "debug" }, "cpython-3.14.0b3+debug-linux-x86_64-gnu": { @@ -683,8 +715,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "26b5ad31f9902f271bc5e3e886975d242d4155ea43968e695214147b6f0758a3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "3092e467a21a4f00d030f0ac3efb97e49939debe3c00ed8c80cfd25c0db238f7", "variant": "debug" }, "cpython-3.14.0b3+debug-linux-x86_64-musl": { @@ -699,8 +731,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "6c608708569a018db38e888410c7871b00ed9b5caa3dabf19ddc51e64e2200ab", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "d8391e345823d2eaf0745ddb77982a35c032674e1d08ec29321568c21737a15e", "variant": "debug" }, "cpython-3.14.0b3+debug-linux-x86_64_v2-gnu": { @@ -715,8 +747,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "2499487ea8a5aa28c3ae5e9073e9cb7b14fdf45722d58db32ba20107a8ff4657", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "3f497ae10d574532c0a79b4704ba7944ee41586e86997ec1933c99efdfe19ec3", "variant": "debug" }, "cpython-3.14.0b3+debug-linux-x86_64_v2-musl": { @@ -731,8 +763,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "88233f533101941f03aef44bb58e44c6e9a2f7802ae85294ff3804472136d363", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "da154e3008bc4bd4185993bc04b355b79fbcd63a900b049d4595ea3615aef76c", "variant": "debug" }, "cpython-3.14.0b3+debug-linux-x86_64_v3-gnu": { @@ -747,8 +779,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "edd1800dd1d2abc38db383d7ff61bb21597f608a64ab44cc23f009af0291a96c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "a64d5b16c684700b58cff51164c0faca6dc4de3dc6509ea958f975217fb1f804", "variant": "debug" }, "cpython-3.14.0b3+debug-linux-x86_64_v3-musl": { @@ -763,8 +795,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "6ceeb66c4876a7484b8ba9847e590d74ca35b67cbe692d06b3d3466a483406f8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "ced306fbaf48996b422c5e45125de61578fe1a7b3f458bd4173f462b6b691fd2", "variant": "debug" }, "cpython-3.14.0b3+debug-linux-x86_64_v4-gnu": { @@ -779,8 +811,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "2f4d7ced306f9d4c800e9d7a86183c5f92c7858d8f64e38afd73db45414cbb82", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "68a7b564d38fafd2c0b864adcdc3595830d58714093951a5412ad72c021b302a", "variant": "debug" }, "cpython-3.14.0b3+debug-linux-x86_64_v4-musl": { @@ -795,8 +827,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.14.0b3%2B20250626-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "40191a40700aa65c0f92fcea8cd63de1d840ca5913c11b0226e677e1617dbf5d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "7213fcbc7628c5a15cf3fb1c8bff43d0b8a5473fd86cf46941f7ee00f2b9d136", "variant": "debug" }, "cpython-3.14.0b2-darwin-aarch64-none": { @@ -5531,8 +5563,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "2303b54d2780aeac8454b000515ea37c1ce9391dd0719bbf4f9aad453c4460fc", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "a36fb4e711c2c7987d541aa5682db80fc7e7b72205bae6c330bf992d23db576f", "variant": null }, "cpython-3.13.5-darwin-x86_64-none": { @@ -5547,8 +5579,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "baf14de314f191fd168fae778796c64e40667b28b8c78ae8b2bc340552e88a9a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "81b51eec47af8cd63d7fbcc809bd0ae3e07966a549620b159598525788961fdc", "variant": null }, "cpython-3.13.5-linux-aarch64-gnu": { @@ -5563,8 +5595,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "bef5e63e7c8c70c2336525ffd6758da801942127ce9f6c7c378fecc4ed09716d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "906ffb000e53469921b0c3cfbcdab88e7fbf8a29cd48dec8cb9a9b8146947e1d", "variant": null }, "cpython-3.13.5-linux-armv7-gnueabi": { @@ -5579,8 +5611,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "ffa3f4b9a45bfac9e4ba045d2419354fccd2e716ffa191eccf0ec0d57af7ad8d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "5cae766871606d74466e54fe8260ce81d74e1d545eb78da8d4986a910d8c418a", "variant": null }, "cpython-3.13.5-linux-armv7-gnueabihf": { @@ -5595,8 +5627,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "f466eef0279076db5d929081dd287f98c7904fc713dd833a2ba6df4250a3b23e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "50e3cd7c633991c1b1d48a00a708ff002437f96e4b8400f93f97e237b2777208", "variant": null }, "cpython-3.13.5-linux-powerpc64le-gnu": { @@ -5611,8 +5643,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "3a1a91c3ac60188e27d87fb3487da1dd452bff984c9ed09c419d38c3c373eea7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "5f2b7dbecf27e21afa5c3a6e203be0bd36a325b422b12bc3526de475e2566c3f", "variant": null }, "cpython-3.13.5-linux-riscv64-gnu": { @@ -5627,8 +5659,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "8b5b76554004afec79469408f3facaa0407fac953c1e39badcd69fb4cbe9e7e3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "713da45173f786f38e22f56d49b495647d93713dd9a0ec8bd8aa1f9e8e87ac1e", "variant": null }, "cpython-3.13.5-linux-s390x-gnu": { @@ -5643,8 +5675,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "9da68c953563b4ff3bf21a014350d76a20da5c5778ed433216f8c2ebc8bfd12b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "c016223dcbfb5f3d686db67ba6d489426de21076d356640dcb0f8bf002573b43", "variant": null }, "cpython-3.13.5-linux-x86_64-gnu": { @@ -5659,8 +5691,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "1894edce20e8df3739d2136368a5c9f38f456f69c9ee4401c01fff4477e2934d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "3057ed944be60b0d636aebfdde12dedb62c6554f33b5b50b8806dbc15a502324", "variant": null }, "cpython-3.13.5-linux-x86_64-musl": { @@ -5675,8 +5707,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "8f50133eda9061154eb82a8807cb41ec64d2e17b429ddfcd1c90062e22d442fa", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "e26d9d405c2f224ac6d989b82d99fd1e3468d2dfaf288ac5041f743fdea327c0", "variant": null }, "cpython-3.13.5-linux-x86_64_v2-gnu": { @@ -5691,8 +5723,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "bf77b2a07d8c91b27befc58de9e512340ccf7ee4a365c3cde6a59e28de8b3343", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "3041155088ef88fd28981f268de5b47d243ac9a2cffa3582fc4c1a6c633bb653", "variant": null }, "cpython-3.13.5-linux-x86_64_v2-musl": { @@ -5707,8 +5739,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "0e3347d77aa1fc36dd5b7a9dfc29968660c305bd2bb763af4abfab6a7f67a80d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "1ec20ff2606e5510b30e0cb56ebe06b806b08c13712007d7550d26a35bddc9bd", "variant": null }, "cpython-3.13.5-linux-x86_64_v3-gnu": { @@ -5723,8 +5755,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "75347d44b5f7cf8c0642cb43b77797b15e6346633bc17c0fb47f7968d7221fa9", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "e62614725c2b31060063a5a54f24d01b90cd0f2418c6ff14c61cfad0f81cfefd", "variant": null }, "cpython-3.13.5-linux-x86_64_v3-musl": { @@ -5739,8 +5771,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "9e4191282a518d02db5d7ce5f42d7e96393768243f55e6a6a96e9c50f0cc72fa", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "9a02ef405e8d811c4cf703aa237c428f9460b4857465f6b4a5c23fce4948e887", "variant": null }, "cpython-3.13.5-linux-x86_64_v4-gnu": { @@ -5755,8 +5787,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "b841a8f77bedde2882f9bc65393939a49142c599b465c12c1bdd86dde52d6fc8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "f06da893a220d6e97f845292c7623743b79167047c0d97ead6b2baa927d9d26b", "variant": null }, "cpython-3.13.5-linux-x86_64_v4-musl": { @@ -5771,8 +5803,24 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "446fb13abdcbf8a329074f9bfb4e9803b292a54f6252948edd08d5cf9c973289", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "a16a169ac9675e82ab190528e93cfbc7ab86f446485748afe1138010753eebd4", + "variant": null + }, + "cpython-3.13.5-windows-aarch64-none": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "windows", + "libc": "none", + "major": 3, + "minor": 13, + "patch": 5, + "prerelease": "", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "bc82935e2102d265b298fb1618930b36930fc1820d8dc2f6e3c2e34767917f44", "variant": null }, "cpython-3.13.5-windows-i686-none": { @@ -5787,8 +5835,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "c458f8200dd040af36d087791c48efa239160de641b7fda8d9c1fc6536a5e4a4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "7ffe48c8242d24b029ae880a0651076ec3eb9b2a23a71f9ad170bee37cc40f42", "variant": null }, "cpython-3.13.5-windows-x86_64-none": { @@ -5803,8 +5851,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "ab9b81bb06a51791040f6a7e9bffa62eec7ae60041d3cf7409ee7431f2237c7b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "5f7b96c86d4bea7a823d0fbce8e139a39acad3560bb0b8ffaae236c0bf8251f9", "variant": null }, "cpython-3.13.5+freethreaded-darwin-aarch64-none": { @@ -5819,8 +5867,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "7223a0e13d5e290fa8441b5439d08fca6fe389bcc186f918f2edd808027dcd08", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "71772559da8e7155ae34ee9e625126bb76df42d66184b533f82c74b7acd0b9f0", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-darwin-x86_64-none": { @@ -5835,8 +5883,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "869ca9d095f9e8f50fc8609d55d6a937c48a7d0b09e7ab5a3679307f9eb90c70", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "e31c161b78c141e9fde65e7a37ac0e944ac2f8fb0e001b49244deae824ed0343", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-aarch64-gnu": { @@ -5851,8 +5899,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-aarch64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "8437225a6066e9f57a2ce631a73eceedffeadfe4146b7861e6ace5647a0472da", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-aarch64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "578fcc7c2f8e9816ccd7302a5f06f3b295e1059249f594b70a15eb6b744405e9", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-armv7-gnueabi": { @@ -5867,8 +5915,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-armv7-unknown-linux-gnueabi-freethreaded%2Blto-full.tar.zst", - "sha256": "eabb70dff454bf923a728853c840ee318bc5a0061d988900f225de5b1eb4058b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-armv7-unknown-linux-gnueabi-freethreaded%2Blto-full.tar.zst", + "sha256": "60be2a6390408fe4ff1def8b5841240749f6e036be0719aa4120dc6412657467", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-armv7-gnueabihf": { @@ -5883,8 +5931,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-armv7-unknown-linux-gnueabihf-freethreaded%2Blto-full.tar.zst", - "sha256": "aa49a863343cbbae5a7a1104adb11e9d1d26843598eb5ba9e3db135d27df721a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-armv7-unknown-linux-gnueabihf-freethreaded%2Blto-full.tar.zst", + "sha256": "e477210f0b1c66e3811433bb1d45d7336e9e0bb52f4ec2a4d6a789dbdd9de9c2", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-powerpc64le-gnu": { @@ -5899,8 +5947,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-ppc64le-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "09008067d69b833b831cc6090edb221f1cce780c4586db8231dcfb988d1b7571", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-ppc64le-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "4d00d210cf083c83950e5af59df044c34538c15f823a6b07c667df2479bbcb21", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-riscv64-gnu": { @@ -5915,8 +5963,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-riscv64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "3bc92a4057557e9a9f7e8bd8e673dfae54f9abbd14217ae4d986ba29c8c1e761", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-riscv64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "63408df03d6d6ec22a9d4e01701a1519247ceb35804161d64f0aed0d73154087", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-s390x-gnu": { @@ -5931,8 +5979,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-s390x-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "3206aa76d604d87222ef1cd069b4c7428b3a8f991580504ae21f0926c53a97c5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-s390x-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "af58974a23754b02301043d6e810fb3ce533d1c34c23d197a6690f3eb2f00fe1", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64-gnu": { @@ -5947,8 +5995,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "a45ffc5a812c3b6db1dce34fc72c35fb3c791075c4602d0fb742c889bc6bf26d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "976133de595393596207fa3c943d4e8e43ddb5d51b864766fc666a1752963e04", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64-musl": { @@ -5963,8 +6011,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "c0199c9816dea06a4bc6c5f970d4909660302acb62639a54da7b5d6a4bb45106", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "f11c199765a4c96e93bd5ffc65477dc7eb4c4edd03f20ecf4d51ea0afc3be0c2", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v2-gnu": { @@ -5979,8 +6027,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v2-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "5ab68785f2b5098e5e984bc49b9df1530bee0097dddcd4fe5f197e48d3e619f2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v2-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "ff8066512111ac80962bdd094043f600eb9dbdd35e93f059f5ab34f9c4ff412e", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v2-musl": { @@ -5995,8 +6043,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v2-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "b91e0b88eb9295fae197f870f2291a3bd1d47758f2aabc8c2e1996af1b14f180", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v2-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "26ab90c03672187ae4a330e59bf5142cbe7ed24e576edfd3b869fa108a3b3fc7", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v3-gnu": { @@ -6011,8 +6059,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v3-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "91bdb8c0792ef29cef987b3198e18f9c441794b33f1266c9155530ddfcfa8b3a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v3-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "15e614a96bf2d789500c9bee03c7330e61278c1b9b4f3138f38bfa05bbe4bd68", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v3-musl": { @@ -6027,8 +6075,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v3-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "01191f5268159e7448a320703c40508802baa1780e855771311f01c550d80b58", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v3-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "448f46715607f34cc217ce2a51d0782417d99077b5ecd8330d2c53b74e893a0c", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v4-gnu": { @@ -6043,8 +6091,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v4-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "ebcd45022331fe1ebdee98a3b16c876d8be147079206fa3ccc4d81b89fd7ac8b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v4-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "6620f3bbd38732ce232d84abbad1836400a680a1208916af502bbeebc8ae825d", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v4-musl": { @@ -6059,8 +6107,24 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v4-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "d30c596a8116a92f860d38b5d5a11e6dd5dea2bbbcdf7439e3223e357298b838", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v4-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "895e650967c266182f64c8f3a3fd395d336bbd95218fd0c7affc82f3c9465899", + "variant": "freethreaded" + }, + "cpython-3.13.5+freethreaded-windows-aarch64-none": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "windows", + "libc": "none", + "major": 3, + "minor": 13, + "patch": 5, + "prerelease": "", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-aarch64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "4dc24bdf390356281dd23a4644565e7f49187894015b8292c0257a0f5cd2fd43", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-windows-i686-none": { @@ -6075,8 +6139,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-i686-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", - "sha256": "d1d421904aa75fce2fb5cb59bacb83787fbe7fbc68dc355c7fdbd19094030473", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-i686-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "009f56db3de1351dd616fb1fd8b04343637dd84b01c1eb37af8559fa87c6b423", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-windows-x86_64-none": { @@ -6091,8 +6155,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", - "sha256": "79c5594d758c7db8323abc23325e17955a6c6e300fec04abdeecf29632de1e34", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "7387f13148676858b9ab61ff7f03b82b88d81d0342346d1a156233b9478f3c81", "variant": "freethreaded" }, "cpython-3.13.5+debug-linux-aarch64-gnu": { @@ -6107,8 +6171,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "bbc5dab19b3444b8056e717babc81d948b4a45b8f1a6e25d1c193fcaf572bf25", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "0698a989ead503f3181118a8c2793b431707190a1ef82fe2a5386a268ebe58d6", "variant": "debug" }, "cpython-3.13.5+debug-linux-armv7-gnueabi": { @@ -6123,8 +6187,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "2be85ab8daa6c898292789d7fe83d939090a44681b0424789fba278a7dded5fd", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "0a5d4cec664ede542ad92115f2c90318bed8020d093ec98a9cf935374d0b684e", "variant": "debug" }, "cpython-3.13.5+debug-linux-armv7-gnueabihf": { @@ -6139,8 +6203,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "372ca41f12f67a44d0aedd28714daade9d35b4c14157ea4cdd5b12eea3f5ddf8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "6be78a4c8df0a4ee420e2a51b6f5581d7edf960b38bcf38c1a4f3ecee018b248", "variant": "debug" }, "cpython-3.13.5+debug-linux-powerpc64le-gnu": { @@ -6155,8 +6219,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "0cb23ab284eab18f471716f4aa1ba4ee570a75a6d79fa5cd091264c514e54a91", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "bba8b2e8c0a6095460c519c273d7e101c616c96e2992407301018d00a5d7b686", "variant": "debug" }, "cpython-3.13.5+debug-linux-riscv64-gnu": { @@ -6171,8 +6235,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "315184f457d26e6e20cf69ec169f3fdfdd232e6766a29905cbb6044501fcd4e5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "10be77859e5f99a83c07e4a521776d2a8860dd46ce8a46c1e3c2544d2593702a", "variant": "debug" }, "cpython-3.13.5+debug-linux-s390x-gnu": { @@ -6187,8 +6251,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "8bc2ca17f35f14b6f07882e75c7034263e23fbe003e275e068f2d84e823ed2bf", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "05e668c47ba1c75f0df0c01b6afb3af39ae9d17a90d02f7f6508c094de79aa3c", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64-gnu": { @@ -6203,8 +6267,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "994e77601b9c4f9f48000f575e1d1c640b8b3d795fb65b6b4656b7664df2f95c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "5d8f83f26f8f203eb888ae42e02d6f85c26ac0306d65ad311e79ca2120095ce1", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64-musl": { @@ -6219,8 +6283,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "50027e1a8a82d7924993b49941c6c6fdeeee282cb31807021c44459637b1ca1e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "70705fd017908822cbfad83b444f4fc46301467f13e17c97de8e456ee9f08173", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v2-gnu": { @@ -6235,8 +6299,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "e9c3d4fdaabe465969533d0129966957088a609814f5f909e25b1397d4fbe960", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "064e7eb23cce3b5ceb22d974d14c80a0ffbe309178d60d56ee910deda69c3bb2", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v2-musl": { @@ -6251,8 +6315,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "1e16597f1d1a72a9ffde00d9d76eda5fdd269d3c69d40b1ab1ccb0ee2e2aafcd", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "f875a778f5c1f4a3f8c0c37ebdd2437eea9abcfa3eff4d161ee8d761d3018892", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v3-gnu": { @@ -6267,8 +6331,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "ab9f5380fe0f3d34bb6c02d29103adf2d3b21f47a5f08613fe4f1d7b69d708b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "7024d12c2241595cb3c66a0c291d416cc431198aa01d8c2d642fad3266b4d2c8", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v3-musl": { @@ -6283,8 +6347,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "11d126eb8cf367b904982d3b7581bd9bf77630774a1c68bb3290c2372f01360c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "36ce0d9ca8163798c83d027d0abee2ed78ae0a91df96346ed5b7c26700131f38", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v4-gnu": { @@ -6299,8 +6363,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "8f0bf5fdfd697d5fdd85f9beca4937e9cf93e213d27f46d782d35c30ca2876f6", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "31c2c46f031fd32b2a8955a9193365f8e56f06e4b598b72d34441b4461ca55e7", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v4-musl": { @@ -6315,8 +6379,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.13.5%2B20250626-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "6fd687f89251c13df002d6bff0f57cbe199f724679df3b8de9bbabafb735d47b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "1129ba59de94be77f3ed9d4fccb5f48f9f297600b71aeea79d20f170f84b4323", "variant": "debug" }, "cpython-3.13.4-darwin-aarch64-none": { @@ -10539,8 +10603,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "4919401e556a2720e2fd75635485ef5a6bb4caedcaa228b49acdd060c1b89f4e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "52f103b1bae4105c8c3ba415fbfb992fc80672795c58262b96719efde97c49d9", "variant": null }, "cpython-3.12.11-darwin-x86_64-none": { @@ -10555,8 +10619,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "4d5d672de6a6f2048f875ea0e6b02c9a4a0248d3d57271c740a1b877442552a1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "5e7229d72ec588b77f753ee043fcefe62f89190e84b8b43d20b1be4b4e6e8540", "variant": null }, "cpython-3.12.11-linux-aarch64-gnu": { @@ -10571,8 +10635,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "e6797c50c9ce77904e63b8c15cc97a9ff459006bea726bf2ce41ba2ef317d4af", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "117b1d2daa96486005827dd50029fecd61e99141396583ea8e57733f7cf0abad", "variant": null }, "cpython-3.12.11-linux-armv7-gnueabi": { @@ -10587,8 +10651,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "60c8843e9e70610f3d82f26b0ba395312382d36e3078d141896ab4aabd422be8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "878d29bab42a2911927948636a1bdd1e83ac1d31dbe4b857a214b04eed2046de", "variant": null }, "cpython-3.12.11-linux-armv7-gnueabihf": { @@ -10603,8 +10667,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "d97c4bcb8514bf2f7b202e018a2b99e9a4ab797bf1b0c31990641fe9652cae2e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "8a3ae17b0ecaa0334ca1bbd3d2cc7ea16d43ead269c03cdaedee54bfd6949962", "variant": null }, "cpython-3.12.11-linux-powerpc64le-gnu": { @@ -10619,8 +10683,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "bebc3aa45ff2d228f7378faedf2556a681e9c626b8e237d39cf4eb6552438242", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "bb0689d5101a4a85eaca7e8443912d196519103695937fb8e4fc819d66e48167", "variant": null }, "cpython-3.12.11-linux-riscv64-gnu": { @@ -10635,8 +10699,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "e82a07c281bef00d14a6bf65b644a4758ee0c8105e0aa549c49471cc4656046f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "59323d58c55f3afc90d93d185f57bdfc9bbfcf1e713290df6eda91fbabf91a90", "variant": null }, "cpython-3.12.11-linux-s390x-gnu": { @@ -10651,8 +10715,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "ccd091fd9a8cc1a57da15d1e8ae523b6363c2ce9a0599ac1b63f5c8b70767268", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "925c52b00a55b52492c197ebc7499317e4403cf603f1670a6e655b9426d286f2", "variant": null }, "cpython-3.12.11-linux-x86_64-gnu": { @@ -10667,8 +10731,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "4ab7c1866d0b072b75d5d7b010f73e6539309c611ecad55852411fc8b0b44541", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "358ad7bf46260a2e1b31726adc62dc04258c7ea7469352e155ffdea0f168499e", "variant": null }, "cpython-3.12.11-linux-x86_64-musl": { @@ -10683,8 +10747,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "2fe4faa95b605a4616c3c3d986fb8ce9a31c16a68b6e77f56c68468a87dff29e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "63bcf41a095c298d412efdb9ceba0009e8544809fe0fa9b1ba51dc918f3dc3ee", "variant": null }, "cpython-3.12.11-linux-x86_64_v2-gnu": { @@ -10699,8 +10763,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "531c60132607239f677d6bb91d33645cd7564f1f563e43fff889c67589d6e092", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "49de0e676faa6e72a988ebe41d67cd433d074ab4fd3b555935d0d4f7437f7168", "variant": null }, "cpython-3.12.11-linux-x86_64_v2-musl": { @@ -10715,8 +10779,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "c2a86e3077ccda859a8669896a4c41ea167c03b105c4307523e6e0b0bc820c25", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "66f322f7eb520a441ef341b166e9cbd45df83e5841fd4998181f431f719f5a8c", "variant": null }, "cpython-3.12.11-linux-x86_64_v3-gnu": { @@ -10731,8 +10795,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "07015ad91ec3ee89c410b09bae84a5910037766783ae47bfb09469f80e1f2337", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "be9ba63f7905b925d958aebcb8fcec40a2ba94552e2bd3d91818d97fc3d68ecb", "variant": null }, "cpython-3.12.11-linux-x86_64_v3-musl": { @@ -10747,8 +10811,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "891ba6c18dd688cf7b633ff2bb40aecf007d439c6732b2a9b8de78ffbb0b9421", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "598661a23fd5c0f2870c99194938732a3e1a4909e41b8a54401b821f1594e66b", "variant": null }, "cpython-3.12.11-linux-x86_64_v4-gnu": { @@ -10763,8 +10827,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "b7f5456cb566bf05bf1f1818eb2dda4f76a3c6257697f31d01ada063ec324f96", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "b76430b697fa0c1ef1b2b04361decf36b7108da719c48ca2098e6aa0cd9a7130", "variant": null }, "cpython-3.12.11-linux-x86_64_v4-musl": { @@ -10779,8 +10843,24 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "55f5121910e7c9229890e59407dc447807bee7173dc979af7cab8ea6ddd36881", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "88121676a946fe506d24821fc89439bc79a89a4eea9ffb5c903e9d7bc1c427d3", + "variant": null + }, + "cpython-3.12.11-windows-aarch64-none": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "windows", + "libc": "none", + "major": 3, + "minor": 12, + "patch": 11, + "prerelease": "", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "45e121ad8e82eb9204cc1da53d0fdf125923f7caed18bf282b3c00dcfae89486", "variant": null }, "cpython-3.12.11-windows-i686-none": { @@ -10795,8 +10875,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "9ddbc2d11445007c8c44ecc8fb23b60c19143e7ff2bc99f9a6d7ab19cf18825e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "d80c67dd88aca4c94a57a8b2fe63de524a9e1f2308e5ecd2ca83a7961b9b9b17", "variant": null }, "cpython-3.12.11-windows-x86_64-none": { @@ -10811,8 +10891,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "429e1cc78f4b7591b4ec2063d334dd0bb85afb41e246af1e2b919acdf99fc1b0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "91f7196d5d103eb3d1214f74606144adb326f0770d80c636cb11b96670477736", "variant": null }, "cpython-3.12.11+debug-linux-aarch64-gnu": { @@ -10827,8 +10907,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "bc8c87d783ae3685df5a0954da4de6513cd0e82135c115c7d472adbabb9c992d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "70a49e31e1f15036715e67ec08ef63df321171c7b922f16db9b462ed9d1ccbc6", "variant": "debug" }, "cpython-3.12.11+debug-linux-armv7-gnueabi": { @@ -10843,8 +10923,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "40f70903123cf16fb677190388f1a63e45458c2b2ae106c8ddb3ef32ace4c8d1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "87f27b3efb66a226f7a03029f14cbabdc512974f8489b003cb848dec3fde869c", "variant": "debug" }, "cpython-3.12.11+debug-linux-armv7-gnueabihf": { @@ -10859,8 +10939,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "fec4a3348980ecbae93e241946bd825f57f26b5a1b99cc56ee4e6d4a3dd53f3c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "28a431937f2b5467a0e74425d64bf27ca2e0c37681e3e93f71c19386cf457237", "variant": "debug" }, "cpython-3.12.11+debug-linux-powerpc64le-gnu": { @@ -10875,8 +10955,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "dea804e42c48de02e12fda2726dea59aa05091c792a2defe0c42637658711c46", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "e08f6f7faabda320f0a38a3388056f29641d7375a6247a6aebf885907a3baf2b", "variant": "debug" }, "cpython-3.12.11+debug-linux-riscv64-gnu": { @@ -10891,8 +10971,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "128949afd8c2ead23abf0210faa20cbd46408e66bba1cc8950b4fcdb54cea8fa", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "3537f9e61a46d50bc500310c0056a7b99d08a09b5ba29142c783d226a7712c4e", "variant": "debug" }, "cpython-3.12.11+debug-linux-s390x-gnu": { @@ -10907,8 +10987,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "b025e7f6acad441a17394d0fc24f696717dd9ec93718000379ca376a02d7c4a6", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "859d643152c2b86621a6aa9cfb9d1f50654b402e852ce9cdc864a2f07652c4f7", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64-gnu": { @@ -10923,8 +11003,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "99ab13c789dffdf0b43e0c206fd69c73a6713527bf74c984c7e99bab8362ab45", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "59aaff170ce2a6efe4a34e5ed50db83c4e29951e3c83ac7fa031f4b8247a440e", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64-musl": { @@ -10939,8 +11019,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "53e842d33cc5a61ee2e954a431ea100a59fa5ae53e355d5f34c00c1555a810ce", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "9b43f05f37cd39ff45021d8a13b219cb6ad970130c1c32463cd563002aaff9a7", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v2-gnu": { @@ -10955,8 +11035,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "790b88b4acfc9f58ce1355aba4c73999adf733ac7f8ef87b92b4b092003a0f05", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "37ba61db27c6a1f9d540db6404ea2dfe1f0a221d08332b35476e2d88dd517a0c", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v2-musl": { @@ -10971,8 +11051,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "88e92e303d1f61f232f15fe3b266abf49981430d4082451dfda3b0c16900f394", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "1c1fc0c7185caf9eaa5914b00176cbf9a2c4bc6922e6dde52587f3125c8f9db4", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v3-gnu": { @@ -10987,8 +11067,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "701553f500dc2f525ce6a4c78891db14942b1a58fb1b5fa4c4c63120208e9edb", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "2c938382641e5d73c4b148396289f5c047ddbaa9c765ec541983c212e1e02407", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v3-musl": { @@ -11003,8 +11083,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "8e3c803ed8d3a32800b1d1b5c3279e11aac1ee07bf977288f77d827ab210794f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "ca3e3795de99768fef314cd40c9b1afe52a4ee6e1409121e8b8d84f524043716", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v4-gnu": { @@ -11019,8 +11099,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "c66f966c7d32e36851126756ba6ce7cfc6ec0fd2f257deb22420b98fb1790364", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "6c8cfad74f3ff99f275717096401021a15b92939fac1ce2ba0803d78beb8fa45", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v4-musl": { @@ -11035,8 +11115,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.12.11%2B20250626-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "438d8cfb97c881636811adf2bceaac28c756cccf852c460716d5f616efae3698", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "f4ec5b942fc2ddf55f26ec6a25b2ddfcda94fe095e158582a8f523258ee09cc4", "variant": "debug" }, "cpython-3.12.10-darwin-aarch64-none": { @@ -15083,8 +15163,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "e484c41e7a5c35b2956ac547c4f16fc2f3b4279b480ba3b89c8aef091aa4b51d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "89079bf9233e4305fac31fceef3e11f955ab78e3e3b0eedd8dabda39ca21558d", "variant": null }, "cpython-3.11.13-darwin-x86_64-none": { @@ -15099,8 +15179,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "1d18084cfa3347dc5e1a343cfd42d03de7ef69c93bf5ad31b105cfe12d7e502d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "441c0ef2ed9ee20d762c489dc3f2489d53d5a2b811af675fec2c0786273dd301", "variant": null }, "cpython-3.11.13-linux-aarch64-gnu": { @@ -15115,8 +15195,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "8c26b055937a25ccf17b7578bad42ce6c9fb452a6d4b1d72816755e234922668", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "7d6fb24f7d81af6a89d1a4358cc007ed513747c5beda578fb62a41e139ce0035", "variant": null }, "cpython-3.11.13-linux-armv7-gnueabi": { @@ -15131,8 +15211,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "17e6023581689c1bd51f37a381e0700770e387d7696bf8d6c7dae3bcea7fdd61", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "cb04fcda5b56cc274893e85f01ce536e5cc540c43352fc47f8b66280ffa1afaa", "variant": null }, "cpython-3.11.13-linux-armv7-gnueabihf": { @@ -15147,8 +15227,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "5a26f2dac227f6667724567027a4b502dea2e27b1105110a71d5a5df0b144a88", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "542cd5b270c16f841993fb63ecdb8ab3d85d6087cfef929304011949f3b6902e", "variant": null }, "cpython-3.11.13-linux-powerpc64le-gnu": { @@ -15163,8 +15243,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "8273efc027cce7fb3e5ec2a283be0266db76a75dea79bccf24470ff412835305", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "fa361259ac188fb6ee21c6928acfbb0ae287af5c5acbb01c8c55880d72d53d22", "variant": null }, "cpython-3.11.13-linux-riscv64-gnu": { @@ -15179,8 +15259,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "cf4719c427131c8d716b0642ddfc84d08d0e870f29cc54c230f3db188da44c72", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "23970dd65b2481eb8c7ddd211ba9841f7eb0927a9a3107a520a75ca97be3df3b", "variant": null }, "cpython-3.11.13-linux-s390x-gnu": { @@ -15195,8 +15275,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "2ec2fd56109668b455effb6201c9ab716d66bd59e1d144fa2ab4f727afa61c19", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "c0d3c3a102df12839c7632fb82e2210ccc0045c7b6cc3fc355e0fda30a9e6745", "variant": null }, "cpython-3.11.13-linux-x86_64-gnu": { @@ -15211,8 +15291,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "c6dd8a6fef05c28710ec62fab10d9343b61bbb9f40d402dad923497d7429fd17", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "07c15c63bdf6d14d80a50ec3ed9a0e81d04b9cf9671decfdec3d508ab252a713", "variant": null }, "cpython-3.11.13-linux-x86_64-musl": { @@ -15227,8 +15307,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "3d4fcdcd36591d80ca3b0affb6f96d5c5f56c5a344efbd013e406c9346053005", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "224de8d55c04df9cbf9555b57810dc6f87af82f0902c0883fcf6ed164c99b2cb", "variant": null }, "cpython-3.11.13-linux-x86_64_v2-gnu": { @@ -15243,8 +15323,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "9cc3c80f518031a0f2af702a38e37d706015bf411144ca29e05756eeee0f32b2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "37bc8eb56569dcb033efb69ffcb3a7dcc19220c9b6c88a47e2df1a4bcbc5bce3", "variant": null }, "cpython-3.11.13-linux-x86_64_v2-musl": { @@ -15259,8 +15339,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "1f1453776244baff8ff7a17d88fd68fdd10c08a950c911dd25cc28845c949421", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "d64c66b290eff19f4c200f07553f80e6d61afdb3d834b3ac989cda21731f2f59", "variant": null }, "cpython-3.11.13-linux-x86_64_v3-gnu": { @@ -15275,8 +15355,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "094fef0b6e8d778a61d430a44a784caab0c1be5a2b94d7169b17791c6fdfa2e5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "3099ad95267bd3154697079c1c057e84e206bdfc6cdb728f0d47c824f82db260", "variant": null }, "cpython-3.11.13-linux-x86_64_v3-musl": { @@ -15291,8 +15371,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "526c2c8088c1a41c4bd362c1d463fccaa37129dfe4f28166eabb726664a7550e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "231358a2adc7a132c07470577dfffa23353aca7384c1af6e87d0174e859df133", "variant": null }, "cpython-3.11.13-linux-x86_64_v4-gnu": { @@ -15307,8 +15387,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "27b7bd55029877537242b702badd96846ba1b41346998dfd8c7be191b6b393fa", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "27c9bec499de6ff14909f030c2d788e41e69d5b5ee2b9deb1f78666f630d6da4", "variant": null }, "cpython-3.11.13-linux-x86_64_v4-musl": { @@ -15323,8 +15403,24 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "f8dc02227a0156fd28652148486878ef7a50de09a5b8373555a0573cc2347f18", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "0d7bffc0778796def75b663e31c138592237cd205e358540b4bafd43df109c09", + "variant": null + }, + "cpython-3.11.13-windows-aarch64-none": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "windows", + "libc": "none", + "major": 3, + "minor": 11, + "patch": 13, + "prerelease": "", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "e5d8f83e8728506ad08382c7f68328b4204dd52c3f9cb4337087b1f21b68ee2a", "variant": null }, "cpython-3.11.13-windows-i686-none": { @@ -15339,8 +15435,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "0d1eb6a83db554379555279da7a00ef421306b35f5fd2603087a960c77aef5dc", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "228e47684fe989db07085f32b8dca766027ba34672252e9140f5bd367e0ba23f", "variant": null }, "cpython-3.11.13-windows-x86_64-none": { @@ -15355,8 +15451,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "709f7f3a0913226230f50c132ab6a1a517206c9d0b2e30c1779574913f0ac74b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "abde9c8b8b1230f1a21b52efd2e41a43b5af0a754255d294e180238bf6d291e0", "variant": null }, "cpython-3.11.13+debug-linux-aarch64-gnu": { @@ -15371,8 +15467,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "09a257ac990d66b64f383767154ab8dde452457fd973e403c3ffe73ea2ef6041", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "c07c910236c121818decbdfd947f5033741afc8610f347256179cbda9cee0ccf", "variant": "debug" }, "cpython-3.11.13+debug-linux-armv7-gnueabi": { @@ -15387,8 +15483,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "d8dc85bbec1c157e078c66def67ad65157a96ba19fcde8962131a41f4f600e98", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "2b15db81177c7975393cbae2a5b7989b59318084c0e8ae3798cb5982606bf5d1", "variant": "debug" }, "cpython-3.11.13+debug-linux-armv7-gnueabihf": { @@ -15403,8 +15499,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "b27b622310194321853d106f4b344b73592b5ca85b1cc9ed3bdb19bdb3d6f0d0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "b3f948f00201905cf6a7448030a40a51213b3f937f29f501114354f4cc419886", "variant": "debug" }, "cpython-3.11.13+debug-linux-powerpc64le-gnu": { @@ -15419,8 +15515,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "8ba75ecfead212f9f5e9b8e2cbc2ad608f5b6812619da4414fd6b283f2acbf78", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "7f2f6ceb4f242e71977eefd5e0e702c5e87d6600342d79e6dadaf2e59ed5476f", "variant": "debug" }, "cpython-3.11.13+debug-linux-riscv64-gnu": { @@ -15435,8 +15531,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "c199f80c7d4502ba3d710c4c7450f81642bdac5524079829b7c7b8002b9747a8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "7454ccaae24cf8742ea55ac74b0b1d147be3f99bf290c7c5a02f0027a36805ac", "variant": "debug" }, "cpython-3.11.13+debug-linux-s390x-gnu": { @@ -15451,8 +15547,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "23934c0dc86aeb1c5d0f0877ac8d9d6632627a0b60c9f4f9ad9db41278b6745f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "9fa9c461b14dd142e15cf95db1b0ca7310ea603fec250eb1952a8d31b64027f0", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64-gnu": { @@ -15467,8 +15563,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "a405a9b2bc94b564a03f351b549c708f686aeade9aec480108b613332cf9cc48", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "533d8ccdb77fc43847227850361809c9bfb85223a08d689e31087df34d8260ac", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64-musl": { @@ -15483,8 +15579,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "2b3fecb1717cf632e789c4b8c03eda825f9c3e112cac922d58b42e7eecb8731f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "0f4dfe78c30d56acb5e6df28de4058177ac188ddd6ea1f2d3a18db7fcfe7ff1b", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v2-gnu": { @@ -15499,8 +15595,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "ab59a4df1e7d269400116e87daaa25b800ffbb24512b57f8d2aa4adbe3d43577", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "03a7b763a2e5deec621813a9081858aad3ed0f24368ff94bf4a78eb652ffa8a0", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v2-musl": { @@ -15515,8 +15611,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "1fc167ea607ef98b6d0dbac121a9ae8739fdf958f21cbbd60cac47b7ce52b003", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "b5e1e4c7fc8a3eff8a85f4b4dd004f7d835a8ac79a982da65054d605f106c5eb", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v3-gnu": { @@ -15531,8 +15627,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "6140e14e713077169826458a47a7439938b98a73810c5e7a709c9c20161ae186", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "d020b9ad0fb3afc95ccf6c9737092f8ea4e977d8f6c0bd2503cd60457113fbfa", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v3-musl": { @@ -15547,8 +15643,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "77cf2b966c521c1036eb599ab19e3f6e0d987dbb9ba5daa86d0b59d7d1a609a1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "2f04db73b5fb332f37e409ec048e09a60fd1da729893f629ae37c4991f354d0e", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v4-gnu": { @@ -15563,8 +15659,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "92583c28fdcbbf716329a6711783e2fb031bb8777e8a99bd0b4d3eed03ec0551", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "8a5ea7c66a05947522d528cb47e03dd795ab108e78b0926039119e9f8341e5b0", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v4-musl": { @@ -15579,8 +15675,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13%2B20250626-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "dda82882159f45a926486bc221739c2ff5c0b1d9fa705a51d0f3f729f736162c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "9829333c2b776ff11ab2e4be31237dc6c36cb2e13c04c361c7c57f4d54c6af3b", "variant": "debug" }, "cpython-3.11.12-darwin-aarch64-none": { @@ -19371,8 +19467,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "e610b8bb397513908b814cb2a1c9f95e97aac33855840bf69f1442ff040ddd33", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "c34bfae5fe85a28c1b13c2d160268eafee18b66f25c29b139958e164897a2527", "variant": null }, "cpython-3.10.18-darwin-x86_64-none": { @@ -19387,8 +19483,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "5c4d02b1626fd677ee3bc432d77e6dca5bbb9b1f03b258edd4c8cf6902eba902", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "f2676d7b52e94334c4564c441c0aeabda9858f29384cbaf824c8a091b574f938", "variant": null }, "cpython-3.10.18-linux-aarch64-gnu": { @@ -19403,8 +19499,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "12519960127a5082410496f59928c3ed1c2d37076d6400b22e52ee962e315522", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "b6f3243caa1fdcf12d62e1711e8a8255ae419632e1e98b6b1fa9809564af82f0", "variant": null }, "cpython-3.10.18-linux-armv7-gnueabi": { @@ -19419,8 +19515,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "6dd0d28f7229d044ec1560e11dcbb5452166921c4a931aeae9b9f08f61451eb9", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "c091409b05c986623be140e87bedea2a7f2d005dbf1564b9b529d3b3727f5608", "variant": null }, "cpython-3.10.18-linux-armv7-gnueabihf": { @@ -19435,8 +19531,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "810089263699a427f8610070ba7ebad2a78dac894e93a6c055ec28f3303c704e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "326c378407486a185c516d927652978728f056af6d62150c70678f6b590c1df9", "variant": null }, "cpython-3.10.18-linux-powerpc64le-gnu": { @@ -19451,8 +19547,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "263befb4049ed1a272b9299ce41f5d5ef855998462a620dd6b62ecfde47d5154", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "3ec27bd1f7f428fb12d9973d84fe268eca476097ab3ab650b4b73fd21861832a", "variant": null }, "cpython-3.10.18-linux-riscv64-gnu": { @@ -19467,8 +19563,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "0999d7be0eb75b096791f2f57369dd1a6f4cd9dc44eb9720886268e3a3adddd7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "9da0a695abfd3a18355642055f79bc0d227e05c03d0430c308e02143fc7dbf9d", "variant": null }, "cpython-3.10.18-linux-s390x-gnu": { @@ -19483,8 +19579,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "2ac9a0791f893b96c4a48942aa2f19b16ddbf60977db63de513beef279599760", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "8192b48982c0f0c0ff642bf84bcf26003812eaac5ef4853ba9b7b6e80745923a", "variant": null }, "cpython-3.10.18-linux-x86_64-gnu": { @@ -19499,8 +19595,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "9d1a87e52e2be1590a6a5148f1b874ba4155095c11e5afad7cb9618e7a4a1912", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "11b27504b26ea1977bf7b7e6ef6c9da4d648b78707fa34fe0538f08744978a4b", "variant": null }, "cpython-3.10.18-linux-x86_64-musl": { @@ -19515,8 +19611,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "c85cac7c12bb3c6bf09801f9b3f95d77acb5aa5de10a85aeceafb026d641c62c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "f5302f08043468f9c9a47fa7884393c27de66b19b3e46096984245d8f4d8be8f", "variant": null }, "cpython-3.10.18-linux-x86_64_v2-gnu": { @@ -19531,8 +19627,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "d5f70ab75fc24ab3efa4b2edb14163bb145c1f9d297d03dde6c2a40ccb0eb9ac", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "e979cf0596697fc729d42c5d6d37e1d591374ac56c42d1b5db9e05750984f122", "variant": null }, "cpython-3.10.18-linux-x86_64_v2-musl": { @@ -19547,8 +19643,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "7800a067ffb6ebd9c226c075da8c57ff843f9b9e7b89b9c14e013bc33f042e4e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "d9d0c7e28bc0f5f2a8ce0a9f4d57f8fe263361f5fd7d8afd6f8eeecc7ce00f96", "variant": null }, "cpython-3.10.18-linux-x86_64_v3-gnu": { @@ -19563,8 +19659,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "ab36f3e99a1fb89cb225fe20a585068a5b4323d084b4441ce0034177b8d8c3bf", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "3438a6066eb9f801f5a3153d7ee5fb0d1bf7769e44a610effd2340e846a9fd44", "variant": null }, "cpython-3.10.18-linux-x86_64_v3-musl": { @@ -19579,8 +19675,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "7e91185f8d1e614c85447561752476b7c079e63df9a707bb9b4c0f1649446e00", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "ae4eeba3037b6810eb9e96814d3980e28a5a3de5277f470100742a48391c6df7", "variant": null }, "cpython-3.10.18-linux-x86_64_v4-gnu": { @@ -19595,8 +19691,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "65cfa877bc7899f0a458ff5327311e77e0b70fa1c871aeb6dfa582d23422337e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "c8cde20b14e0ef0e564d5a1cbd0c6003ae178dfea9d446cbf3ee15d75ac8a9d8", "variant": null }, "cpython-3.10.18-linux-x86_64_v4-musl": { @@ -19611,8 +19707,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "869b5a0919558fe8bbdac4d75a9e9a114a4aa7ca0905da4243ec1b7e4ff99006", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "46da91101e88f68f5fa21a2aeadeeb91072cbe9d9f1caa08e34481180ec3dea3", "variant": null }, "cpython-3.10.18-windows-i686-none": { @@ -19627,8 +19723,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "a0a5edc7cff851a59dca8471858262a2bb3708b00ad443dd908c3b67384b8ee4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "faab88604275a0c869cf3fb19f63b0158bd8356d74fff15ebd05114908683fb1", "variant": null }, "cpython-3.10.18-windows-x86_64-none": { @@ -19643,8 +19739,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "d6e97e2984ff157d3b5caf9078eb45613684a39368da2fbac8dd364080322e82", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "ac56903c7ae726f154b5b000f04db72ddb52775812c638cc67b5090146267502", "variant": null }, "cpython-3.10.18+debug-linux-aarch64-gnu": { @@ -19659,8 +19755,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "e42a95657c84804f0edf02db89e12e907e1fb9d8c92b707214be7c1b3dc0f4d5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "fcbbf8d8e274f7983a70e104bf708ff21effc28bb594314429317e73960f85ff", "variant": "debug" }, "cpython-3.10.18+debug-linux-armv7-gnueabi": { @@ -19675,8 +19771,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "e733a7aa76137d5a795ec5db08d5c37d06e111e2343d70308f09789ced7b4d32", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "400ab706d0f21b68793379040f9fa96fce9cacfff25217aaa3f37cac8d15fea5", "variant": "debug" }, "cpython-3.10.18+debug-linux-armv7-gnueabihf": { @@ -19691,8 +19787,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "06ec2977ec2d01f3225272926ab43d66f976f1481d069d9a618a0541b2644451", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "48a8a9cbf0b6a9bfd95af64af97dda167d99240cd3f66265011006f2d2a54e34", "variant": "debug" }, "cpython-3.10.18+debug-linux-powerpc64le-gnu": { @@ -19707,8 +19803,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "2ae4ed1805b647f105db8c41d9ac55fcda2672526b63c2e1aa9d0eb16a537594", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "8571d2b81798162c1effb57370359fe178d96f3a5a6bb15f5bd88c83caf8c6b4", "variant": "debug" }, "cpython-3.10.18+debug-linux-riscv64-gnu": { @@ -19723,8 +19819,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "c9fdef27a8295c29c4ba6fd2daff344c12471f97ca7a19280c3e0f7955438668", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "dedf8c8bdae3b3d4d8ec028c28bb8ad12f9c78064828426f6570322eba490015", "variant": "debug" }, "cpython-3.10.18+debug-linux-s390x-gnu": { @@ -19739,8 +19835,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "eac2aab5eaea5e8df9c24918c0ade7a003abe6d25390d4a85e7dd8eea6eee1e3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "ffd41140c4f55b9700be4381a0ef774067486572156ec17839e38f360512cc3e", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64-gnu": { @@ -19755,8 +19851,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "1d651aebc511e100a80142b78d2ea4655a6309f5a87a6cbd557fed5afda28f33", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "ec0b1c02cb0813db9d9bfb1c41633c15c73446d74406d07df0de041dadcd2d7b", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64-musl": { @@ -19771,8 +19867,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "c84b94373b1704d5d3a2f4c997c3462d1a03ebbd141eeeaec58da411e1cd62c1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "448962b7ac159470130fa32817f917c5db659d5adde6c300c93335fdb0915d92", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v2-gnu": { @@ -19787,8 +19883,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "72eacef9b8752ad823dbc009aa9be2b3199f7556218a63a4de0f1cb1b6bd08ec", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "a6d0b714ee3b0b0aae36dbb6c5a28378717c99070797ffda12ddd433a8f8588c", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v2-musl": { @@ -19803,8 +19899,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "01a9ddcf418a74805c17ceaa9ecdcbe0871f309e0649b43e6cd6f0fe883d8a14", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "09903f2c08fccddce4f7a5d52c2956d898a70a7e47bb07825581e77ad9a77829", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v3-gnu": { @@ -19819,8 +19915,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "06d979ecd8256d978f5a76f90063b198f08bb6833ead81e52a32f0213337f333", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "c453e8adeed1f0dc8253bbd6ebd72d9803b88fbfd015049b3d281ce915b0dfbf", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v3-musl": { @@ -19835,8 +19931,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "f910de7dcd093e74d45b349bcd7a0cf291602979a90ef682fcf2b90a6bd4e301", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "054fdb4b6d27155371cdfa9188a93c4d58325ebb9a061ded8ad7b044cc177064", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v4-gnu": { @@ -19851,8 +19947,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "c3516587af46e3e38115b282f8585960e5050ad9a822e8d85c4159f1a209960f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "0e47c951e3800a10f531ef4eb4d3307822f76674dbe863c11a48547ae51f0e27", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v4-musl": { @@ -19867,8 +19963,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.10.18%2B20250626-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "827cbcaad8cb5e16192f2f89f2e62a48ee3be89ec924d83e526f213806433d4a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "7f625673e2d06abac133fd9588b6492c3b6b69b6cacd1b1bb480b9476316ab74", "variant": "debug" }, "cpython-3.10.17-darwin-aarch64-none": { @@ -24811,8 +24907,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "dbb161ced093b9b3b7f93ae7064fe20821471c2a4ac884e2bc94a216a2e19cba", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "f47a96f6bcf29ef0e803c8524b027c4c1b411d27934e6521ebe3381f9d674f7b", "variant": null }, "cpython-3.9.23-darwin-x86_64-none": { @@ -24827,8 +24923,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "b7dbbec3f74512ce7282ffeb9d262c3605e824f564c49c698a83d8ad9a9810df", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "04fe6e2fd53c9dc4dfbcf870174c44136f82690f7387451bf8863712314eb156", "variant": null }, "cpython-3.9.23-linux-aarch64-gnu": { @@ -24843,8 +24939,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "f8886fd05163ff181bc162399815e2c74b2dc85831a05ce76472fe10a0e509d6", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "324e4aaba0d4749d9d9a2007a7fb2e55c6c206241c8607ade29e9a61a70f23c0", "variant": null }, "cpython-3.9.23-linux-armv7-gnueabi": { @@ -24859,8 +24955,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "f05ac5a425eaf4ae61bfb8981627fb6b6a6224733d9bfbe79f1c9cd89694fa1a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "5f88c66314c7820cb20ca3fef3c191850d0247c78d66cb366590ebb813b36b4d", "variant": null }, "cpython-3.9.23-linux-armv7-gnueabihf": { @@ -24875,8 +24971,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "c5cb4da17943d74c1a94b6894d9960d96e8e4afc83e629a5788adbd2f8f4d51f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "2a4a348c42b424f5d01ff61882edde283e0135331b54349f5bc41f70282fc56f", "variant": null }, "cpython-3.9.23-linux-powerpc64le-gnu": { @@ -24891,8 +24987,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "815e88f67598ebb039e1735a5d5f4f9facf61481111657a3ecefb69993a6a8ab", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "f80b1a541b74122260372e36504157a94b7b036626779b215f66e26174c16ba1", "variant": null }, "cpython-3.9.23-linux-riscv64-gnu": { @@ -24907,8 +25003,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "595c1ca3dbad46cc2370e17a5d415da82b75140a057449fc7de5d41faa2cc87a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "4a57ad0c9a214e97cf18ed366f858b224e7a4c3da8519adcb0f757fb4939c64e", "variant": null }, "cpython-3.9.23-linux-s390x-gnu": { @@ -24923,8 +25019,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "e6b2c9ca1962a069959f70d304e15ba0e63506db6e1b6bc3c75d03ca7012ac9f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "f76716832859664fb3bb89708fd60e55cf7bbb9fd4066a1746dda26c9baa633a", "variant": null }, "cpython-3.9.23-linux-x86_64-gnu": { @@ -24939,8 +25035,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "e6707a3a40fc68f37bad9b7ad9e018715af3be057b62bc80fee830a161c775f3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "a4c3137284a68bcae77e794873978703b079ed3355c86f2d9e3e1e71b2910ccb", "variant": null }, "cpython-3.9.23-linux-x86_64-musl": { @@ -24955,8 +25051,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "9ce6c722a5436f4adc1b25c01c5ada2c230f0089302a544d35b5b80702c0a7db", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "a501bfae7b0c8f3fbfd4de76698face42af7f878395b22a7d567c9117f118c43", "variant": null }, "cpython-3.9.23-linux-x86_64_v2-gnu": { @@ -24971,8 +25067,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "d6d6329699af5d463cfecef304e739347b1e485d959a63dc54b39a818dd0c5dd", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "5a609ff46904d28acbc52cfa6b445066ec98ca690ffa8870fdd17537b72d98b0", "variant": null }, "cpython-3.9.23-linux-x86_64_v2-musl": { @@ -24987,8 +25083,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "eb082839a9dd585e151395041f7832bb725a3bfc4e153e3f41949d43163902ab", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "a041d91af09e4246f463361ec76de06080fd1cf05771f59e13a1fbbc42fb3d6f", "variant": null }, "cpython-3.9.23-linux-x86_64_v3-gnu": { @@ -25003,8 +25099,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "2a0080b853d55ae9db6b20209d63fbd4cacc794be647e7f9cf1a342dfd7e5796", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "b7bd686ea5e00697498baead5a01abe0ceb4a0c9e6fbed5823b0c8225fb25176", "variant": null }, "cpython-3.9.23-linux-x86_64_v3-musl": { @@ -25019,8 +25115,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "3eb47ce30946e30415538acf0e7a3efbac4679d5d16900c5c379f77ebef3321d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "4daf37cac1f2fcc6696c64f0a9705f01121046f9b4a3c5424b83714a61161a5b", "variant": null }, "cpython-3.9.23-linux-x86_64_v4-gnu": { @@ -25035,8 +25131,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "2a81c218277a16515531df2568b8dc72cd1b2a2c22268882d57881a6f0f931d4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "4cf404087df0b1deb8332725583789078b868b5baa6aa5375e168d3cdb987e7f", "variant": null }, "cpython-3.9.23-linux-x86_64_v4-musl": { @@ -25051,8 +25147,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "a30191e1ecd58075909c5f28f21ddc290687afaa216b5cdd8c1f5a97963d1a95", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "f7e827cc26ce8051e77ec7a7bea2d658a1f924664e0725c21a67ab15d6f77bf8", "variant": null }, "cpython-3.9.23-windows-i686-none": { @@ -25067,8 +25163,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "f0482615c924623c3441ea96bfa1ea748b597db9099b8ad2e7c51526d799020b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "09e4db6bd06c7b26e437df662fda4d507f276ee1ba90b986e5596a823f5d980b", "variant": null }, "cpython-3.9.23-windows-x86_64-none": { @@ -25083,8 +25179,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "d73d4aee5f2097cd1f5cc16a0f345c7cb53c3e8b94f1ca007b918a3152185c4b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "3c50ad97c45bcf0b2984479ebe896f61032222605d317ad059a481730a0ee72a", "variant": null }, "cpython-3.9.23+debug-linux-aarch64-gnu": { @@ -25099,8 +25195,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "140645dbd801afa90101806401cb2f855cd243e7b88a6c3bce665e39c252c6e1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "751f68beb0dd5067bde56d9a4028b0233e2a71f84bf421e8627dc2abd068d954", "variant": "debug" }, "cpython-3.9.23+debug-linux-armv7-gnueabi": { @@ -25115,8 +25211,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "b6e8223142843640e8e5acbf96eaea72f2e7218e9da61de1d2215a626ebe207b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "29ee738ac4d186bb431c4382a6d1cc550a0916a1ce99d58769fc5371faff8409", "variant": "debug" }, "cpython-3.9.23+debug-linux-armv7-gnueabihf": { @@ -25131,8 +25227,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "1b9cc881bcf52e9dd22da45782a11beda7de63e5d0644092412542ec8c3c2ce8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "afea4baf253f4bf91781c122308ca306d72fa1e76526abf2b78648772c979d2c", "variant": "debug" }, "cpython-3.9.23+debug-linux-powerpc64le-gnu": { @@ -25147,8 +25243,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "93cdb8c7bc5a13c273855b97e827a3b9b12522b7d7ed14e5110a7fa5043f7654", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "0c29b50f57489b93b29a6fe00cb24fb84cd6713c065e35794d1d29d81485c01d", "variant": "debug" }, "cpython-3.9.23+debug-linux-riscv64-gnu": { @@ -25163,8 +25259,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "fb04b810e7980a211ab926f0db3430919790b86dee304682e2453669063fff34", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "e4c17ce1f909fe47049fb3236301dde356d4ccc456f70e59e31df8f6b6e21f7d", "variant": "debug" }, "cpython-3.9.23+debug-linux-s390x-gnu": { @@ -25179,8 +25275,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "394cd53c4c012d7c32950600462a68fe0ed52f5204f745a7ebbc19f2473121b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "8f2783d56728a918568f8ec81d2b7c8b0e4060e605f42b4ae50b9f127925b54c", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64-gnu": { @@ -25195,8 +25291,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "a772240194dd997da21e53029fde922da7e1893af1356eb47d710b2fbf144b0e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "f4ef26643dfb13a80439373ce4e192f9c678ccffce8bda9455258052c29e0c85", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64-musl": { @@ -25211,8 +25307,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "8153da9228087fc1ed655a83eb304318cc646333966ccb9ccd75ab9589b8585f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "f74953fe519fbdbb35ce722c97521b89c915da7d630af5a82911dd352f5c8cec", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v2-gnu": { @@ -25227,8 +25323,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "79d6c604c491c02cff2e1ffd632baf4d2418a85fe10dd1260ec860dde92322c1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "039ada74a3fea0f359a617a57e83ee92c43d5770175bb3c26d96ac65364f7f5c", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v2-musl": { @@ -25243,8 +25339,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "7e222a1a6333e1e10bf476ac37ca2734885cb2cf92a7a8d3dc4f7fb81f69683b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "64e72ce5b13bff5f7f9e44032ecf6ff70212b7384d2512ab5ed8b6edddd90e68", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v3-gnu": { @@ -25259,8 +25355,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "a03b38980fb6578be1a7c8e78f7219662e72ac1dc095b246d5a0b06d27e61ece", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "9a525d2a45db88cf7b450b2c0226204c1e5f634e3fb793f345ff6d44a9bac0fb", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v3-musl": { @@ -25275,8 +25371,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "93df57f432ad2741e8edeefb1caf7a3d538f8c90cac732c6865d914d17577aed", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "7370898765b907757b7220db752c91de9a1832460d665845c74c4c946bbdd4ec", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v4-gnu": { @@ -25291,8 +25387,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "2ba7ac2755c52728882cf187e8f2127b0b89cade6eaa2d42dd379d1bcd01a0a9", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "c104d6d5d6943ecf53b9cc8560a32239a62c030a1170d1d4c686e87d7da60d58", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v4-musl": { @@ -25307,8 +25403,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.9.23%2B20250626-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "5253927e725888dae7a39caca1a88dcbfa002b2a115cb6e06000db04e133b51d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "933c85357368d1819a32bed32cdb871153ecc2a39b93565ae61e5fa0b509e2eb", "variant": "debug" }, "cpython-3.9.22-darwin-aarch64-none": { From 9e9505df50c821c848f5677e58536563493aa0ac Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Tue, 1 Jul 2025 00:58:29 +0200 Subject: [PATCH 111/349] Bump CodSpeed to v3 (#14371) ## Summary As explained in the [`codspeed-rust` v3 release notes](https://github.com/CodSpeedHQ/codspeed-rust/releases/tag/v3.0.0), the `v3` of the compatibility layers is now required to work with the latest version(`v3`) of `cargo-codspeed`. --- Cargo.lock | 186 ++++++++++++++++++++++++++++++++++--- crates/uv-bench/Cargo.toml | 6 +- 2 files changed, 175 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eb2faac26..e317fd224 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -94,6 +94,15 @@ version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + [[package]] name = "arbitrary" version = "1.4.1" @@ -364,6 +373,15 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bisection" version = "0.1.0" @@ -672,22 +690,27 @@ checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "codspeed" -version = "2.10.1" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f4cce9c27c49c4f101fffeebb1826f41a9df2e7498b7cd4d95c0658b796c6c" +checksum = "38b41c7ae78309311a5ce5f31dbdae2d7d6ad68b057163eaa23e05b68c9ffac8" dependencies = [ + "anyhow", + "bincode", "colored", + "glob", "libc", + "nix 0.29.0", "serde", "serde_json", + "statrs", "uuid", ] [[package]] name = "codspeed-criterion-compat" -version = "2.10.1" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c23d880a28a2aab52d38ca8481dd7a3187157d0a952196b6db1db3c8499725" +checksum = "b2c699a447da1c442a81e14f01363f44ffd68eb13fedfd3ed13ce5cf30f738d9" dependencies = [ "codspeed", "codspeed-criterion-compat-walltime", @@ -696,9 +719,9 @@ dependencies = [ [[package]] name = "codspeed-criterion-compat-walltime" -version = "2.10.1" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0a2f7365e347f4f22a67e9ea689bf7bc89900a354e22e26cf8a531a42c8fbb" +checksum = "c104831cdfa515c938d8ec5b8dd38030a5f815b113762f0d03f494177b261cfd" dependencies = [ "anes", "cast", @@ -738,7 +761,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1115,7 +1138,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1930,7 +1953,7 @@ checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" dependencies = [ "hermit-abi 0.4.0", "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1990,7 +2013,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2084,6 +2107,12 @@ version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + [[package]] name = "libmimalloc-sys" version = "0.1.39" @@ -2204,6 +2233,16 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "matrixmultiply" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" +dependencies = [ + "autocfg", + "rawpointer", +] + [[package]] name = "md-5" version = "0.10.6" @@ -2341,6 +2380,23 @@ dependencies = [ "syn", ] +[[package]] +name = "nalgebra" +version = "0.33.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26aecdf64b707efd1310e3544d709c5c0ac61c13756046aaaba41be5c4f66a3b" +dependencies = [ + "approx", + "matrixmultiply", + "num-complex", + "num-rational", + "num-traits", + "rand", + "rand_distr", + "simba", + "typenum", +] + [[package]] name = "nanoid" version = "0.4.0" @@ -2399,6 +2455,45 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -2406,6 +2501,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -2849,7 +2945,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2906,6 +3002,22 @@ dependencies = [ "getrandom 0.2.15", ] +[[package]] +name = "rand_distr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "rayon" version = "1.10.0" @@ -3286,7 +3398,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3299,7 +3411,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.2", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3385,6 +3497,15 @@ version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" +[[package]] +name = "safe_arch" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" +dependencies = [ + "bytemuck", +] + [[package]] name = "same-file" version = "1.0.6" @@ -3641,6 +3762,19 @@ dependencies = [ "libc", ] +[[package]] +name = "simba" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a386a501cd104797982c15ae17aafe8b9261315b5d07e3ec803f2ea26be0fa" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", + "wide", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -3720,6 +3854,18 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "statrs" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a3fe7c28c6512e766b0874335db33c94ad7b8f9054228ae1c2abd47ce7d335e" +dependencies = [ + "approx", + "nalgebra", + "num-traits", + "rand", +] + [[package]] name = "strict-num" version = "0.1.1" @@ -3881,7 +4027,7 @@ dependencies = [ "getrandom 0.3.1", "once_cell", "rustix 1.0.7", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -6216,6 +6362,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wide" +version = "0.7.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" +dependencies = [ + "bytemuck", + "safe_arch", +] + [[package]] name = "widestring" version = "1.1.0" @@ -6244,7 +6400,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/crates/uv-bench/Cargo.toml b/crates/uv-bench/Cargo.toml index 5d55fafd7..afa73c32b 100644 --- a/crates/uv-bench/Cargo.toml +++ b/crates/uv-bench/Cargo.toml @@ -42,8 +42,10 @@ uv-types = { workspace = true } uv-workspace = { workspace = true } anyhow = { workspace = true } -codspeed-criterion-compat = { version = "2.7.2", default-features = false, optional = true } -criterion = { version = "0.6.0", default-features = false, features = ["async_tokio"] } +codspeed-criterion-compat = { version = "3.0.1", default-features = false, optional = true } +criterion = { version = "0.6.0", default-features = false, features = [ + "async_tokio", +] } jiff = { workspace = true } tokio = { workspace = true } From 5f8d4bbf02831d64595591fd3385131d162ca1e5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 30 Jun 2025 19:16:31 -0400 Subject: [PATCH 112/349] Update Rust crate indexmap to v2.10.0 (#14365) --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e317fd224..0c5bab5a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1896,9 +1896,9 @@ checksum = "b72ad49b554c1728b1e83254a1b1565aea4161e28dabbfa171fc15fe62299caf" [[package]] name = "indexmap" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", "hashbrown 0.15.4", From 7bbdc08dae5218029c8ae1b3b2234c748129f882 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 30 Jun 2025 19:16:46 -0400 Subject: [PATCH 113/349] Update depot/build-push-action action to v1.15.0 (#14361) --- .github/workflows/build-docker.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 272a69e9a..843ee8dfb 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -137,7 +137,7 @@ jobs: - name: Build and push by digest id: build - uses: depot/build-push-action@636daae76684e38c301daa0c5eca1c095b24e780 # v1.14.0 + uses: depot/build-push-action@2583627a84956d07561420dcc1d0eb1f2af3fac0 # v1.15.0 with: project: 7hd4vdzmw5 # astral-sh/uv context: . @@ -267,7 +267,7 @@ jobs: - name: Build and push id: build-and-push - uses: depot/build-push-action@636daae76684e38c301daa0c5eca1c095b24e780 # v1.14.0 + uses: depot/build-push-action@2583627a84956d07561420dcc1d0eb1f2af3fac0 # v1.15.0 with: context: . project: 7hd4vdzmw5 # astral-sh/uv From c5ca240fb7b6956a9d375707917647b8c45d8446 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 30 Jun 2025 19:16:53 -0400 Subject: [PATCH 114/349] Update PyO3/maturin-action action to v1.49.3 (#14363) --- .github/workflows/build-binaries.yml | 44 ++++++++++++++-------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml index 0d0498453..ccd3ef3ee 100644 --- a/.github/workflows/build-binaries.yml +++ b/.github/workflows/build-binaries.yml @@ -54,7 +54,7 @@ jobs: - name: "Prep README.md" run: python scripts/transform_readme.py --target pypi - name: "Build sdist" - uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1 + uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3 with: command: sdist args: --out dist @@ -74,7 +74,7 @@ jobs: # uv-build - name: "Build sdist uv-build" - uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1 + uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3 with: command: sdist args: --out crates/uv-build/dist -m crates/uv-build/Cargo.toml @@ -103,7 +103,7 @@ jobs: # uv - name: "Build wheels - x86_64" - uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1 + uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3 with: target: x86_64 args: --release --locked --out dist --features self-update @@ -133,7 +133,7 @@ jobs: # uv-build - name: "Build wheels uv-build - x86_64" - uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1 + uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3 with: target: x86_64 args: --profile minimal-size --locked --out crates/uv-build/dist -m crates/uv-build/Cargo.toml @@ -157,7 +157,7 @@ jobs: # uv - name: "Build wheels - aarch64" - uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1 + uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3 with: target: aarch64 args: --release --locked --out dist --features self-update @@ -193,7 +193,7 @@ jobs: # uv-build - name: "Build wheels uv-build - aarch64" - uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1 + uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3 with: target: aarch64 args: --profile minimal-size --locked --out crates/uv-build/dist -m crates/uv-build/Cargo.toml @@ -231,7 +231,7 @@ jobs: # uv - name: "Build wheels" - uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1 + uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3 with: target: ${{ matrix.platform.target }} args: --release --locked --out dist --features self-update,windows-gui-bin @@ -267,7 +267,7 @@ jobs: # uv-build - name: "Build wheels uv-build" - uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1 + uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3 with: target: ${{ matrix.platform.target }} args: --profile minimal-size --locked --out crates/uv-build/dist -m crates/uv-build/Cargo.toml @@ -303,7 +303,7 @@ jobs: # uv - name: "Build wheels" - uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1 + uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3 with: target: ${{ matrix.target }} # Generally, we try to build in a target docker container. In this case however, a @@ -368,7 +368,7 @@ jobs: # uv-build - name: "Build wheels uv-build" - uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1 + uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3 with: target: ${{ matrix.target }} manylinux: auto @@ -412,7 +412,7 @@ jobs: # uv - name: "Build wheels" - uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1 + uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3 with: target: ${{ matrix.platform.target }} # On `aarch64`, use `manylinux: 2_28`; otherwise, use `manylinux: auto`. @@ -461,7 +461,7 @@ jobs: # uv-build - name: "Build wheels uv-build" - uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1 + uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3 with: target: ${{ matrix.platform.target }} # On `aarch64`, use `manylinux: 2_28`; otherwise, use `manylinux: auto`. @@ -509,7 +509,7 @@ jobs: # uv - name: "Build wheels" - uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1 + uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3 with: target: ${{ matrix.platform.target }} manylinux: auto @@ -561,7 +561,7 @@ jobs: # uv-build - name: "Build wheels uv-build" - uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1 + uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3 with: target: ${{ matrix.platform.target }} manylinux: auto @@ -614,7 +614,7 @@ jobs: # uv - name: "Build wheels" - uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1 + uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3 with: target: ${{ matrix.platform.target }} manylinux: auto @@ -671,7 +671,7 @@ jobs: # uv-build - name: "Build wheels uv-build" - uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1 + uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3 with: target: ${{ matrix.platform.target }} manylinux: auto @@ -712,7 +712,7 @@ jobs: # uv - name: "Build wheels" - uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1 + uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3 with: target: ${{ matrix.platform.target }} manylinux: auto @@ -761,7 +761,7 @@ jobs: # uv-build - name: "Build wheels uv-build" - uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1 + uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3 with: target: ${{ matrix.platform.target }} manylinux: auto @@ -807,7 +807,7 @@ jobs: # uv - name: "Build wheels" - uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1 + uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3 with: target: ${{ matrix.target }} manylinux: musllinux_1_1 @@ -854,7 +854,7 @@ jobs: # uv-build - name: "Build wheels uv-build" - uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1 + uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3 with: target: ${{ matrix.target }} manylinux: musllinux_1_1 @@ -901,7 +901,7 @@ jobs: # uv - name: "Build wheels" - uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1 + uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3 with: target: ${{ matrix.platform.target }} manylinux: musllinux_1_1 @@ -966,7 +966,7 @@ jobs: # uv-build - name: "Build wheels" - uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1 + uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3 with: target: ${{ matrix.platform.target }} manylinux: musllinux_1_1 From a3db9a9ae4a6232161ee55d6e5283aaa5e527342 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 1 Jul 2025 03:44:18 +0000 Subject: [PATCH 115/349] Sync latest Python releases (#14381) 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 2e67f8e17..e35d2c3fc 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/20250626/cpython-unix/targets.yml"; +const TARGETS_YML_URL: &str = "https://raw.githubusercontent.com/astral-sh/python-build-standalone/refs/tags/20250630/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 1ae689833..850b2c764 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 b1812d111a77b29eef89dce2be32d90db43de0be Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 1 Jul 2025 03:44:23 -0500 Subject: [PATCH 116/349] Edits to the build backend documentation (#14376) Co-authored-by: konstin --- docs/concepts/build-backend.md | 210 +++++++++++++++++++++++---------- 1 file changed, 150 insertions(+), 60 deletions(-) diff --git a/docs/concepts/build-backend.md b/docs/concepts/build-backend.md index 78bc84636..dd7685756 100644 --- a/docs/concepts/build-backend.md +++ b/docs/concepts/build-backend.md @@ -7,98 +7,188 @@ When preview mode is not enabled, uv uses [hatchling](https://pypi.org/project/hatchling/) as the default build backend. A build backend transforms a source tree (i.e., a directory) into a source distribution or a wheel. -While uv supports all build backends (as specified by PEP 517), it includes a `uv_build` backend -that integrates tightly with uv to improve performance and user experience. -The uv build backend currently only supports Python code. An alternative backend is required if you -want to create a -[library with extension modules](../concepts/projects/init.md#projects-with-extension-modules). +uv supports all build backends (as specified by [PEP 517](https://peps.python.org/pep-0517/)), but +also provides a native build backend (`uv_build`) that integrates tightly with uv to improve +performance and user experience. -To use the uv build backend as [build system](../concepts/projects/config.md#build-systems) in an -existing project, add it to the `[build-system]` section in your `pyproject.toml`: +## Using the uv build backend -```toml +!!! important + + The uv build backend currently **only supports pure Python code**. An alternative backend is to + build a [library with extension modules](../concepts/projects/init.md#projects-with-extension-modules). + +To use uv as a build backend in an existing project, add `uv_build` to the +[`[build-system]`](../concepts/projects/config.md#build-systems) section in your `pyproject.toml`: + +```toml title="pyproject.toml" [build-system] requires = ["uv_build>=0.7.17,<0.8.0"] build-backend = "uv_build" ``` -!!! important +!!! note - The uv build backend follows the same [versioning policy](../reference/policies/versioning.md), - setting an upper bound on the `uv_build` version ensures that the package continues to build in - the future. + The uv build backend follows the same [versioning policy](../reference/policies/versioning.md) + as uv. Including an upper bound on the `uv_build` version ensures that your package continues to + build correctly as new versions are released. -You can also create a new project that uses the uv build backend with `uv init`: +To create a new project that uses the uv build backend, use `uv init`: -```shell -uv init --build-backend uv +```console +$ uv init --build-backend uv ``` -`uv_build` is a separate package from uv, optimized for portability and small binary size. The `uv` -command includes a copy of the build backend, so when running `uv build`, the same version will be -used for the build backend as for the uv process. Other build frontends, such as `python -m build`, -will choose the latest compatible `uv_build` version. +When the project is built, e.g., with [`uv build`](../guides/package.md), the uv build backend will +be used to create the source distribution and wheel. + +## Bundled build backend + +The build backend is published as a separate package (`uv_build`) that is optimized for portability +and small binary size. However, the `uv` executable also includes a copy of the build backend, which +will be used during builds performed by uv, e.g., during `uv build`, if its version is compatible +with the `uv_build` requirement. If it's not compatible, a compatible version of the `uv_build` +package will be used. Other build frontends, such as `python -m build`, will always use the +`uv_build` package, typically choosing the latest compatible version. ## Modules -The default module name is the package name in lower case with dots and dashes replaced by -underscores, and the default module location is under the `src` directory, i.e., the build backend -expects to find `src//__init__.py`. These defaults can be changed with the -`module-name` and `module-root` setting. The example below expects a module in the project root with -`PIL/__init__.py` instead: +Python packages are expected to contain one or more Python modules, which are directories containing +an `__init__.py`. By default, a single root module is expected at `src//__init__.py`. -```toml +For example, the structure for a project named `foo` would be: + +```text +pyproject.toml +src +└── foo + └── __init__.py +``` + +uv normalizes the package name to determine the default module name: the package name is lowercased +and dots and dashes are replaced with underscores, e.g., `Foo-Bar` would be converted to `foo_bar`. + +The `src/` directory is the default directory for module discovery. + +These defaults can be changed with the `module-name` and `module-root` settings. For example, to use +a `FOO` module in the root directory, as in the project structure: + +```text +pyproject.toml +FOO +└── __init__.py +``` + +The correct build configuration would be: + +```toml title="pyproject.toml" [tool.uv.build-backend] -module-name = "PIL" +module-name = "FOO" module-root = "" ``` -For a namespace packages, the path can be dotted. The example below expects to find a -`src/cloud/db/schema/__init__.py`: +## Namespace packages -```toml -[tool.uv.build-backend] -module-name = "cloud.db.schema" +Namespace packages are intended for use-cases where multiple packages write modules into a shared +namespace. + +Namespace package modules are identified by a `.` in the `module-name`. For example, to package the +module `bar` in the shared namespace `foo`, the project structure would be: + +```text +pyproject.toml +src +└── foo + └── bar + └── __init__.py ``` -Complex namespaces with more than one root module can be built by setting the `namespace` option, -which allows more than one root `__init__.py`: +And the `module-name` configuration would be: -```toml +```toml title="pyproject.toml" +[tool.uv.build-backend] +module-name = "foo.bar" +``` + +!!! important + + The `__init__.py` file is not included in `foo`, since it's the shared namespace module. + +It's also possible to have a complex namespace package with more than one root module, e.g., with +the project structure: + +```text +pyproject.toml +src +├── foo +│   └── __init__.py +└── bar + └── __init__.py +``` + +While we do not recommend this structure (i.e., you should use a workspace with multiple packages +instead), it is supported via the `namespace` option: + +```toml title="pyproject.toml" [tool.uv.build-backend] namespace = true ``` -The build backend supports building stubs packages with a `-stubs` suffix on the package or module -name, including for namespace packages. +## Stub packages -## Include and exclude configuration +The build backend also supports building type stub packages, which are identified by the `-stubs` +suffix on the package or module name, e.g., `foo-stubs`. The module name for type stub packages must +end in `-stubs`, so uv will not normalize the `-` to an underscore. Additionally, uv will search for +a `__init__.pyi` file. For example, the project structure would be: -To select which files to include in the source distribution, uv first adds the included files and +```text +pyproject.toml +src +└── foo-stubs + └── __init__.pyi +``` + +Type stub modules are also supported for [namespace packages](#namespace-packages). + +## File inclusion and exclusion + +The build backend is responsible for determining which files in a source tree should be packaged +into the distributions. + +To determine which files to include in a source distribution, uv first adds the included files and directories, then removes the excluded files and directories. This means that exclusions always take precedence over inclusions. -When building the source distribution, the following files and directories are included: +By default, uv excludes `__pycache__`, `*.pyc`, and `*.pyo`. -- `pyproject.toml` -- The module under `tool.uv.build-backend.module-root`, by default - `src//**`. -- `project.license-files` and `project.readme`. -- All directories under `tool.uv.build-backend.data`. -- All patterns from `tool.uv.build-backend.source-include`. +When building a source distribution, the following files and directories are included: -From these, `tool.uv.build-backend.source-exclude` and the default excludes are removed. +- The `pyproject.toml` +- The [module](#modules) under + [`tool.uv.build-backend.module-root`](../reference/settings.md#build-backend_module-root). +- The files referenced by `project.license-files` and `project.readme`. +- All directories under [`tool.uv.build-backend.data`](../reference/settings.md#build-backend_data). +- All files matching patterns from + [`tool.uv.build-backend.source-include`](../reference/settings.md#build-backend_source-include). -When building the wheel, the following files and directories are included: +From these, items matching +[`tool.uv.build-backend.source-exclude`](../reference/settings.md#build-backend_source-exclude) and +the [default excludes](../reference/settings.md#build-backend_default-excludes) are removed. -- The module under `tool.uv.build-backend.module-root`, by default - `src//**`. -- `project.license-files` and `project.readme`, as part of the project metadata. -- Each directory under `tool.uv.build-backend.data`, as data directories. +When building a wheel, the following files and directories are included: -From these, `tool.uv.build-backend.source-exclude`, `tool.uv.build-backend.wheel-exclude` and the -default excludes are removed. The source dist excludes are applied to avoid source tree to wheel +- The [module](#modules) under + [`tool.uv.build-backend.module-root`](../reference/settings.md#build-backend_module-root) +- The files referenced by `project.license-files`, which are copied into the `.dist-info` directory. +- The `project.readme`, which is copied into the project metadata. +- All directories under [`tool.uv.build-backend.data`](../reference/settings.md#build-backend_data), + which are copied into the `.data` directory. + +From these, +[`tool.uv.build-backend.source-exclude`](../reference/settings.md#build-backend_source-exclude), +[`tool.uv.build-backend.wheel-exclude`](../reference/settings.md#build-backend_wheel-exclude) and +the default excludes are removed. The source dist excludes are applied to avoid source tree to wheel source builds including more files than source tree to source distribution to wheel build. There are no specific wheel includes. There must only be one top level module, and all data files @@ -106,20 +196,20 @@ must either be under the module root or in the appropriate [data directory](../reference/settings.md#build-backend_data). Most packages store small data in the module root alongside the source code. -## Include and exclude syntax +### Include and exclude syntax -Includes are anchored, which means that `pyproject.toml` includes only -`/pyproject.toml`. For example, `assets/**/sample.csv` includes all `sample.csv` files -in `/assets` or any child directory. To recursively include all files under a -directory, use a `/**` suffix, e.g. `src/**`. +Includes are anchored, which means that `pyproject.toml` includes only `/pyproject.toml` and +not `/bar/pyproject.toml`. To recursively include all files under a directory, use a `/**` +suffix, e.g. `src/**`. Recursive inclusions are also anchored, e.g., `assets/**/sample.csv` includes +all `sample.csv` files in `/assets` or any of its children. !!! note For performance and reproducibility, avoid patterns without an anchor such as `**/sample.csv`. Excludes are not anchored, which means that `__pycache__` excludes all directories named -`__pycache__` and its children anywhere. To anchor a directory, use a `/` prefix, e.g., `/dist` will -exclude only `/dist`. +`__pycache__` regardless of its parent directory. All children of an exclusion are excluded as well. +To anchor a directory, use a `/` prefix, e.g., `/dist` will exclude only `/dist`. All fields accepting patterns use the reduced portable glob syntax from [PEP 639](https://peps.python.org/pep-0639/#add-license-FILES-key), with the addition that From 3774a656d7c864370408b8c253909af3b83296d5 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 1 Jul 2025 08:18:01 -0400 Subject: [PATCH 117/349] Use parsed URLs for conflicting URL error message (#14380) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary There's a good example of the downside of using verbatim URLs here: https://github.com/astral-sh/uv/pull/14197#discussion_r2163599625 (we show two relative paths that point to the same directory, but it's not clear from the error message). The diff: ``` 2 2 │ ----- stdout ----- 3 3 │ 4 4 │ ----- stderr ----- 5 5 │ error: Requirements contain conflicting URLs for package `library` in all marker environments: 6 │-- ../../library 7 │-- ./library 6 │+- file://[TEMP_DIR]/library 7 │+- file://[TEMP_DIR]/library (editable) ``` --- crates/uv-resolver/src/error.rs | 16 ++++++++++++---- crates/uv-resolver/src/fork_indexes.rs | 2 +- crates/uv-resolver/src/fork_urls.rs | 7 ++----- crates/uv-resolver/src/resolver/urls.rs | 5 ++--- crates/uv/tests/it/pip_compile.rs | 6 +++--- crates/uv/tests/it/pip_install.rs | 6 +++--- 6 files changed, 23 insertions(+), 19 deletions(-) diff --git a/crates/uv-resolver/src/error.rs b/crates/uv-resolver/src/error.rs index 2033ed0c0..e327e8562 100644 --- a/crates/uv-resolver/src/error.rs +++ b/crates/uv-resolver/src/error.rs @@ -17,6 +17,8 @@ use uv_normalize::{ExtraName, InvalidNameError, PackageName}; use uv_pep440::{LocalVersionSlice, LowerBound, Version, VersionSpecifier}; use uv_pep508::{MarkerEnvironment, MarkerExpression, MarkerTree, MarkerValueVersion}; use uv_platform_tags::Tags; +use uv_pypi_types::ParsedUrl; +use uv_redacted::DisplaySafeUrl; use uv_static::EnvVars; use crate::candidate_selector::CandidateSelector; @@ -56,11 +58,14 @@ pub enum ResolveError { } else { format!(" in {env}") }, - urls.join("\n- "), + urls.iter() + .map(|url| format!("{}{}", DisplaySafeUrl::from(url.clone()), if url.is_editable() { " (editable)" } else { "" })) + .collect::>() + .join("\n- ") )] ConflictingUrls { package_name: PackageName, - urls: Vec, + urls: Vec, env: ResolverEnvironment, }, @@ -71,11 +76,14 @@ pub enum ResolveError { } else { format!(" in {env}") }, - indexes.join("\n- "), + indexes.iter() + .map(std::string::ToString::to_string) + .collect::>() + .join("\n- ") )] ConflictingIndexesForEnvironment { package_name: PackageName, - indexes: Vec, + indexes: Vec, env: ResolverEnvironment, }, diff --git a/crates/uv-resolver/src/fork_indexes.rs b/crates/uv-resolver/src/fork_indexes.rs index 5b39fb626..7283b5cbc 100644 --- a/crates/uv-resolver/src/fork_indexes.rs +++ b/crates/uv-resolver/src/fork_indexes.rs @@ -24,7 +24,7 @@ impl ForkIndexes { ) -> Result<(), ResolveError> { if let Some(previous) = self.0.insert(package_name.clone(), index.clone()) { if &previous != index { - let mut conflicts = vec![previous.url.to_string(), index.url.to_string()]; + let mut conflicts = vec![previous.url, index.url.clone()]; conflicts.sort(); return Err(ResolveError::ConflictingIndexesForEnvironment { package_name: package_name.clone(), diff --git a/crates/uv-resolver/src/fork_urls.rs b/crates/uv-resolver/src/fork_urls.rs index dc1b067c4..dd69f7bf7 100644 --- a/crates/uv-resolver/src/fork_urls.rs +++ b/crates/uv-resolver/src/fork_urls.rs @@ -2,7 +2,6 @@ use std::collections::hash_map::Entry; use rustc_hash::FxHashMap; -use uv_distribution_types::Verbatim; use uv_normalize::PackageName; use uv_pypi_types::VerbatimParsedUrl; @@ -34,10 +33,8 @@ impl ForkUrls { match self.0.entry(package_name.clone()) { Entry::Occupied(previous) => { if previous.get() != url { - let mut conflicting_url = vec![ - previous.get().verbatim.verbatim().to_string(), - url.verbatim.verbatim().to_string(), - ]; + let mut conflicting_url = + vec![previous.get().parsed_url.clone(), url.parsed_url.clone()]; conflicting_url.sort(); return Err(ResolveError::ConflictingUrls { package_name: package_name.clone(), diff --git a/crates/uv-resolver/src/resolver/urls.rs b/crates/uv-resolver/src/resolver/urls.rs index a41f33371..73d190b4a 100644 --- a/crates/uv-resolver/src/resolver/urls.rs +++ b/crates/uv-resolver/src/resolver/urls.rs @@ -4,7 +4,6 @@ use same_file::is_same_file; use tracing::debug; use uv_cache_key::CanonicalUrl; -use uv_distribution_types::Verbatim; use uv_git::GitResolver; use uv_normalize::PackageName; use uv_pep508::{MarkerTree, VerbatimUrl}; @@ -170,8 +169,8 @@ impl Urls { let [allowed_url] = matching_urls.as_slice() else { let mut conflicting_urls: Vec<_> = matching_urls .into_iter() - .map(|parsed_url| parsed_url.verbatim.verbatim().to_string()) - .chain(std::iter::once(verbatim_url.verbatim().to_string())) + .map(|parsed_url| parsed_url.parsed_url.clone()) + .chain(std::iter::once(parsed_url.clone())) .collect(); conflicting_urls.sort(); return Err(ResolveError::ConflictingUrls { diff --git a/crates/uv/tests/it/pip_compile.rs b/crates/uv/tests/it/pip_compile.rs index 79a98a3bf..c80027761 100644 --- a/crates/uv/tests/it/pip_compile.rs +++ b/crates/uv/tests/it/pip_compile.rs @@ -2909,16 +2909,16 @@ fn incompatible_narrowed_url_dependency() -> Result<()> { "})?; uv_snapshot!(context.filters(), context.pip_compile() - .arg("requirements.in"), @r###" + .arg("requirements.in"), @r" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- error: Requirements contain conflicting URLs for package `uv-public-pypackage`: - - git+https://github.com/astral-test/uv-public-pypackage@b270df1a2fb5d012294e9aaf05e7e0bab1e6a389 - git+https://github.com/astral-test/uv-public-pypackage@test-branch - "### + - git+https://github.com/astral-test/uv-public-pypackage@b270df1a2fb5d012294e9aaf05e7e0bab1e6a389 + " ); Ok(()) diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index 090fb03a9..91da3ce81 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -1515,16 +1515,16 @@ fn install_editable_incompatible_constraint_url() -> Result<()> { .arg("-e") .arg(context.workspace_root.join("scripts/packages/black_editable")) .arg("--constraint") - .arg("constraints.txt"), @r###" + .arg("constraints.txt"), @r" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- error: Requirements contain conflicting URLs for package `black`: - - [WORKSPACE]/scripts/packages/black_editable + - file://[WORKSPACE]/scripts/packages/black_editable (editable) - https://files.pythonhosted.org/packages/0f/89/294c9a6b6c75a08da55e9d05321d0707e9418735e3062b12ef0f54c33474/black-24.4.2-py3-none-any.whl - "### + " ); Ok(()) From 43745d2ecfd34c07e17d952d982e8ef6a3056bcb Mon Sep 17 00:00:00 2001 From: konsti Date: Tue, 1 Jul 2025 17:48:48 +0200 Subject: [PATCH 118/349] Fix equals-star and tilde-equals with `python_version` and `python_full_version` (#14271) The marker display code assumes that all versions are normalized, in that all trailing zeroes are stripped. This is not the case for tilde-equals and equals-star versions, where the trailing zeroes (before the `.*`) are semantically relevant. This would cause path dependent-behavior where we would get a different marker string depending on whether a version with or without a trailing zero was added to the cache first. To handle both equals-star and tilde-equals when converting `python_version` to `python_full_version` markers, we have to merge the version normalization (i.e. trimming the trailing zeroes) and the conversion both to `python_full_version` and to `Ranges`, while special casing equals-star and tilde-equals. To avoid churn in lockfiles, we only trim in the conversion to `Ranges` for markers, but keep using untrimmed versions for requires-python. (Note that this behavior is technically also path dependent, as versions with and without trailing zeroes have the same Hash and Eq. E.q., `requires-python == ">= 3.10.0"` and `requires-python == ">= 3.10"` in the same workspace could lead to either value in `uv.lock`, and which one it is could change if we make unrelated (performance) changes. Always trimming however definitely changes lockfiles, a churn I wouldn't do outside another breaking or lockfile-changing change.) Nevertheless, there is a change for users who have `requires-python = "~= 3.12.0"` in their `pyproject.toml`, as this now hits the correct normalization path. Fixes #14231 Fixes #14270 --- .../src/requires_python.rs | 6 +- crates/uv-pep440/src/version.rs | 18 +++ crates/uv-pep440/src/version_ranges.rs | 81 ++++++------ crates/uv-pep440/src/version_specifier.rs | 92 ++++++++++--- crates/uv-pep508/src/lib.rs | 1 + crates/uv-pep508/src/marker/algebra.rs | 122 ++++++++++-------- crates/uv-pep508/src/marker/simplify.rs | 42 +++--- crates/uv-pep508/src/marker/tree.rs | 117 +++++++++++++++-- crates/uv-pypi-types/src/conflicts.rs | 4 +- crates/uv-pypi-types/src/identifier.rs | 1 + ...__line-endings-poetry-with-hashes.txt.snap | 11 +- ...t__test__parse-poetry-with-hashes.txt.snap | 11 +- crates/uv/tests/it/lock.rs | 39 +++++- 13 files changed, 382 insertions(+), 163 deletions(-) diff --git a/crates/uv-distribution-types/src/requires_python.rs b/crates/uv-distribution-types/src/requires_python.rs index 786aed83a..cedfff843 100644 --- a/crates/uv-distribution-types/src/requires_python.rs +++ b/crates/uv-distribution-types/src/requires_python.rs @@ -71,7 +71,7 @@ impl RequiresPython { // Warn if there’s exactly one `~=` specifier without a patch. if let [spec] = &specs[..] { if spec.is_tilde_without_patch() { - if let Some((lo_b, hi_b)) = release_specifier_to_range(spec.clone()) + if let Some((lo_b, hi_b)) = release_specifier_to_range(spec.clone(), false) .bounding_range() .map(|(l, u)| (l.cloned(), u.cloned())) { @@ -80,8 +80,8 @@ impl RequiresPython { warn_user_once!( "The release specifier (`{spec}`) contains a compatible release \ match without a patch version. This will be interpreted as \ - `{lo_spec}, {hi_spec}`. Did you mean `{spec}.0` to freeze the minor \ - version?" + `{lo_spec}, {hi_spec}`. Did you mean `{spec}.0` to freeze the \ + minor version?" ); } } diff --git a/crates/uv-pep440/src/version.rs b/crates/uv-pep440/src/version.rs index 1ef0badf2..a496f95a2 100644 --- a/crates/uv-pep440/src/version.rs +++ b/crates/uv-pep440/src/version.rs @@ -610,6 +610,24 @@ impl Version { Self::new(self.release().iter().copied()) } + /// Return the version with any segments apart from the release removed, with trailing zeroes + /// trimmed. + #[inline] + #[must_use] + pub fn only_release_trimmed(&self) -> Self { + if let Some(last_non_zero) = self.release().iter().rposition(|segment| *segment != 0) { + if last_non_zero == self.release().len() { + // Already trimmed. + self.clone() + } else { + Self::new(self.release().iter().take(last_non_zero + 1).copied()) + } + } else { + // `0` is a valid version. + Self::new([0]) + } + } + /// Return the version with trailing `.0` release segments removed. /// /// # Panics diff --git a/crates/uv-pep440/src/version_ranges.rs b/crates/uv-pep440/src/version_ranges.rs index 26cd048d3..38038ffcf 100644 --- a/crates/uv-pep440/src/version_ranges.rs +++ b/crates/uv-pep440/src/version_ranges.rs @@ -130,10 +130,11 @@ impl From for Ranges { /// /// See: pub fn release_specifiers_to_ranges(specifiers: VersionSpecifiers) -> Ranges { - specifiers - .into_iter() - .map(release_specifier_to_range) - .fold(Ranges::full(), |acc, range| acc.intersection(&range)) + let mut range = Ranges::full(); + for specifier in specifiers { + range = range.intersection(&release_specifier_to_range(specifier, false)); + } + range } /// Convert the [`VersionSpecifier`] to a PubGrub-compatible version range, using release-only @@ -147,67 +148,57 @@ pub fn release_specifiers_to_ranges(specifiers: VersionSpecifiers) -> Ranges -pub fn release_specifier_to_range(specifier: VersionSpecifier) -> Ranges { +pub fn release_specifier_to_range(specifier: VersionSpecifier, trim: bool) -> Ranges { let VersionSpecifier { operator, version } = specifier; + // Note(konsti): We switched strategies to trimmed for the markers, but we don't want to cause + // churn in lockfile requires-python, so we only trim for markers. + let version_trimmed = if trim { + version.only_release_trimmed() + } else { + version.only_release() + }; match operator { - Operator::Equal => { - let version = version.only_release(); - Ranges::singleton(version) - } - Operator::ExactEqual => { - let version = version.only_release(); - Ranges::singleton(version) - } - Operator::NotEqual => { - let version = version.only_release(); - Ranges::singleton(version).complement() - } + // Trailing zeroes are not semantically relevant. + Operator::Equal => Ranges::singleton(version_trimmed), + Operator::ExactEqual => Ranges::singleton(version_trimmed), + Operator::NotEqual => Ranges::singleton(version_trimmed).complement(), + Operator::LessThan => Ranges::strictly_lower_than(version_trimmed), + Operator::LessThanEqual => Ranges::lower_than(version_trimmed), + Operator::GreaterThan => Ranges::strictly_higher_than(version_trimmed), + Operator::GreaterThanEqual => Ranges::higher_than(version_trimmed), + + // Trailing zeroes are semantically relevant. Operator::TildeEqual => { let release = version.release(); let [rest @ .., last, _] = &*release else { unreachable!("~= must have at least two segments"); }; let upper = Version::new(rest.iter().chain([&(last + 1)])); - let version = version.only_release(); - Ranges::from_range_bounds(version..upper) - } - Operator::LessThan => { - let version = version.only_release(); - Ranges::strictly_lower_than(version) - } - Operator::LessThanEqual => { - let version = version.only_release(); - Ranges::lower_than(version) - } - Operator::GreaterThan => { - let version = version.only_release(); - Ranges::strictly_higher_than(version) - } - Operator::GreaterThanEqual => { - let version = version.only_release(); - Ranges::higher_than(version) + Ranges::from_range_bounds(version_trimmed..upper) } Operator::EqualStar => { - let low = version.only_release(); + // For (not-)equal-star, trailing zeroes are still before the star. + let low_full = version.only_release(); let high = { - let mut high = low.clone(); + let mut high = low_full.clone(); let mut release = high.release().to_vec(); *release.last_mut().unwrap() += 1; high = high.with_release(release); high }; - Ranges::from_range_bounds(low..high) + Ranges::from_range_bounds(version..high) } Operator::NotEqualStar => { - let low = version.only_release(); + // For (not-)equal-star, trailing zeroes are still before the star. + let low_full = version.only_release(); let high = { - let mut high = low.clone(); + let mut high = low_full.clone(); let mut release = high.release().to_vec(); *release.last_mut().unwrap() += 1; high = high.with_release(release); high }; - Ranges::from_range_bounds(low..high).complement() + Ranges::from_range_bounds(version..high).complement() } } } @@ -222,8 +213,8 @@ impl LowerBound { /// These bounds use release-only semantics when comparing versions. pub fn new(bound: Bound) -> Self { Self(match bound { - Bound::Included(version) => Bound::Included(version.only_release()), - Bound::Excluded(version) => Bound::Excluded(version.only_release()), + Bound::Included(version) => Bound::Included(version.only_release_trimmed()), + Bound::Excluded(version) => Bound::Excluded(version.only_release_trimmed()), Bound::Unbounded => Bound::Unbounded, }) } @@ -357,8 +348,8 @@ impl UpperBound { /// These bounds use release-only semantics when comparing versions. pub fn new(bound: Bound) -> Self { Self(match bound { - Bound::Included(version) => Bound::Included(version.only_release()), - Bound::Excluded(version) => Bound::Excluded(version.only_release()), + Bound::Included(version) => Bound::Included(version.only_release_trimmed()), + Bound::Excluded(version) => Bound::Excluded(version.only_release_trimmed()), Bound::Unbounded => Bound::Unbounded, }) } diff --git a/crates/uv-pep440/src/version_specifier.rs b/crates/uv-pep440/src/version_specifier.rs index 19acff2eb..bbbe440bf 100644 --- a/crates/uv-pep440/src/version_specifier.rs +++ b/crates/uv-pep440/src/version_specifier.rs @@ -80,24 +80,38 @@ impl VersionSpecifiers { // Add specifiers for the holes between the bounds. for (lower, upper) in bounds { - match (next, lower) { + let specifier = match (next, lower) { // Ex) [3.7, 3.8.5), (3.8.5, 3.9] -> >=3.7,!=3.8.5,<=3.9 (Bound::Excluded(prev), Bound::Excluded(lower)) if prev == lower => { - specifiers.push(VersionSpecifier::not_equals_version(prev.clone())); + Some(VersionSpecifier::not_equals_version(prev.clone())) } // Ex) [3.7, 3.8), (3.8, 3.9] -> >=3.7,!=3.8.*,<=3.9 - (Bound::Excluded(prev), Bound::Included(lower)) - if prev.release().len() == 2 - && *lower.release() == [prev.release()[0], prev.release()[1] + 1] => - { - specifiers.push(VersionSpecifier::not_equals_star_version(prev.clone())); - } - _ => { - #[cfg(feature = "tracing")] - warn!( - "Ignoring unsupported gap in `requires-python` version: {next:?} -> {lower:?}" - ); + (Bound::Excluded(prev), Bound::Included(lower)) => { + match *prev.only_release_trimmed().release() { + [major] if *lower.only_release_trimmed().release() == [major, 1] => { + Some(VersionSpecifier::not_equals_star_version(Version::new([ + major, 0, + ]))) + } + [major, minor] + if *lower.only_release_trimmed().release() == [major, minor + 1] => + { + Some(VersionSpecifier::not_equals_star_version(Version::new([ + major, minor, + ]))) + } + _ => None, + } } + _ => None, + }; + if let Some(specifier) = specifier { + specifiers.push(specifier); + } else { + #[cfg(feature = "tracing")] + warn!( + "Ignoring unsupported gap in `requires-python` version: {next:?} -> {lower:?}" + ); } next = upper; } @@ -348,6 +362,33 @@ impl VersionSpecifier { Ok(Self { operator, version }) } + /// Remove all non-release parts of the version. + /// + /// The marker decision diagram relies on the assumption that the negation of a marker tree is + /// the complement of the marker space. However, pre-release versions violate this assumption. + /// + /// For example, the marker `python_full_version > '3.9' or python_full_version <= '3.9'` + /// does not match `python_full_version == 3.9.0a0` and so cannot simplify to `true`. However, + /// its negation, `python_full_version > '3.9' and python_full_version <= '3.9'`, also does not + /// match `3.9.0a0` and simplifies to `false`, which violates the algebra decision diagrams + /// rely on. For this reason we ignore pre-release versions entirely when evaluating markers. + /// + /// Note that `python_version` cannot take on pre-release values as it is truncated to just the + /// major and minor version segments. Thus using release-only specifiers is definitely necessary + /// for `python_version` to fully simplify any ranges, such as + /// `python_version > '3.9' or python_version <= '3.9'`, which is always `true` for + /// `python_version`. For `python_full_version` however, this decision is a semantic change. + /// + /// For Python versions, the major.minor is considered the API version, so unlike the rules + /// for package versions in PEP 440, we Python `3.9.0a0` is acceptable for `>= "3.9"`. + #[must_use] + pub fn only_release(self) -> Self { + Self { + operator: self.operator, + version: self.version.only_release(), + } + } + /// `==` pub fn equals_version(version: Version) -> Self { Self { @@ -442,14 +483,23 @@ impl VersionSpecifier { (Some(VersionSpecifier::equals_version(v1.clone())), None) } // `v >= 3.7 && v < 3.8` is equivalent to `v == 3.7.*` - (Bound::Included(v1), Bound::Excluded(v2)) - if v1.release().len() == 2 - && *v2.release() == [v1.release()[0], v1.release()[1] + 1] => - { - ( - Some(VersionSpecifier::equals_star_version(v1.clone())), - None, - ) + (Bound::Included(v1), Bound::Excluded(v2)) => { + match *v1.only_release_trimmed().release() { + [major] if *v2.only_release_trimmed().release() == [major, 1] => { + let version = Version::new([major, 0]); + (Some(VersionSpecifier::equals_star_version(version)), None) + } + [major, minor] + if *v2.only_release_trimmed().release() == [major, minor + 1] => + { + let version = Version::new([major, minor]); + (Some(VersionSpecifier::equals_star_version(version)), None) + } + _ => ( + VersionSpecifier::from_lower_bound(&Bound::Included(v1.clone())), + VersionSpecifier::from_upper_bound(&Bound::Excluded(v2.clone())), + ), + } } (lower, upper) => ( VersionSpecifier::from_lower_bound(lower), diff --git a/crates/uv-pep508/src/lib.rs b/crates/uv-pep508/src/lib.rs index 46e2f3039..e2945743b 100644 --- a/crates/uv-pep508/src/lib.rs +++ b/crates/uv-pep508/src/lib.rs @@ -16,6 +16,7 @@ #![warn(missing_docs)] +#[cfg(feature = "schemars")] use std::borrow::Cow; use std::error::Error; use std::fmt::{Debug, Display, Formatter}; diff --git a/crates/uv-pep508/src/marker/algebra.rs b/crates/uv-pep508/src/marker/algebra.rs index f421a8fa3..2a3f82f27 100644 --- a/crates/uv-pep508/src/marker/algebra.rs +++ b/crates/uv-pep508/src/marker/algebra.rs @@ -172,7 +172,7 @@ impl InternerGuard<'_> { ), // Normalize `python_version` markers to `python_full_version` nodes. MarkerValueVersion::PythonVersion => { - match python_version_to_full_version(normalize_specifier(specifier)) { + match python_version_to_full_version(specifier.only_release()) { Ok(specifier) => ( Variable::Version(CanonicalMarkerValueVersion::PythonFullVersion), Edges::from_specifier(specifier), @@ -1214,7 +1214,7 @@ impl Edges { /// Returns the [`Edges`] for a version specifier. fn from_specifier(specifier: VersionSpecifier) -> Edges { - let specifier = release_specifier_to_range(normalize_specifier(specifier)); + let specifier = release_specifier_to_range(specifier.only_release(), true); Edges::Version { edges: Edges::from_range(&specifier), } @@ -1227,9 +1227,9 @@ impl Edges { let mut range: Ranges = versions .into_iter() .map(|version| { - let specifier = VersionSpecifier::equals_version(version.clone()); + let specifier = VersionSpecifier::equals_version(version.only_release()); let specifier = python_version_to_full_version(specifier)?; - Ok(release_specifier_to_range(normalize_specifier(specifier))) + Ok(release_specifier_to_range(specifier, true)) }) .flatten_ok() .collect::, NodeId>>()?; @@ -1526,57 +1526,62 @@ impl Edges { } } -// Normalize a [`VersionSpecifier`] before adding it to the tree. -fn normalize_specifier(specifier: VersionSpecifier) -> VersionSpecifier { - let (operator, version) = specifier.into_parts(); - - // The decision diagram relies on the assumption that the negation of a marker tree is - // the complement of the marker space. However, pre-release versions violate this assumption. - // - // For example, the marker `python_full_version > '3.9' or python_full_version <= '3.9'` - // does not match `python_full_version == 3.9.0a0` and so cannot simplify to `true`. However, - // its negation, `python_full_version > '3.9' and python_full_version <= '3.9'`, also does not - // match `3.9.0a0` and simplifies to `false`, which violates the algebra decision diagrams - // rely on. For this reason we ignore pre-release versions entirely when evaluating markers. - // - // Note that `python_version` cannot take on pre-release values as it is truncated to just the - // major and minor version segments. Thus using release-only specifiers is definitely necessary - // for `python_version` to fully simplify any ranges, such as `python_version > '3.9' or python_version <= '3.9'`, - // which is always `true` for `python_version`. For `python_full_version` however, this decision - // is a semantic change. - let mut release = &*version.release(); - - // Strip any trailing `0`s. - // - // The [`Version`] type ignores trailing `0`s for equality, but still preserves them in its - // [`Display`] output. We must normalize all versions by stripping trailing `0`s to remove the - // distinction between versions like `3.9` and `3.9.0`. Otherwise, their output would depend on - // which form was added to the global marker interner first. - // - // Note that we cannot strip trailing `0`s for star equality, as `==3.0.*` is different from `==3.*`. - if !operator.is_star() { - if let Some(end) = release.iter().rposition(|segment| *segment != 0) { - if end > 0 { - release = &release[..=end]; - } - } - } - - VersionSpecifier::from_version(operator, Version::new(release)).unwrap() -} - /// Returns the equivalent `python_full_version` specifier for a `python_version` specifier. /// /// Returns `Err` with a constant node if the equivalent comparison is always `true` or `false`. fn python_version_to_full_version(specifier: VersionSpecifier) -> Result { + // Trailing zeroes matter only for (not-)equals-star and tilde-equals. This means that below + // the next two blocks, we can use the trimmed release as the release. + if specifier.operator().is_star() { + // Input python_version python_full_version + // ==3.* 3.* 3.* + // ==3.0.* 3.0 3.0.* + // ==3.0.0.* 3.0 3.0.* + // ==3.9.* 3.9 3.9.* + // ==3.9.0.* 3.9 3.9.* + // ==3.9.0.0.* 3.9 3.9.* + // ==3.9.1.* FALSE FALSE + // ==3.9.1.0.* FALSE FALSE + // ==3.9.1.0.0.* FALSE FALSE + return match &*specifier.version().release() { + // `3.*` + [_major] => Ok(specifier), + // Ex) `3.9.*`, `3.9.0.*`, or `3.9.0.0.*` + [major, minor, rest @ ..] if rest.iter().all(|x| *x == 0) => { + let python_version = Version::new([major, minor]); + // Unwrap safety: A star operator with two version segments is always valid. + Ok(VersionSpecifier::from_version(*specifier.operator(), python_version).unwrap()) + } + // Ex) `3.9.1.*` or `3.9.0.1.*` + _ => Err(NodeId::FALSE), + }; + } + + if *specifier.operator() == Operator::TildeEqual { + // python_version python_full_version + // ~=3 (not possible) + // ~= 3.0 >= 3.0, < 4.0 + // ~= 3.9 >= 3.9, < 4.0 + // ~= 3.9.0 == 3.9.* + // ~= 3.9.1 FALSE + // ~= 3.9.0.0 == 3.9.* + // ~= 3.9.0.1 FALSE + return match &*specifier.version().release() { + // Ex) `3.0`, `3.7` + [_major, _minor] => Ok(specifier), + // Ex) `3.9`, `3.9.0`, or `3.9.0.0` + [major, minor, rest @ ..] if rest.iter().all(|x| *x == 0) => { + let python_version = Version::new([major, minor]); + Ok(VersionSpecifier::equals_star_version(python_version)) + } + // Ex) `3.9.1` or `3.9.0.1` + _ => Err(NodeId::FALSE), + }; + } + // Extract the major and minor version segments if the specifier contains exactly // those segments, or if it contains a major segment with an implied minor segment of `0`. - let major_minor = match *specifier.version().release() { - // For star operators, we cannot add a trailing `0`. - // - // `python_version == 3.*` is equivalent to `python_full_version == 3.*`. Adding a - // trailing `0` would result in `python_version == 3.0.*`, which is incorrect. - [_major] if specifier.operator().is_star() => return Ok(specifier), + let major_minor = match *specifier.version().only_release_trimmed().release() { // Add a trailing `0` for the minor version, which is implied. // For example, `python_version == 3` matches `3.0.1`, `3.0.2`, etc. [major] => Some((major, 0)), @@ -1614,9 +1619,10 @@ fn python_version_to_full_version(specifier: VersionSpecifier) -> Result specifier, + Operator::EqualStar | Operator::NotEqualStar | Operator::TildeEqual => { + // Handled above. + unreachable!() + } }) } else { let [major, minor, ..] = *specifier.version().release() else { @@ -1624,13 +1630,14 @@ fn python_version_to_full_version(specifier: VersionSpecifier) -> Result { + // `python_version` cannot have more than two release segments, and we know + // that the following release segments aren't purely zeroes so equality is impossible. + Operator::Equal | Operator::ExactEqual => { return Err(NodeId::FALSE); } // Similarly, inequalities are always `true`. - Operator::NotEqual | Operator::NotEqualStar => return Err(NodeId::TRUE), + Operator::NotEqual => return Err(NodeId::TRUE), // `python_version {<,<=} 3.7.8` is equivalent to `python_full_version < 3.8`. Operator::LessThan | Operator::LessThanEqual => { @@ -1641,6 +1648,11 @@ fn python_version_to_full_version(specifier: VersionSpecifier) -> Result { VersionSpecifier::greater_than_equal_version(Version::new([major, minor + 1])) } + + Operator::EqualStar | Operator::NotEqualStar | Operator::TildeEqual => { + // Handled above. + unreachable!() + } }) } } diff --git a/crates/uv-pep508/src/marker/simplify.rs b/crates/uv-pep508/src/marker/simplify.rs index 34c095b09..3dc03693a 100644 --- a/crates/uv-pep508/src/marker/simplify.rs +++ b/crates/uv-pep508/src/marker/simplify.rs @@ -64,8 +64,8 @@ fn collect_dnf( continue; } - // Detect whether the range for this edge can be simplified as a star inequality. - if let Some(specifier) = star_range_inequality(&range) { + // Detect whether the range for this edge can be simplified as a star specifier. + if let Some(specifier) = star_range_specifier(&range) { path.push(MarkerExpression::Version { key: marker.key().into(), specifier, @@ -343,22 +343,34 @@ where Some(excluded) } -/// Returns `Some` if the version expression can be simplified as a star inequality with the given -/// specifier. +/// Returns `Some` if the version range can be simplified as a star specifier. /// -/// For example, `python_full_version < '3.8' or python_full_version >= '3.9'` can be simplified to -/// `python_full_version != '3.8.*'`. -fn star_range_inequality(range: &Ranges) -> Option { +/// Only for the two bounds case not covered by [`VersionSpecifier::from_release_only_bounds`]. +/// +/// For negative ranges like `python_full_version < '3.8' or python_full_version >= '3.9'`, +/// returns `!= '3.8.*'`. +fn star_range_specifier(range: &Ranges) -> Option { + if range.iter().count() != 2 { + return None; + } + // Check for negative star range: two segments [(Unbounded, Excluded(v1)), (Included(v2), Unbounded)] let (b1, b2) = range.iter().collect_tuple()?; - - match (b1, b2) { - ((Bound::Unbounded, Bound::Excluded(v1)), (Bound::Included(v2), Bound::Unbounded)) - if v1.release().len() == 2 - && *v2.release() == [v1.release()[0], v1.release()[1] + 1] => - { - Some(VersionSpecifier::not_equals_star_version(v1.clone())) + if let ((Bound::Unbounded, Bound::Excluded(v1)), (Bound::Included(v2), Bound::Unbounded)) = + (b1, b2) + { + match *v1.only_release_trimmed().release() { + [major] if *v2.release() == [major, 1] => { + Some(VersionSpecifier::not_equals_star_version(Version::new([ + major, 0, + ]))) + } + [major, minor] if *v2.release() == [major, minor + 1] => { + Some(VersionSpecifier::not_equals_star_version(v1.clone())) + } + _ => None, } - _ => None, + } else { + None } } diff --git a/crates/uv-pep508/src/marker/tree.rs b/crates/uv-pep508/src/marker/tree.rs index 975d4ff10..5739d7c98 100644 --- a/crates/uv-pep508/src/marker/tree.rs +++ b/crates/uv-pep508/src/marker/tree.rs @@ -2271,13 +2271,13 @@ mod test { #[test] fn test_marker_simplification() { assert_false("python_version == '3.9.1'"); - assert_false("python_version == '3.9.0.*'"); assert_true("python_version != '3.9.1'"); - // Technically these is are valid substring comparison, but we do not allow them. - // e.g., using a version with patch components with `python_version` is considered - // impossible to satisfy since the value it is truncated at the minor version - assert_false("python_version in '3.9.0'"); + // This is an edge case that happens to be supported, but is not critical to support. + assert_simplifies( + "python_version in '3.9.0'", + "python_full_version == '3.9.*'", + ); // e.g., using a version that is not PEP 440 compliant is considered arbitrary assert_true("python_version in 'foo'"); // e.g., including `*` versions, which would require tracking a version specifier @@ -2287,16 +2287,25 @@ mod test { assert_true("python_version in '3.9,3.10'"); assert_true("python_version in '3.9 or 3.10'"); - // e.g, when one of the values cannot be true - // TODO(zanieb): This seems like a quirk of the `python_full_version` normalization, this - // should just act as though the patch version isn't present - assert_false("python_version in '3.9 3.10.0 3.11'"); + // This is an edge case that happens to be supported, but is not critical to support. + assert_simplifies( + "python_version in '3.9 3.10.0 3.11'", + "python_full_version >= '3.9' and python_full_version < '3.12'", + ); assert_simplifies("python_version == '3.9'", "python_full_version == '3.9.*'"); assert_simplifies( "python_version == '3.9.0'", "python_full_version == '3.9.*'", ); + assert_simplifies( + "python_version == '3.9.0.*'", + "python_full_version == '3.9.*'", + ); + assert_simplifies( + "python_version == '3.*'", + "python_full_version >= '3' and python_full_version < '4'", + ); // ` in` // e.g., when the range is not contiguous @@ -2528,6 +2537,68 @@ mod test { ); } + #[test] + fn test_python_version_equal_star() { + // Input, equivalent with python_version, equivalent with python_full_version + let cases = [ + ("3.*", "3.*", "3.*"), + ("3.0.*", "3.0", "3.0.*"), + ("3.0.0.*", "3.0", "3.0.*"), + ("3.9.*", "3.9", "3.9.*"), + ("3.9.0.*", "3.9", "3.9.*"), + ("3.9.0.0.*", "3.9", "3.9.*"), + ]; + for (input, equal_python_version, equal_python_full_version) in cases { + assert_eq!( + m(&format!("python_version == '{input}'")), + m(&format!("python_version == '{equal_python_version}'")), + "{input} {equal_python_version}" + ); + assert_eq!( + m(&format!("python_version == '{input}'")), + m(&format!( + "python_full_version == '{equal_python_full_version}'" + )), + "{input} {equal_python_full_version}" + ); + } + + let cases_false = ["3.9.1.*", "3.9.1.0.*", "3.9.1.0.0.*"]; + for input in cases_false { + assert!( + m(&format!("python_version == '{input}'")).is_false(), + "{input}" + ); + } + } + + #[test] + fn test_tilde_equal_normalization() { + assert_eq!( + m("python_version ~= '3.10.0'"), + m("python_version >= '3.10.0' and python_version < '3.11.0'") + ); + + // Two digit versions such as `python_version` get padded with a zero, so they can never + // match + assert_eq!(m("python_version ~= '3.10.1'"), MarkerTree::FALSE); + + assert_eq!( + m("python_version ~= '3.10'"), + m("python_version >= '3.10' and python_version < '4.0'") + ); + + assert_eq!( + m("python_full_version ~= '3.10.0'"), + m("python_full_version >= '3.10.0' and python_full_version < '3.11.0'") + ); + + assert_eq!( + m("python_full_version ~= '3.10'"), + m("python_full_version >= '3.10' and python_full_version < '4.0'") + ); + } + /// This tests marker implication. /// /// Specifically, these test cases come from a [bug] where `foo` and `bar` @@ -3324,4 +3395,32 @@ mod test { ] ); } + + /// Case a: There is no version `3` (no trailing zero) in the interner yet. + #[test] + fn marker_normalization_a() { + let left_tree = m("python_version == '3.0.*'"); + let left = left_tree.try_to_string().unwrap(); + let right = "python_full_version == '3.0.*'"; + assert_eq!(left, right, "{left} != {right}"); + } + + /// Case b: There is already a version `3` (no trailing zero) in the interner. + #[test] + fn marker_normalization_b() { + m("python_version >= '3' and python_version <= '3.0'"); + + let left_tree = m("python_version == '3.0.*'"); + let left = left_tree.try_to_string().unwrap(); + let right = "python_full_version == '3.0.*'"; + assert_eq!(left, right, "{left} != {right}"); + } + + #[test] + fn marker_normalization_c() { + let left_tree = MarkerTree::from_str("python_version == '3.10.0.*'").unwrap(); + let left = left_tree.try_to_string().unwrap(); + let right = "python_full_version == '3.10.*'"; + assert_eq!(left, right, "{left} != {right}"); + } } diff --git a/crates/uv-pypi-types/src/conflicts.rs b/crates/uv-pypi-types/src/conflicts.rs index dd1e96b77..81064955a 100644 --- a/crates/uv-pypi-types/src/conflicts.rs +++ b/crates/uv-pypi-types/src/conflicts.rs @@ -3,7 +3,9 @@ use petgraph::{ graph::{DiGraph, NodeIndex}, }; use rustc_hash::{FxHashMap, FxHashSet}; -use std::{borrow::Cow, collections::BTreeSet, hash::Hash, rc::Rc}; +#[cfg(feature = "schemars")] +use std::borrow::Cow; +use std::{collections::BTreeSet, hash::Hash, rc::Rc}; use uv_normalize::{ExtraName, GroupName, PackageName}; use crate::dependency_groups::{DependencyGroupSpecifier, DependencyGroups}; diff --git a/crates/uv-pypi-types/src/identifier.rs b/crates/uv-pypi-types/src/identifier.rs index 972a327ae..47439f2c9 100644 --- a/crates/uv-pypi-types/src/identifier.rs +++ b/crates/uv-pypi-types/src/identifier.rs @@ -1,4 +1,5 @@ use serde::{Serialize, Serializer}; +#[cfg(feature = "schemars")] use std::borrow::Cow; use std::fmt::Display; use std::str::FromStr; diff --git a/crates/uv-requirements-txt/src/snapshots/uv_requirements_txt__test__line-endings-poetry-with-hashes.txt.snap b/crates/uv-requirements-txt/src/snapshots/uv_requirements_txt__test__line-endings-poetry-with-hashes.txt.snap index ad5e2a0e6..e13ab75b7 100644 --- a/crates/uv-requirements-txt/src/snapshots/uv_requirements_txt__test__line-endings-poetry-with-hashes.txt.snap +++ b/crates/uv-requirements-txt/src/snapshots/uv_requirements_txt__test__line-endings-poetry-with-hashes.txt.snap @@ -1,6 +1,7 @@ --- source: crates/uv-requirements-txt/src/lib.rs expression: actual +snapshot_kind: text --- RequirementsTxt { requirements: [ @@ -23,7 +24,7 @@ RequirementsTxt { ), ), ), - marker: python_full_version >= '3.8' and python_full_version < '4.0', + marker: python_full_version >= '3.8' and python_full_version < '4', origin: Some( File( "/poetry-with-hashes.txt", @@ -54,7 +55,7 @@ RequirementsTxt { ), ), ), - marker: python_full_version >= '3.8' and python_full_version < '4.0', + marker: python_full_version >= '3.8' and python_full_version < '4', origin: Some( File( "/poetry-with-hashes.txt", @@ -85,7 +86,7 @@ RequirementsTxt { ), ), ), - marker: python_full_version >= '3.8' and python_full_version < '4.0' and sys_platform == 'win32', + marker: python_full_version >= '3.8' and python_full_version < '4' and sys_platform == 'win32', origin: Some( File( "/poetry-with-hashes.txt", @@ -116,7 +117,7 @@ RequirementsTxt { ), ), ), - marker: python_full_version >= '3.8' and python_full_version < '4.0', + marker: python_full_version >= '3.8' and python_full_version < '4', origin: Some( File( "/poetry-with-hashes.txt", @@ -148,7 +149,7 @@ RequirementsTxt { ), ), ), - marker: python_full_version >= '3.8' and python_full_version < '4.0', + marker: python_full_version >= '3.8' and python_full_version < '4', origin: Some( File( "/poetry-with-hashes.txt", diff --git a/crates/uv-requirements-txt/src/snapshots/uv_requirements_txt__test__parse-poetry-with-hashes.txt.snap b/crates/uv-requirements-txt/src/snapshots/uv_requirements_txt__test__parse-poetry-with-hashes.txt.snap index ad5e2a0e6..e13ab75b7 100644 --- a/crates/uv-requirements-txt/src/snapshots/uv_requirements_txt__test__parse-poetry-with-hashes.txt.snap +++ b/crates/uv-requirements-txt/src/snapshots/uv_requirements_txt__test__parse-poetry-with-hashes.txt.snap @@ -1,6 +1,7 @@ --- source: crates/uv-requirements-txt/src/lib.rs expression: actual +snapshot_kind: text --- RequirementsTxt { requirements: [ @@ -23,7 +24,7 @@ RequirementsTxt { ), ), ), - marker: python_full_version >= '3.8' and python_full_version < '4.0', + marker: python_full_version >= '3.8' and python_full_version < '4', origin: Some( File( "/poetry-with-hashes.txt", @@ -54,7 +55,7 @@ RequirementsTxt { ), ), ), - marker: python_full_version >= '3.8' and python_full_version < '4.0', + marker: python_full_version >= '3.8' and python_full_version < '4', origin: Some( File( "/poetry-with-hashes.txt", @@ -85,7 +86,7 @@ RequirementsTxt { ), ), ), - marker: python_full_version >= '3.8' and python_full_version < '4.0' and sys_platform == 'win32', + marker: python_full_version >= '3.8' and python_full_version < '4' and sys_platform == 'win32', origin: Some( File( "/poetry-with-hashes.txt", @@ -116,7 +117,7 @@ RequirementsTxt { ), ), ), - marker: python_full_version >= '3.8' and python_full_version < '4.0', + marker: python_full_version >= '3.8' and python_full_version < '4', origin: Some( File( "/poetry-with-hashes.txt", @@ -148,7 +149,7 @@ RequirementsTxt { ), ), ), - marker: python_full_version >= '3.8' and python_full_version < '4.0', + marker: python_full_version >= '3.8' and python_full_version < '4', origin: Some( File( "/poetry-with-hashes.txt", diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index ff5465042..1c375b2e3 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -5013,14 +5013,14 @@ fn lock_requires_python_not_equal() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.lock(), @r###" + uv_snapshot!(context.filters(), context.lock(), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Resolved 2 packages in [TIME] - "###); + "); let lock = fs_err::read_to_string(&lockfile).unwrap(); @@ -27522,7 +27522,7 @@ fn windows_arm() -> Result<()> { lock, @r#" version = 1 revision = 2 - requires-python = ">=3.12.[X], <3.13" + requires-python = "==3.12.*" resolution-markers = [ "platform_machine == 'x86_64' and sys_platform == 'linux'", "platform_machine == 'AMD64' and sys_platform == 'win32'", @@ -27599,7 +27599,7 @@ fn windows_amd64_required() -> Result<()> { lock, @r#" version = 1 revision = 2 - requires-python = ">=3.12.[X], <3.13" + requires-python = "==3.12.*" required-markers = [ "platform_machine == 'x86' and sys_platform == 'win32'", "platform_machine == 'AMD64' and sys_platform == 'win32'", @@ -28725,3 +28725,34 @@ fn lock_prefix_match() -> Result<()> { Ok(()) } + +/// Regression test for . +#[test] +fn test_tilde_equals_python_version() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "debug" + version = "0.1.0" + requires-python = ">=3.9" + dependencies = [ + "anyio==4.2.0; python_full_version >= '3.11'", + "anyio==4.3.0; python_full_version ~= '3.10.0'", + ] + "#, + )?; + + uv_snapshot!(context.filters(), context.lock(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 7 packages in [TIME] + "); + + Ok(()) +} From 9af3e9b6ec5fd8d84644d5ecaf45be273ed21a3a Mon Sep 17 00:00:00 2001 From: konsti Date: Tue, 1 Jul 2025 18:00:30 +0200 Subject: [PATCH 119/349] Remove unnecessary codspeed deps (#14396) See https://github.com/CodSpeedHQ/codspeed-rust/pull/108 --- Cargo.lock | 135 ++----------------------------------- crates/uv-bench/Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 130 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0c5bab5a7..f961b2b5a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -690,9 +690,9 @@ checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "codspeed" -version = "3.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b41c7ae78309311a5ce5f31dbdae2d7d6ad68b057163eaa23e05b68c9ffac8" +checksum = "922018102595f6668cdd09c03f4bff2d951ce2318c6dca4fe11bdcb24b65b2bf" dependencies = [ "anyhow", "bincode", @@ -708,9 +708,9 @@ dependencies = [ [[package]] name = "codspeed-criterion-compat" -version = "3.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2c699a447da1c442a81e14f01363f44ffd68eb13fedfd3ed13ce5cf30f738d9" +checksum = "24d8ad82d2383cb74995f58993cbdd2914aed57b2f91f46580310dd81dc3d05a" dependencies = [ "codspeed", "codspeed-criterion-compat-walltime", @@ -719,9 +719,9 @@ dependencies = [ [[package]] name = "codspeed-criterion-compat-walltime" -version = "3.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c104831cdfa515c938d8ec5b8dd38030a5f815b113762f0d03f494177b261cfd" +checksum = "61badaa6c452d192a29f8387147888f0ab358553597c3fe9bf8a162ef7c2fa64" dependencies = [ "anes", "cast", @@ -2107,12 +2107,6 @@ version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" -[[package]] -name = "libm" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" - [[package]] name = "libmimalloc-sys" version = "0.1.39" @@ -2233,16 +2227,6 @@ dependencies = [ "regex-automata 0.1.10", ] -[[package]] -name = "matrixmultiply" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" -dependencies = [ - "autocfg", - "rawpointer", -] - [[package]] name = "md-5" version = "0.10.6" @@ -2380,23 +2364,6 @@ dependencies = [ "syn", ] -[[package]] -name = "nalgebra" -version = "0.33.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26aecdf64b707efd1310e3544d709c5c0ac61c13756046aaaba41be5c4f66a3b" -dependencies = [ - "approx", - "matrixmultiply", - "num-complex", - "num-rational", - "num-traits", - "rand", - "rand_distr", - "simba", - "typenum", -] - [[package]] name = "nanoid" version = "0.4.0" @@ -2455,45 +2422,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -2501,7 +2429,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -3002,22 +2929,6 @@ dependencies = [ "getrandom 0.2.15", ] -[[package]] -name = "rand_distr" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" -dependencies = [ - "num-traits", - "rand", -] - -[[package]] -name = "rawpointer" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" - [[package]] name = "rayon" version = "1.10.0" @@ -3497,15 +3408,6 @@ version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" -[[package]] -name = "safe_arch" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" -dependencies = [ - "bytemuck", -] - [[package]] name = "same-file" version = "1.0.6" @@ -3762,19 +3664,6 @@ dependencies = [ "libc", ] -[[package]] -name = "simba" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3a386a501cd104797982c15ae17aafe8b9261315b5d07e3ec803f2ea26be0fa" -dependencies = [ - "approx", - "num-complex", - "num-traits", - "paste", - "wide", -] - [[package]] name = "simd-adler32" version = "0.3.7" @@ -3861,9 +3750,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a3fe7c28c6512e766b0874335db33c94ad7b8f9054228ae1c2abd47ce7d335e" dependencies = [ "approx", - "nalgebra", "num-traits", - "rand", ] [[package]] @@ -6362,16 +6249,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "wide" -version = "0.7.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" -dependencies = [ - "bytemuck", - "safe_arch", -] - [[package]] name = "widestring" version = "1.1.0" diff --git a/crates/uv-bench/Cargo.toml b/crates/uv-bench/Cargo.toml index afa73c32b..8c08d4dd2 100644 --- a/crates/uv-bench/Cargo.toml +++ b/crates/uv-bench/Cargo.toml @@ -42,7 +42,7 @@ uv-types = { workspace = true } uv-workspace = { workspace = true } anyhow = { workspace = true } -codspeed-criterion-compat = { version = "3.0.1", default-features = false, optional = true } +codspeed-criterion-compat = { version = "3.0.2", default-features = false, optional = true } criterion = { version = "0.6.0", default-features = false, features = [ "async_tokio", ] } From c777491bf4f829b4431cc3fa3513e57d2bfe0f5c Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 1 Jul 2025 11:29:10 -0500 Subject: [PATCH 120/349] Use the insiders requirements when building docs in CI (#14379) --- .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 6c6d47ce2..701699239 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -443,7 +443,7 @@ jobs: - name: "Build docs (insiders)" if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }} - run: uvx --with-requirements docs/requirements.txt mkdocs build --strict -f mkdocs.insiders.yml + run: uvx --with-requirements docs/requirements-insiders.txt mkdocs build --strict -f mkdocs.insiders.yml build-binary-linux-libc: timeout-minutes: 10 From c0786832170ec0eac636d19a40a2b1ad7d82f389 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 1 Jul 2025 12:50:19 -0400 Subject: [PATCH 121/349] Only drop build directories on program exit (#14304) ## Summary This PR ensures that we avoid cleaning up build directories until the end of a resolve-and-install cycle. It's not bulletproof (since we could still run into issues with `uv lock` followed by `uv sync` whereby a build directory gets cleaned up that's still referenced in the `build` artifacts), but it at least gets PyTorch building without error with `uv pip install .`, which is a case that's been reported several times. Closes https://github.com/astral-sh/uv/issues/14269. --- Cargo.lock | 2 ++ crates/uv-build-frontend/src/lib.rs | 4 ++++ crates/uv-dispatch/src/lib.rs | 22 +++++++++++++++++----- crates/uv-distribution/src/source/mod.rs | 24 +++++++++++++++++++----- crates/uv-types/Cargo.toml | 2 ++ crates/uv-types/src/builds.rs | 13 +++++++++++++ crates/uv-types/src/traits.rs | 8 ++++++++ 7 files changed, 65 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f961b2b5a..fa75564dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5937,7 +5937,9 @@ name = "uv-types" version = "0.0.1" dependencies = [ "anyhow", + "boxcar", "rustc-hash", + "tempfile", "thiserror 2.0.12", "uv-cache", "uv-configuration", diff --git a/crates/uv-build-frontend/src/lib.rs b/crates/uv-build-frontend/src/lib.rs index df6fc09cf..d35526951 100644 --- a/crates/uv-build-frontend/src/lib.rs +++ b/crates/uv-build-frontend/src/lib.rs @@ -862,6 +862,10 @@ impl SourceBuild { } impl SourceBuildTrait for SourceBuild { + fn into_build_dir(self) -> TempDir { + self.temp_dir + } + async fn metadata(&mut self) -> Result, AnyErrorBuild> { Ok(self.get_metadata_without_build().await?) } diff --git a/crates/uv-dispatch/src/lib.rs b/crates/uv-dispatch/src/lib.rs index 207f241ad..d95a4f39a 100644 --- a/crates/uv-dispatch/src/lib.rs +++ b/crates/uv-dispatch/src/lib.rs @@ -2,15 +2,15 @@ //! [installer][`uv_installer`] and [build][`uv_build`] through [`BuildDispatch`] //! implementing [`BuildContext`]. -use std::ffi::{OsStr, OsString}; -use std::path::Path; - use anyhow::{Context, Result}; use futures::FutureExt; use itertools::Itertools; use rustc_hash::FxHashMap; +use std::ffi::{OsStr, OsString}; +use std::path::Path; use thiserror::Error; use tracing::{debug, instrument, trace}; + use uv_build_backend::check_direct_build; use uv_build_frontend::{SourceBuild, SourceBuildContext}; use uv_cache::Cache; @@ -35,8 +35,8 @@ use uv_resolver::{ PythonRequirement, Resolver, ResolverEnvironment, }; use uv_types::{ - AnyErrorBuild, BuildContext, BuildIsolation, BuildStack, EmptyInstalledPackages, HashStrategy, - InFlight, + AnyErrorBuild, BuildArena, BuildContext, BuildIsolation, BuildStack, EmptyInstalledPackages, + HashStrategy, InFlight, }; use uv_workspace::WorkspaceCache; @@ -179,6 +179,10 @@ impl BuildContext for BuildDispatch<'_> { &self.shared_state.git } + fn build_arena(&self) -> &BuildArena { + &self.shared_state.build_arena + } + fn capabilities(&self) -> &IndexCapabilities { &self.shared_state.capabilities } @@ -521,6 +525,8 @@ pub struct SharedState { index: InMemoryIndex, /// The downloaded distributions. in_flight: InFlight, + /// Build directories for any PEP 517 builds executed during resolution or installation. + build_arena: BuildArena, } impl SharedState { @@ -533,6 +539,7 @@ impl SharedState { Self { git: self.git.clone(), capabilities: self.capabilities.clone(), + build_arena: self.build_arena.clone(), ..Default::default() } } @@ -556,4 +563,9 @@ impl SharedState { pub fn capabilities(&self) -> &IndexCapabilities { &self.capabilities } + + /// Return the [`BuildArena`] used by the [`SharedState`]. + pub fn build_arena(&self) -> &BuildArena { + &self.build_arena + } } diff --git a/crates/uv-distribution/src/source/mod.rs b/crates/uv-distribution/src/source/mod.rs index 7be26fece..5116ce72b 100644 --- a/crates/uv-distribution/src/source/mod.rs +++ b/crates/uv-distribution/src/source/mod.rs @@ -2276,6 +2276,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { fs::create_dir_all(&cache_shard) .await .map_err(Error::CacheWrite)?; + // Try a direct build if that isn't disabled and the uv build backend is used. let disk_filename = if let Some(name) = self .build_context @@ -2296,7 +2297,8 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { // In the uv build backend, the normalized filename and the disk filename are the same. name.to_string() } else { - self.build_context + let builder = self + .build_context .setup_build( source_root, subdirectory, @@ -2313,10 +2315,17 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { self.build_stack.cloned().unwrap_or_default(), ) .await - .map_err(|err| Error::Build(err.into()))? - .wheel(temp_dir.path()) - .await - .map_err(Error::Build)? + .map_err(|err| Error::Build(err.into()))?; + + // Build the wheel. + let wheel = builder.wheel(temp_dir.path()).await.map_err(Error::Build)?; + + // Store a reference to the build context. + self.build_context + .build_arena() + .push(builder.into_build_dir()); + + wheel }; // Read the metadata from the wheel. @@ -2398,6 +2407,11 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { return Ok(None); }; + // Store a reference to the build context. + self.build_context + .build_arena() + .push(builder.into_build_dir()); + // Read the metadata from disk. debug!("Prepared metadata for: {source}"); let content = fs::read(dist_info.join("METADATA")) diff --git a/crates/uv-types/Cargo.toml b/crates/uv-types/Cargo.toml index 0973a5218..7c1ca60b3 100644 --- a/crates/uv-types/Cargo.toml +++ b/crates/uv-types/Cargo.toml @@ -31,7 +31,9 @@ uv-redacted = { workspace = true } uv-workspace = { workspace = true } anyhow = { workspace = true } +boxcar = { workspace = true } rustc-hash = { workspace = true } +tempfile = { workspace = true } thiserror = { workspace = true } [features] diff --git a/crates/uv-types/src/builds.rs b/crates/uv-types/src/builds.rs index ea5e0b6a3..759dd4135 100644 --- a/crates/uv-types/src/builds.rs +++ b/crates/uv-types/src/builds.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; +use tempfile::TempDir; use uv_pep508::PackageName; use uv_python::PythonEnvironment; @@ -37,3 +39,14 @@ impl BuildIsolation<'_> { } } } + +/// An arena of temporary directories used for builds. +#[derive(Default, Debug, Clone)] +pub struct BuildArena(Arc>); + +impl BuildArena { + /// Push a new temporary directory into the arena. + pub fn push(&self, temp_dir: TempDir) { + self.0.push(temp_dir); + } +} diff --git a/crates/uv-types/src/traits.rs b/crates/uv-types/src/traits.rs index 6f724b27a..16f58fe13 100644 --- a/crates/uv-types/src/traits.rs +++ b/crates/uv-types/src/traits.rs @@ -5,7 +5,9 @@ use std::path::{Path, PathBuf}; use anyhow::Result; use rustc_hash::FxHashSet; +use tempfile::TempDir; +use crate::BuildArena; use uv_cache::Cache; use uv_configuration::{BuildKind, BuildOptions, BuildOutput, ConfigSettings, SourceStrategy}; use uv_distribution_filename::DistFilename; @@ -67,6 +69,9 @@ pub trait BuildContext { /// Return a reference to the Git resolver. fn git(&self) -> &GitResolver; + /// Return a reference to the build arena. + fn build_arena(&self) -> &BuildArena; + /// Return a reference to the discovered registry capabilities. fn capabilities(&self) -> &IndexCapabilities; @@ -148,6 +153,9 @@ pub trait BuildContext { /// You can either call only `wheel()` to build the wheel directly, call only `metadata()` to get /// the metadata without performing the actual or first call `metadata()` and then `wheel()`. pub trait SourceBuildTrait { + /// Return the temporary build directory. + fn into_build_dir(self) -> TempDir; + /// A wrapper for `uv_build::SourceBuild::get_metadata_without_build`. /// /// For PEP 517 builds, this calls `prepare_metadata_for_build_wheel` From 85358fe9c615a9f75b1e7367018c22b28d540ecf Mon Sep 17 00:00:00 2001 From: Jack O'Connor Date: Mon, 30 Jun 2025 13:37:51 -0700 Subject: [PATCH 122/349] Keep track of retries in `ManagedPythonDownload::fetch_with_retry` If/when we see https://github.com/astral-sh/uv/issues/14171 again, this should clarify whether our retry logic was skipped (i.e. a transient error wasn't correctly identified as transient), or whether we exhausted our retries. Previously, if you ran a local example fileserver as in https://github.com/astral-sh/uv/issues/14171#issuecomment-3014580701 and then you tried to install Python from it, you'd get: ``` $ export UV_TEST_NO_CLI_PROGRESS=1 $ uv python install 3.8.20 --mirror http://localhost:8000 2>&1 | cat error: Failed to install cpython-3.8.20-linux-x86_64-gnu Caused by: Failed to extract archive: cpython-3.8.20-20241002-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz Caused by: failed to unpack `/home/jacko/.local/share/uv/python/.temp/.tmpS4sHHZ/python/lib/libpython3.8.so.1.0` Caused by: failed to unpack `python/lib/libpython3.8.so.1.0` into `/home/jacko/.local/share/uv/python/.temp/.tmpS4sHHZ/python/lib/libpython3.8.so.1.0` Caused by: error decoding response body Caused by: request or response body error Caused by: error reading a body from connection Caused by: Connection reset by peer (os error 104) ``` With this change you get: ``` error: Failed to install cpython-3.8.20-linux-x86_64-gnu Caused by: Request failed after 3 retries Caused by: Failed to extract archive: cpython-3.8.20-20241002-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz Caused by: failed to unpack `/home/jacko/.local/share/uv/python/.temp/.tmp4Ia24w/python/lib/libpython3.8.so.1.0` Caused by: failed to unpack `python/lib/libpython3.8.so.1.0` into `/home/jacko/.local/share/uv/python/.temp/.tmp4Ia24w/python/lib/libpython3.8.so.1.0` Caused by: error decoding response body Caused by: request or response body error Caused by: error reading a body from connection Caused by: Connection reset by peer (os error 104) ``` At the same time, I'm updating the way we handle the retry count to avoid nested retry loops exceeding the intended number of attempts, as I mentioned at https://github.com/astral-sh/uv/issues/14069#issuecomment-3020634281. It's not clear to me whether we actually want this part of the change, and I need feedback here. --- crates/uv-python/src/downloads.rs | 84 +++++++++++++++++++++++-------- 1 file changed, 64 insertions(+), 20 deletions(-) diff --git a/crates/uv-python/src/downloads.rs b/crates/uv-python/src/downloads.rs index 2b0a7e669..ad516d096 100644 --- a/crates/uv-python/src/downloads.rs +++ b/crates/uv-python/src/downloads.rs @@ -12,7 +12,7 @@ use futures::TryStreamExt; use itertools::Itertools; use once_cell::sync::OnceCell; use owo_colors::OwoColorize; -use reqwest_retry::RetryPolicy; +use reqwest_retry::{RetryError, RetryPolicy}; use serde::Deserialize; use thiserror::Error; use tokio::io::{AsyncRead, AsyncWriteExt, BufWriter, ReadBuf}; @@ -111,6 +111,33 @@ pub enum Error { }, } +impl Error { + // Return the number of attempts that were made to complete this request before this error was + // returned. Note that e.g. 3 retries equates to 4 attempts. + // + // It's easier to do arithmetic with "attempts" instead of "retries", because if you have + // nested retry loops you can just add up all the attempts directly, while adding up the + // retries requires +1/-1 adjustments. + fn attempts(&self) -> u32 { + // Unfortunately different variants of `Error` track retry counts in different ways. We + // could consider unifying the variants we handle here in `Error::from_reqwest_middleware` + // instead, but both approaches will be fragile as new variants get added over time. + if let Error::NetworkErrorWithRetries { retries, .. } = self { + return retries + 1; + } + // TODO(jack): let-chains are stable as of Rust 1.88. We should use them here as soon as + // our rust-version is high enough. + if let Error::NetworkMiddlewareError(_, anyhow_error) = self { + if let Some(RetryError::WithRetries { retries, .. }) = + anyhow_error.downcast_ref::() + { + return retries + 1; + } + } + 1 + } +} + #[derive(Debug, PartialEq, Eq, Clone, Hash)] pub struct ManagedPythonDownload { key: PythonInstallationKey, @@ -695,7 +722,8 @@ impl ManagedPythonDownload { pypy_install_mirror: Option<&str>, reporter: Option<&dyn Reporter>, ) -> Result { - let mut n_past_retries = 0; + let mut total_attempts = 0; + let mut retried_here = false; let start_time = SystemTime::now(); let retry_policy = client.retry_policy(); loop { @@ -710,25 +738,41 @@ impl ManagedPythonDownload { reporter, ) .await; - if result - .as_ref() - .err() - .is_some_and(|err| is_extended_transient_error(err)) - { - let retry_decision = retry_policy.should_retry(start_time, n_past_retries); - if let reqwest_retry::RetryDecision::Retry { execute_after } = retry_decision { - debug!( - "Transient failure while handling response for {}; retrying...", - self.key() - ); - let duration = execute_after - .duration_since(SystemTime::now()) - .unwrap_or_else(|_| Duration::default()); - tokio::time::sleep(duration).await; - n_past_retries += 1; - continue; + let result = match result { + Ok(download_result) => Ok(download_result), + Err(err) => { + // Inner retry loops (e.g. `reqwest-retry` middleware) might make more than one + // attempt per error we see here. + total_attempts += err.attempts(); + // We currently interpret e.g. "3 retries" to mean we should make 4 attempts. + let n_past_retries = total_attempts - 1; + if is_extended_transient_error(&err) { + let retry_decision = retry_policy.should_retry(start_time, n_past_retries); + if let reqwest_retry::RetryDecision::Retry { execute_after } = + retry_decision + { + debug!( + "Transient failure while handling response for {}; retrying...", + self.key() + ); + let duration = execute_after + .duration_since(SystemTime::now()) + .unwrap_or_else(|_| Duration::default()); + tokio::time::sleep(duration).await; + retried_here = true; + continue; // Retry. + } + } + if retried_here { + Err(Error::NetworkErrorWithRetries { + err: Box::new(err), + retries: n_past_retries, + }) + } else { + Err(err) + } } - } + }; return result; } } From d9f9ed4aec8b0a68b66c0346450aa23744dc69ef Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 1 Jul 2025 13:15:47 -0400 Subject: [PATCH 123/349] Reuse build (virtual) environments across resolution and installation (#14338) ## Summary The basic idea here is that we can (should) reuse a build environment across resolution (`prepare_metadata_for_build_wheel`) and installation. This also happens to solve the build-PyTorch-from-source problem, since we use a consistent build environment between the invocations. Since `SourceDistributionBuilder` is stateless, we instead store the builds on `BuildContext`, and we key them by various properties: the underlying interpreter, the configuration settings, etc. This just ensures that if we build the same package twice within a process, we don't accidentally reuse an incompatible build (virtual) environment. (Note that still drop build environments at the end of the command, and don't attempt to reuse them across processes.) Closes #14269. --- Cargo.lock | 3 +- crates/uv-build-frontend/src/lib.rs | 51 +++---- crates/uv-configuration/src/build_options.rs | 2 +- crates/uv-configuration/src/sources.rs | 4 +- crates/uv-dispatch/src/lib.rs | 11 +- crates/uv-distribution/src/error.rs | 2 + crates/uv-distribution/src/source/mod.rs | 138 ++++++++++++++----- crates/uv-types/Cargo.toml | 3 +- crates/uv-types/src/builds.rs | 48 +++++-- crates/uv-types/src/traits.rs | 17 +-- 10 files changed, 189 insertions(+), 90 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fa75564dc..802b069f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5937,9 +5937,8 @@ name = "uv-types" version = "0.0.1" dependencies = [ "anyhow", - "boxcar", + "dashmap", "rustc-hash", - "tempfile", "thiserror 2.0.12", "uv-cache", "uv-configuration", diff --git a/crates/uv-build-frontend/src/lib.rs b/crates/uv-build-frontend/src/lib.rs index d35526951..06c07425c 100644 --- a/crates/uv-build-frontend/src/lib.rs +++ b/crates/uv-build-frontend/src/lib.rs @@ -259,8 +259,6 @@ pub struct SourceBuild { environment_variables: FxHashMap, /// Runner for Python scripts. runner: PythonRunner, - /// A file lock representing the source tree, currently only used with setuptools. - _source_tree_lock: Option, } impl SourceBuild { @@ -394,23 +392,6 @@ impl SourceBuild { OsString::from(venv.scripts()) }; - // Depending on the command, setuptools puts `*.egg-info`, `build/`, and `dist/` in the - // source tree, and concurrent invocations of setuptools using the same source dir can - // stomp on each other. We need to lock something to fix that, but we don't want to dump a - // `.lock` file into the source tree that the user will need to .gitignore. Take a global - // proxy lock instead. - let mut source_tree_lock = None; - if pep517_backend.is_setuptools() { - debug!("Locking the source tree for setuptools"); - let canonical_source_path = source_tree.canonicalize()?; - let lock_path = std::env::temp_dir().join(format!( - "uv-setuptools-{}.lock", - cache_digest(&canonical_source_path) - )); - source_tree_lock = - Some(LockedFile::acquire(lock_path, source_tree.to_string_lossy()).await?); - } - // Create the PEP 517 build environment. If build isolation is disabled, we assume the build // environment is already setup. let runner = PythonRunner::new(concurrent_builds, level); @@ -457,10 +438,30 @@ impl SourceBuild { environment_variables, modified_path, runner, - _source_tree_lock: source_tree_lock, }) } + /// Acquire a lock on the source tree, if necessary. + async fn acquire_lock(&self) -> Result, Error> { + // Depending on the command, setuptools puts `*.egg-info`, `build/`, and `dist/` in the + // source tree, and concurrent invocations of setuptools using the same source dir can + // stomp on each other. We need to lock something to fix that, but we don't want to dump a + // `.lock` file into the source tree that the user will need to .gitignore. Take a global + // proxy lock instead. + let mut source_tree_lock = None; + if self.pep517_backend.is_setuptools() { + debug!("Locking the source tree for setuptools"); + let canonical_source_path = self.source_tree.canonicalize()?; + let lock_path = env::temp_dir().join(format!( + "uv-setuptools-{}.lock", + cache_digest(&canonical_source_path) + )); + source_tree_lock = + Some(LockedFile::acquire(lock_path, self.source_tree.to_string_lossy()).await?); + } + Ok(source_tree_lock) + } + async fn get_resolved_requirements( build_context: &impl BuildContext, source_build_context: SourceBuildContext, @@ -631,6 +632,9 @@ impl SourceBuild { return Ok(Some(metadata_dir.clone())); } + // Lock the source tree, if necessary. + let _lock = self.acquire_lock().await?; + // Hatch allows for highly dynamic customization of metadata via hooks. In such cases, Hatch // can't uphold the PEP 517 contract, in that the metadata Hatch would return by // `prepare_metadata_for_build_wheel` isn't guaranteed to match that of the built wheel. @@ -749,6 +753,9 @@ impl SourceBuild { /// Perform a PEP 517 build for a wheel or source distribution (sdist). async fn pep517_build(&self, output_dir: &Path) -> Result { + // Lock the source tree, if necessary. + let _lock = self.acquire_lock().await?; + // Write the hook output to a file so that we can read it back reliably. let outfile = self .temp_dir @@ -862,10 +869,6 @@ impl SourceBuild { } impl SourceBuildTrait for SourceBuild { - fn into_build_dir(self) -> TempDir { - self.temp_dir - } - async fn metadata(&mut self) -> Result, AnyErrorBuild> { Ok(self.get_metadata_without_build().await?) } diff --git a/crates/uv-configuration/src/build_options.rs b/crates/uv-configuration/src/build_options.rs index 1a62a1a12..8b493cbf0 100644 --- a/crates/uv-configuration/src/build_options.rs +++ b/crates/uv-configuration/src/build_options.rs @@ -4,7 +4,7 @@ use uv_pep508::PackageName; use crate::{PackageNameSpecifier, PackageNameSpecifiers}; -#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)] pub enum BuildKind { /// A PEP 517 wheel build. #[default] diff --git a/crates/uv-configuration/src/sources.rs b/crates/uv-configuration/src/sources.rs index c60d69ef4..f8d0c3367 100644 --- a/crates/uv-configuration/src/sources.rs +++ b/crates/uv-configuration/src/sources.rs @@ -1,4 +1,6 @@ -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[derive( + Debug, Default, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, +)] #[serde(rename_all = "kebab-case", deny_unknown_fields)] pub enum SourceStrategy { /// Use `tool.uv.sources` when resolving dependencies. diff --git a/crates/uv-dispatch/src/lib.rs b/crates/uv-dispatch/src/lib.rs index d95a4f39a..222450539 100644 --- a/crates/uv-dispatch/src/lib.rs +++ b/crates/uv-dispatch/src/lib.rs @@ -2,12 +2,13 @@ //! [installer][`uv_installer`] and [build][`uv_build`] through [`BuildDispatch`] //! implementing [`BuildContext`]. +use std::ffi::{OsStr, OsString}; +use std::path::Path; + use anyhow::{Context, Result}; use futures::FutureExt; use itertools::Itertools; use rustc_hash::FxHashMap; -use std::ffi::{OsStr, OsString}; -use std::path::Path; use thiserror::Error; use tracing::{debug, instrument, trace}; @@ -179,7 +180,7 @@ impl BuildContext for BuildDispatch<'_> { &self.shared_state.git } - fn build_arena(&self) -> &BuildArena { + fn build_arena(&self) -> &BuildArena { &self.shared_state.build_arena } @@ -526,7 +527,7 @@ pub struct SharedState { /// The downloaded distributions. in_flight: InFlight, /// Build directories for any PEP 517 builds executed during resolution or installation. - build_arena: BuildArena, + build_arena: BuildArena, } impl SharedState { @@ -565,7 +566,7 @@ impl SharedState { } /// Return the [`BuildArena`] used by the [`SharedState`]. - pub fn build_arena(&self) -> &BuildArena { + pub fn build_arena(&self) -> &BuildArena { &self.build_arena } } diff --git a/crates/uv-distribution/src/error.rs b/crates/uv-distribution/src/error.rs index c19867e75..7c2a0f804 100644 --- a/crates/uv-distribution/src/error.rs +++ b/crates/uv-distribution/src/error.rs @@ -108,6 +108,8 @@ pub enum Error { CacheHeal(String, HashAlgorithm), #[error("The source distribution requires Python {0}, but {1} is installed")] RequiresPython(VersionSpecifiers, Version), + #[error("Failed to identify base Python interpreter")] + BaseInterpreter(#[source] std::io::Error), /// A generic request middleware error happened while making a request. /// Refer to the error message for more details. diff --git a/crates/uv-distribution/src/source/mod.rs b/crates/uv-distribution/src/source/mod.rs index 5116ce72b..2b73eb4ff 100644 --- a/crates/uv-distribution/src/source/mod.rs +++ b/crates/uv-distribution/src/source/mod.rs @@ -43,7 +43,7 @@ use uv_normalize::PackageName; use uv_pep440::{Version, release_specifiers_to_ranges}; use uv_platform_tags::Tags; use uv_pypi_types::{HashAlgorithm, HashDigest, HashDigests, PyProjectToml, ResolutionMetadata}; -use uv_types::{BuildContext, BuildStack, SourceBuildTrait}; +use uv_types::{BuildContext, BuildKey, BuildStack, SourceBuildTrait}; use uv_workspace::pyproject::ToolUvSources; use crate::distribution_database::ManagedClient; @@ -2297,35 +2297,73 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { // In the uv build backend, the normalized filename and the disk filename are the same. name.to_string() } else { - let builder = self - .build_context - .setup_build( - source_root, - subdirectory, - source_root, - Some(&source.to_string()), - source.as_dist(), - source_strategy, - if source.is_editable() { - BuildKind::Editable - } else { - BuildKind::Wheel - }, - BuildOutput::Debug, - self.build_stack.cloned().unwrap_or_default(), - ) - .await - .map_err(|err| Error::Build(err.into()))?; + // Identify the base Python interpreter to use in the cache key. + let base_python = if cfg!(unix) { + self.build_context + .interpreter() + .find_base_python() + .map_err(Error::BaseInterpreter)? + } else { + self.build_context + .interpreter() + .to_base_python() + .map_err(Error::BaseInterpreter)? + }; - // Build the wheel. - let wheel = builder.wheel(temp_dir.path()).await.map_err(Error::Build)?; + let build_kind = if source.is_editable() { + BuildKind::Editable + } else { + BuildKind::Wheel + }; - // Store a reference to the build context. - self.build_context - .build_arena() - .push(builder.into_build_dir()); + let build_key = BuildKey { + base_python: base_python.into_boxed_path(), + source_root: source_root.to_path_buf().into_boxed_path(), + subdirectory: subdirectory + .map(|subdirectory| subdirectory.to_path_buf().into_boxed_path()), + source_strategy, + build_kind, + }; - wheel + if let Some(builder) = self.build_context.build_arena().remove(&build_key) { + debug!("Creating build environment for: {source}"); + let wheel = builder.wheel(temp_dir.path()).await.map_err(Error::Build)?; + + // Store the build context. + self.build_context.build_arena().insert(build_key, builder); + + wheel + } else { + debug!("Reusing existing build environment for: {source}"); + + let builder = self + .build_context + .setup_build( + source_root, + subdirectory, + source_root, + Some(&source.to_string()), + source.as_dist(), + source_strategy, + if source.is_editable() { + BuildKind::Editable + } else { + BuildKind::Wheel + }, + BuildOutput::Debug, + self.build_stack.cloned().unwrap_or_default(), + ) + .await + .map_err(|err| Error::Build(err.into()))?; + + // Build the wheel. + let wheel = builder.wheel(temp_dir.path()).await.map_err(Error::Build)?; + + // Store the build context. + self.build_context.build_arena().insert(build_key, builder); + + wheel + } }; // Read the metadata from the wheel. @@ -2380,6 +2418,26 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { } } + // Identify the base Python interpreter to use in the cache key. + let base_python = if cfg!(unix) { + self.build_context + .interpreter() + .find_base_python() + .map_err(Error::BaseInterpreter)? + } else { + self.build_context + .interpreter() + .to_base_python() + .map_err(Error::BaseInterpreter)? + }; + + // Determine whether this is an editable or non-editable build. + let build_kind = if source.is_editable() { + BuildKind::Editable + } else { + BuildKind::Wheel + }; + // Set up the builder. let mut builder = self .build_context @@ -2390,11 +2448,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { Some(&source.to_string()), source.as_dist(), source_strategy, - if source.is_editable() { - BuildKind::Editable - } else { - BuildKind::Wheel - }, + build_kind, BuildOutput::Debug, self.build_stack.cloned().unwrap_or_default(), ) @@ -2403,15 +2457,25 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { // Build the metadata. let dist_info = builder.metadata().await.map_err(Error::Build)?; + + // Store the build context. + self.build_context.build_arena().insert( + BuildKey { + base_python: base_python.into_boxed_path(), + source_root: source_root.to_path_buf().into_boxed_path(), + subdirectory: subdirectory + .map(|subdirectory| subdirectory.to_path_buf().into_boxed_path()), + source_strategy, + build_kind, + }, + builder, + ); + + // Return the `.dist-info` directory, if it exists. let Some(dist_info) = dist_info else { return Ok(None); }; - // Store a reference to the build context. - self.build_context - .build_arena() - .push(builder.into_build_dir()); - // Read the metadata from disk. debug!("Prepared metadata for: {source}"); let content = fs::read(dist_info.join("METADATA")) diff --git a/crates/uv-types/Cargo.toml b/crates/uv-types/Cargo.toml index 7c1ca60b3..f29af4ca4 100644 --- a/crates/uv-types/Cargo.toml +++ b/crates/uv-types/Cargo.toml @@ -31,9 +31,8 @@ uv-redacted = { workspace = true } uv-workspace = { workspace = true } anyhow = { workspace = true } -boxcar = { workspace = true } +dashmap = { workspace = true } rustc-hash = { workspace = true } -tempfile = { workspace = true } thiserror = { workspace = true } [features] diff --git a/crates/uv-types/src/builds.rs b/crates/uv-types/src/builds.rs index 759dd4135..e8c622057 100644 --- a/crates/uv-types/src/builds.rs +++ b/crates/uv-types/src/builds.rs @@ -1,5 +1,9 @@ +use std::path::Path; use std::sync::Arc; -use tempfile::TempDir; + +use dashmap::DashMap; + +use uv_configuration::{BuildKind, SourceStrategy}; use uv_pep508::PackageName; use uv_python::PythonEnvironment; @@ -40,13 +44,41 @@ impl BuildIsolation<'_> { } } -/// An arena of temporary directories used for builds. -#[derive(Default, Debug, Clone)] -pub struct BuildArena(Arc>); +/// A key for the build cache, which includes the interpreter, source root, subdirectory, source +/// strategy, and build kind. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct BuildKey { + pub base_python: Box, + pub source_root: Box, + pub subdirectory: Option>, + pub source_strategy: SourceStrategy, + pub build_kind: BuildKind, +} -impl BuildArena { - /// Push a new temporary directory into the arena. - pub fn push(&self, temp_dir: TempDir) { - self.0.push(temp_dir); +/// An arena of in-process builds. +#[derive(Debug)] +pub struct BuildArena(Arc>); + +impl Default for BuildArena { + fn default() -> Self { + Self(Arc::new(DashMap::new())) + } +} + +impl Clone for BuildArena { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl BuildArena { + /// Insert a build entry into the arena. + pub fn insert(&self, key: BuildKey, value: T) { + self.0.insert(key, value); + } + + /// Remove a build entry from the arena. + pub fn remove(&self, key: &BuildKey) -> Option { + self.0.remove(key).map(|entry| entry.1) } } diff --git a/crates/uv-types/src/traits.rs b/crates/uv-types/src/traits.rs index 16f58fe13..a95367fef 100644 --- a/crates/uv-types/src/traits.rs +++ b/crates/uv-types/src/traits.rs @@ -5,9 +5,7 @@ use std::path::{Path, PathBuf}; use anyhow::Result; use rustc_hash::FxHashSet; -use tempfile::TempDir; -use crate::BuildArena; use uv_cache::Cache; use uv_configuration::{BuildKind, BuildOptions, BuildOutput, ConfigSettings, SourceStrategy}; use uv_distribution_filename::DistFilename; @@ -20,6 +18,8 @@ use uv_pep508::PackageName; use uv_python::{Interpreter, PythonEnvironment}; use uv_workspace::WorkspaceCache; +use crate::BuildArena; + /// Avoids cyclic crate dependencies between resolver, installer and builder. /// /// To resolve the dependencies of a packages, we may need to build one or more source @@ -70,7 +70,7 @@ pub trait BuildContext { fn git(&self) -> &GitResolver; /// Return a reference to the build arena. - fn build_arena(&self) -> &BuildArena; + fn build_arena(&self) -> &BuildArena; /// Return a reference to the discovered registry capabilities. fn capabilities(&self) -> &IndexCapabilities; @@ -153,9 +153,6 @@ pub trait BuildContext { /// You can either call only `wheel()` to build the wheel directly, call only `metadata()` to get /// the metadata without performing the actual or first call `metadata()` and then `wheel()`. pub trait SourceBuildTrait { - /// Return the temporary build directory. - fn into_build_dir(self) -> TempDir; - /// A wrapper for `uv_build::SourceBuild::get_metadata_without_build`. /// /// For PEP 517 builds, this calls `prepare_metadata_for_build_wheel` @@ -188,13 +185,13 @@ pub trait InstalledPackagesProvider: Clone + Send + Sync + 'static { pub struct EmptyInstalledPackages; impl InstalledPackagesProvider for EmptyInstalledPackages { - fn get_packages(&self, _name: &PackageName) -> Vec<&InstalledDist> { - Vec::new() - } - fn iter(&self) -> impl Iterator { std::iter::empty() } + + fn get_packages(&self, _name: &PackageName) -> Vec<&InstalledDist> { + Vec::new() + } } /// [`anyhow::Error`]-like wrapper type for [`BuildDispatch`] method return values, that also makes From 29fcd6faeeb3a2f2e69655d6d34f30b26c71c850 Mon Sep 17 00:00:00 2001 From: John Mumm Date: Tue, 1 Jul 2025 20:39:17 +0200 Subject: [PATCH 124/349] Fix test cases to match Cow variants (#14390) Updates `without_trailing_slash` and `without_fragment` to separately match values against `Cow` variants. Closes #14350 --- crates/uv-distribution-types/src/file.rs | 27 +++++++++++++++--------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/crates/uv-distribution-types/src/file.rs b/crates/uv-distribution-types/src/file.rs index 81e3e2878..a75af3977 100644 --- a/crates/uv-distribution-types/src/file.rs +++ b/crates/uv-distribution-types/src/file.rs @@ -272,42 +272,49 @@ mod tests { fn without_fragment() { // Borrows a URL without a fragment let url = UrlString("https://example.com/path".into()); - assert_eq!(url.without_fragment(), Cow::Borrowed(&url)); + assert_eq!(&*url.without_fragment(), &url); + assert!(matches!(url.without_fragment(), Cow::Borrowed(_))); // Removes the fragment if present on the URL let url = UrlString("https://example.com/path?query#fragment".into()); assert_eq!( - url.without_fragment(), - Cow::Owned(UrlString("https://example.com/path?query".into())) + &*url.without_fragment(), + &UrlString("https://example.com/path?query".into()) ); + assert!(matches!(url.without_fragment(), Cow::Owned(_))); } #[test] fn without_trailing_slash() { // Borrows a URL without a slash let url = UrlString("https://example.com/path".into()); - assert_eq!(url.without_trailing_slash(), Cow::Borrowed(&url)); + assert_eq!(&*url.without_trailing_slash(), &url); + assert!(matches!(url.without_trailing_slash(), Cow::Borrowed(_))); // Removes the trailing slash if present on the URL let url = UrlString("https://example.com/path/".into()); assert_eq!( - url.without_trailing_slash(), - Cow::Owned(UrlString("https://example.com/path".into())) + &*url.without_trailing_slash(), + &UrlString("https://example.com/path".into()) ); + assert!(matches!(url.without_trailing_slash(), Cow::Owned(_))); // Does not remove a trailing slash if it's the only path segment let url = UrlString("https://example.com/".into()); - assert_eq!(url.without_trailing_slash(), Cow::Borrowed(&url)); + assert_eq!(&*url.without_trailing_slash(), &url); + assert!(matches!(url.without_trailing_slash(), Cow::Borrowed(_))); // Does not remove a trailing slash if it's the only path segment with a missing scheme let url = UrlString("example.com/".into()); - assert_eq!(url.without_trailing_slash(), Cow::Borrowed(&url)); + assert_eq!(&*url.without_trailing_slash(), &url); + assert!(matches!(url.without_trailing_slash(), Cow::Borrowed(_))); // Removes the trailing slash when the scheme is missing let url = UrlString("example.com/path/".into()); assert_eq!( - url.without_trailing_slash(), - Cow::Owned(UrlString("example.com/path".into())) + &*url.without_trailing_slash(), + &UrlString("example.com/path".into()) ); + assert!(matches!(url.without_trailing_slash(), Cow::Owned(_))); } } From 06df95adbfe44af2c8bcab1481a8ec4b62cce3b5 Mon Sep 17 00:00:00 2001 From: konsti Date: Tue, 1 Jul 2025 20:39:46 +0200 Subject: [PATCH 125/349] Workaround for panic due to missing global validation in clap (#14368) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clap does not perform global validation, so flag that are declared as overriding can be set at the same time: https://github.com/clap-rs/clap/issues/6049. This would previously cause a panic. We work around this by choosing the yes-value always and writing a warning. An alternative would be erroring when both are set, but it's unclear to me if this may break things we want to support. (`UV_OFFLINE=1 cargo run -q pip --no-offline install tqdm --no-cache` is already banned). Fixes https://github.com/astral-sh/uv/pull/14299 **Test Plan** ``` $ cargo run -q pip --offline install --no-offline tqdm --no-cache warning: Boolean flags on different levels are not correctly supported (https://github.com/clap-rs/clap/issues/6049) × No solution found when resolving dependencies: ╰─▶ Because tqdm was not found in the cache and you require tqdm, we can conclude that your requirements are unsatisfiable. hint: Packages were unavailable because the network was disabled. When the network is disabled, registry packages may only be read from the cache. ``` --- crates/uv-cli/src/options.rs | 62 ++++++---- crates/uv/src/settings.rs | 183 ++++++++++++++++++------------ crates/uv/tests/it/pip_install.rs | 22 ++++ 3 files changed, 170 insertions(+), 97 deletions(-) diff --git a/crates/uv-cli/src/options.rs b/crates/uv-cli/src/options.rs index 656edd43c..f522022a1 100644 --- a/crates/uv-cli/src/options.rs +++ b/crates/uv-cli/src/options.rs @@ -1,7 +1,10 @@ +use anstream::eprintln; + use uv_cache::Refresh; use uv_configuration::ConfigSettings; use uv_resolver::PrereleaseMode; use uv_settings::{Combine, PipOptions, ResolverInstallerOptions, ResolverOptions}; +use uv_warnings::owo_colors::OwoColorize; use crate::{ BuildOptionsArgs, FetchArgs, IndexArgs, InstallerArgs, Maybe, RefreshArgs, ResolverArgs, @@ -9,12 +12,27 @@ use crate::{ }; /// Given a boolean flag pair (like `--upgrade` and `--no-upgrade`), resolve the value of the flag. -pub fn flag(yes: bool, no: bool) -> Option { +pub fn flag(yes: bool, no: bool, name: &str) -> Option { match (yes, no) { (true, false) => Some(true), (false, true) => Some(false), (false, false) => None, - (..) => unreachable!("Clap should make this impossible"), + (..) => { + eprintln!( + "{}{} `{}` and `{}` cannot be used together. \ + Boolean flags on different levels are currently not supported \ + (https://github.com/clap-rs/clap/issues/6049)", + "error".bold().red(), + ":".bold(), + format!("--{name}").green(), + format!("--no-{name}").green(), + ); + // No error forwarding since should eventually be solved on the clap side. + #[allow(clippy::exit)] + { + std::process::exit(2); + } + } } } @@ -26,7 +44,7 @@ impl From for Refresh { refresh_package, } = value; - Self::from_args(flag(refresh, no_refresh), refresh_package) + Self::from_args(flag(refresh, no_refresh, "no-refresh"), refresh_package) } } @@ -53,7 +71,7 @@ impl From for PipOptions { } = args; Self { - upgrade: flag(upgrade, no_upgrade), + upgrade: flag(upgrade, no_upgrade, "no-upgrade"), upgrade_package: Some(upgrade_package), index_strategy, keyring_provider, @@ -66,7 +84,7 @@ impl From for PipOptions { }, config_settings: config_setting .map(|config_settings| config_settings.into_iter().collect::()), - no_build_isolation: flag(no_build_isolation, build_isolation), + no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"), no_build_isolation_package: Some(no_build_isolation_package), exclude_newer, link_mode, @@ -96,16 +114,16 @@ impl From for PipOptions { } = args; Self { - reinstall: flag(reinstall, no_reinstall), + reinstall: flag(reinstall, no_reinstall, "reinstall"), reinstall_package: Some(reinstall_package), index_strategy, keyring_provider, config_settings: config_setting .map(|config_settings| config_settings.into_iter().collect::()), - no_build_isolation: flag(no_build_isolation, build_isolation), + no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"), exclude_newer, link_mode, - compile_bytecode: flag(compile_bytecode, no_compile_bytecode), + compile_bytecode: flag(compile_bytecode, no_compile_bytecode, "compile-bytecode"), no_sources: if no_sources { Some(true) } else { None }, ..PipOptions::from(index_args) } @@ -140,9 +158,9 @@ impl From for PipOptions { } = args; Self { - upgrade: flag(upgrade, no_upgrade), + upgrade: flag(upgrade, no_upgrade, "upgrade"), upgrade_package: Some(upgrade_package), - reinstall: flag(reinstall, no_reinstall), + reinstall: flag(reinstall, no_reinstall, "reinstall"), reinstall_package: Some(reinstall_package), index_strategy, keyring_provider, @@ -155,11 +173,11 @@ impl From for PipOptions { fork_strategy, config_settings: config_setting .map(|config_settings| config_settings.into_iter().collect::()), - no_build_isolation: flag(no_build_isolation, build_isolation), + no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"), no_build_isolation_package: Some(no_build_isolation_package), exclude_newer, link_mode, - compile_bytecode: flag(compile_bytecode, no_compile_bytecode), + compile_bytecode: flag(compile_bytecode, no_compile_bytecode, "compile-bytecode"), no_sources: if no_sources { Some(true) } else { None }, ..PipOptions::from(index_args) } @@ -289,7 +307,7 @@ pub fn resolver_options( .filter_map(Maybe::into_option) .collect() }), - upgrade: flag(upgrade, no_upgrade), + upgrade: flag(upgrade, no_upgrade, "no-upgrade"), upgrade_package: Some(upgrade_package), index_strategy, keyring_provider, @@ -303,13 +321,13 @@ pub fn resolver_options( dependency_metadata: None, config_settings: config_setting .map(|config_settings| config_settings.into_iter().collect::()), - no_build_isolation: flag(no_build_isolation, build_isolation), + no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"), no_build_isolation_package: Some(no_build_isolation_package), exclude_newer, link_mode, - no_build: flag(no_build, build), + no_build: flag(no_build, build, "build"), no_build_package: Some(no_build_package), - no_binary: flag(no_binary, binary), + no_binary: flag(no_binary, binary, "binary"), no_binary_package: Some(no_binary_package), no_sources: if no_sources { Some(true) } else { None }, } @@ -386,13 +404,13 @@ pub fn resolver_installer_options( .filter_map(Maybe::into_option) .collect() }), - upgrade: flag(upgrade, no_upgrade), + upgrade: flag(upgrade, no_upgrade, "upgrade"), upgrade_package: if upgrade_package.is_empty() { None } else { Some(upgrade_package) }, - reinstall: flag(reinstall, no_reinstall), + reinstall: flag(reinstall, no_reinstall, "reinstall"), reinstall_package: if reinstall_package.is_empty() { None } else { @@ -410,7 +428,7 @@ pub fn resolver_installer_options( dependency_metadata: None, config_settings: config_setting .map(|config_settings| config_settings.into_iter().collect::()), - no_build_isolation: flag(no_build_isolation, build_isolation), + no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"), no_build_isolation_package: if no_build_isolation_package.is_empty() { None } else { @@ -418,14 +436,14 @@ pub fn resolver_installer_options( }, exclude_newer, link_mode, - compile_bytecode: flag(compile_bytecode, no_compile_bytecode), - no_build: flag(no_build, build), + compile_bytecode: flag(compile_bytecode, no_compile_bytecode, "compile-bytecode"), + no_build: flag(no_build, build, "build"), no_build_package: if no_build_package.is_empty() { None } else { Some(no_build_package) }, - no_binary: flag(no_binary, binary), + no_binary: flag(no_binary, binary, "binary"), no_binary_package: if no_binary_package.is_empty() { None } else { diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 58a012d89..004ce5053 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -118,16 +118,20 @@ impl GlobalSettings { }, show_settings: args.show_settings, preview: PreviewMode::from( - flag(args.preview, args.no_preview) + flag(args.preview, args.no_preview, "preview") .combine(workspace.and_then(|workspace| workspace.globals.preview)) .unwrap_or(false), ), python_preference, - python_downloads: flag(args.allow_python_downloads, args.no_python_downloads) - .map(PythonDownloads::from) - .combine(env(env::UV_PYTHON_DOWNLOADS)) - .combine(workspace.and_then(|workspace| workspace.globals.python_downloads)) - .unwrap_or_default(), + python_downloads: flag( + args.allow_python_downloads, + args.no_python_downloads, + "python-downloads", + ) + .map(PythonDownloads::from) + .combine(env(env::UV_PYTHON_DOWNLOADS)) + .combine(workspace.and_then(|workspace| workspace.globals.python_downloads)) + .unwrap_or_default(), // Disable the progress bar with `RUST_LOG` to avoid progress fragments interleaving // with log messages. no_progress: args.no_progress || std::env::var_os(EnvVars::RUST_LOG).is_some(), @@ -161,7 +165,7 @@ pub(crate) struct NetworkSettings { impl NetworkSettings { pub(crate) fn resolve(args: &GlobalArgs, workspace: Option<&FilesystemOptions>) -> Self { - let connectivity = if flag(args.offline, args.no_offline) + let connectivity = if flag(args.offline, args.no_offline, "offline") .combine(workspace.and_then(|workspace| workspace.globals.offline)) .unwrap_or(false) { @@ -169,7 +173,7 @@ impl NetworkSettings { } else { Connectivity::Online }; - let native_tls = flag(args.native_tls, args.no_native_tls) + let native_tls = flag(args.native_tls, args.no_native_tls, "native-tls") .combine(workspace.and_then(|workspace| workspace.globals.native_tls)) .unwrap_or(false); let allow_insecure_host = args @@ -274,8 +278,12 @@ impl InitSettings { (_, _, _) => unreachable!("`app`, `lib`, and `script` are mutually exclusive"), }; - let package = flag(package || build_backend.is_some(), no_package || r#virtual) - .unwrap_or(kind.packaged_by_default()); + let package = flag( + package || build_backend.is_some(), + no_package || r#virtual, + "virtual", + ) + .unwrap_or(kind.packaged_by_default()); let install_mirrors = filesystem .map(|fs| fs.install_mirrors.clone()) @@ -295,7 +303,7 @@ impl InitSettings { build_backend, no_readme: no_readme || bare, author_from, - pin_python: flag(pin_python, no_pin_python).unwrap_or(!bare), + pin_python: flag(pin_python, no_pin_python, "pin-python").unwrap_or(!bare), no_workspace, python: python.and_then(Maybe::into_option), install_mirrors, @@ -398,7 +406,7 @@ impl RunSettings { false, // TODO(blueraft): support only_extra vec![], - flag(all_extras, no_all_extras).unwrap_or_default(), + flag(all_extras, no_all_extras, "all-extras").unwrap_or_default(), ), groups: DependencyGroups::from_args( dev, @@ -411,7 +419,7 @@ impl RunSettings { all_groups, ), editable: EditableMode::from_args(no_editable), - modifications: if flag(exact, inexact).unwrap_or(false) { + modifications: if flag(exact, inexact, "inexact").unwrap_or(false) { Modifications::Exact } else { Modifications::Sufficient @@ -434,7 +442,7 @@ impl RunSettings { package, no_project, no_sync, - active: flag(active, no_active), + active: flag(active, no_active, "active"), python: python.and_then(Maybe::into_option), refresh: Refresh::from(refresh), settings: ResolverInstallerSettings::combine( @@ -1081,7 +1089,7 @@ impl PythonFindSettings { request, show_version, no_project, - system: flag(system, no_system).unwrap_or_default(), + system: flag(system, no_system, "system").unwrap_or_default(), } } } @@ -1116,7 +1124,7 @@ impl PythonPinSettings { Self { request, - resolved: flag(resolved, no_resolved).unwrap_or(false), + resolved: flag(resolved, no_resolved, "resolved").unwrap_or(false), no_project, global, rm, @@ -1195,7 +1203,7 @@ impl SyncSettings { filesystem, ); - let check = flag(check, no_check).unwrap_or_default(); + let check = flag(check, no_check, "check").unwrap_or_default(); let dry_run = if check { DryRun::Check } else { @@ -1207,7 +1215,7 @@ impl SyncSettings { frozen, dry_run, script, - active: flag(active, no_active), + active: flag(active, no_active, "active"), extras: ExtrasSpecification::from_args( extra.unwrap_or_default(), no_extra, @@ -1215,7 +1223,7 @@ impl SyncSettings { false, // TODO(blueraft): support only_extra vec![], - flag(all_extras, no_all_extras).unwrap_or_default(), + flag(all_extras, no_all_extras, "all-extras").unwrap_or_default(), ), groups: DependencyGroups::from_args( dev, @@ -1233,7 +1241,7 @@ impl SyncSettings { no_install_workspace, no_install_package, ), - modifications: if flag(exact, inexact).unwrap_or(true) { + modifications: if flag(exact, inexact, "inexact").unwrap_or(true) { Modifications::Exact } else { Modifications::Sufficient @@ -1437,7 +1445,7 @@ impl AddSettings { Self { locked, frozen, - active: flag(active, no_active), + active: flag(active, no_active, "active"), no_sync, packages, requirements, @@ -1455,7 +1463,7 @@ impl AddSettings { package, script, python: python.and_then(Maybe::into_option), - editable: flag(editable, no_editable), + editable: flag(editable, no_editable, "editable"), extras: extra.unwrap_or_default(), refresh: Refresh::from(refresh), indexes, @@ -1531,7 +1539,7 @@ impl RemoveSettings { Self { locked, frozen, - active: flag(active, no_active), + active: flag(active, no_active, "active"), no_sync, packages, dependency_type, @@ -1603,7 +1611,7 @@ impl VersionSettings { dry_run, locked, frozen, - active: flag(active, no_active), + active: flag(active, no_active, "active"), no_sync, package, python: python.and_then(Maybe::into_option), @@ -1779,7 +1787,7 @@ impl ExportSettings { false, // TODO(blueraft): support only_extra vec![], - flag(all_extras, no_all_extras).unwrap_or_default(), + flag(all_extras, no_all_extras, "all-extras").unwrap_or_default(), ), groups: DependencyGroups::from_args( dev, @@ -1792,7 +1800,7 @@ impl ExportSettings { all_groups, ), editable: EditableMode::from_args(no_editable), - hashes: flag(hashes, no_hashes).unwrap_or(true), + hashes: flag(hashes, no_hashes, "hashes").unwrap_or(true), install_options: InstallOptions::new( no_emit_project, no_emit_workspace, @@ -1801,8 +1809,8 @@ impl ExportSettings { output_file, locked, frozen, - include_annotations: flag(annotate, no_annotate).unwrap_or(true), - include_header: flag(header, no_header).unwrap_or(true), + include_annotations: flag(annotate, no_annotate, "annotate").unwrap_or(true), + include_header: flag(header, no_header, "header").unwrap_or(true), script, python: python.and_then(Maybe::into_option), refresh: Refresh::from(refresh), @@ -1955,30 +1963,42 @@ impl PipCompileSettings { settings: PipSettings::combine( PipOptions { python: python.and_then(Maybe::into_option), - system: flag(system, no_system), - no_build: flag(no_build, build), + system: flag(system, no_system, "system"), + no_build: flag(no_build, build, "build"), no_binary, only_binary, extra, - all_extras: flag(all_extras, no_all_extras), - no_deps: flag(no_deps, deps), + all_extras: flag(all_extras, no_all_extras, "all-extras"), + no_deps: flag(no_deps, deps, "deps"), group: Some(group), output_file, - no_strip_extras: flag(no_strip_extras, strip_extras), - no_strip_markers: flag(no_strip_markers, strip_markers), - no_annotate: flag(no_annotate, annotate), - no_header: flag(no_header, header), + no_strip_extras: flag(no_strip_extras, strip_extras, "strip-extras"), + no_strip_markers: flag(no_strip_markers, strip_markers, "strip-markers"), + no_annotate: flag(no_annotate, annotate, "annotate"), + no_header: flag(no_header, header, "header"), custom_compile_command, - generate_hashes: flag(generate_hashes, no_generate_hashes), + generate_hashes: flag(generate_hashes, no_generate_hashes, "generate-hashes"), python_version, python_platform, - universal: flag(universal, no_universal), + universal: flag(universal, no_universal, "universal"), no_emit_package, - emit_index_url: flag(emit_index_url, no_emit_index_url), - emit_find_links: flag(emit_find_links, no_emit_find_links), - emit_build_options: flag(emit_build_options, no_emit_build_options), - emit_marker_expression: flag(emit_marker_expression, no_emit_marker_expression), - emit_index_annotation: flag(emit_index_annotation, no_emit_index_annotation), + emit_index_url: flag(emit_index_url, no_emit_index_url, "emit-index-url"), + emit_find_links: flag(emit_find_links, no_emit_find_links, "emit-find-links"), + emit_build_options: flag( + emit_build_options, + no_emit_build_options, + "emit-build-options", + ), + emit_marker_expression: flag( + emit_marker_expression, + no_emit_marker_expression, + "emit-marker-expression", + ), + emit_index_annotation: flag( + emit_index_annotation, + no_emit_index_annotation, + "emit-index-annotation", + ), annotation_style, torch_backend, ..PipOptions::from(resolver) @@ -2050,22 +2070,27 @@ impl PipSyncSettings { settings: PipSettings::combine( PipOptions { python: python.and_then(Maybe::into_option), - system: flag(system, no_system), - break_system_packages: flag(break_system_packages, no_break_system_packages), + system: flag(system, no_system, "system"), + break_system_packages: flag( + break_system_packages, + no_break_system_packages, + "break-system-packages", + ), target, prefix, - require_hashes: flag(require_hashes, no_require_hashes), - verify_hashes: flag(verify_hashes, no_verify_hashes), - no_build: flag(no_build, build), + require_hashes: flag(require_hashes, no_require_hashes, "require-hashes"), + verify_hashes: flag(verify_hashes, no_verify_hashes, "verify-hashes"), + no_build: flag(no_build, build, "build"), no_binary, only_binary, allow_empty_requirements: flag( allow_empty_requirements, no_allow_empty_requirements, + "allow-empty-requirements", ), python_version, python_platform, - strict: flag(strict, no_strict), + strict: flag(strict, no_strict, "strict"), torch_backend, ..PipOptions::from(installer) }, @@ -2199,7 +2224,7 @@ impl PipInstallSettings { constraints_from_workspace, overrides_from_workspace, build_constraints_from_workspace, - modifications: if flag(exact, inexact).unwrap_or(false) { + modifications: if flag(exact, inexact, "inexact").unwrap_or(false) { Modifications::Exact } else { Modifications::Sufficient @@ -2208,22 +2233,26 @@ impl PipInstallSettings { settings: PipSettings::combine( PipOptions { python: python.and_then(Maybe::into_option), - system: flag(system, no_system), - break_system_packages: flag(break_system_packages, no_break_system_packages), + system: flag(system, no_system, "system"), + break_system_packages: flag( + break_system_packages, + no_break_system_packages, + "break-system-packages", + ), target, prefix, - no_build: flag(no_build, build), + no_build: flag(no_build, build, "build"), no_binary, only_binary, - strict: flag(strict, no_strict), + strict: flag(strict, no_strict, "strict"), extra, - all_extras: flag(all_extras, no_all_extras), + all_extras: flag(all_extras, no_all_extras, "all-extras"), group: Some(group), - no_deps: flag(no_deps, deps), + no_deps: flag(no_deps, deps, "deps"), python_version, python_platform, - require_hashes: flag(require_hashes, no_require_hashes), - verify_hashes: flag(verify_hashes, no_verify_hashes), + require_hashes: flag(require_hashes, no_require_hashes, "require-hashes"), + verify_hashes: flag(verify_hashes, no_verify_hashes, "verify-hashes"), torch_backend, ..PipOptions::from(installer) }, @@ -2267,8 +2296,12 @@ impl PipUninstallSettings { settings: PipSettings::combine( PipOptions { python: python.and_then(Maybe::into_option), - system: flag(system, no_system), - break_system_packages: flag(break_system_packages, no_break_system_packages), + system: flag(system, no_system, "system"), + break_system_packages: flag( + break_system_packages, + no_break_system_packages, + "break-system-packages", + ), target, prefix, keyring_provider, @@ -2308,8 +2341,8 @@ impl PipFreezeSettings { settings: PipSettings::combine( PipOptions { python: python.and_then(Maybe::into_option), - system: flag(system, no_system), - strict: flag(strict, no_strict), + system: flag(system, no_system, "system"), + strict: flag(strict, no_strict, "strict"), ..PipOptions::default() }, filesystem, @@ -2348,15 +2381,15 @@ impl PipListSettings { } = args; Self { - editable: flag(editable, exclude_editable), + editable: flag(editable, exclude_editable, "exclude-editable"), exclude, format, - outdated: flag(outdated, no_outdated).unwrap_or(false), + outdated: flag(outdated, no_outdated, "outdated").unwrap_or(false), settings: PipSettings::combine( PipOptions { python: python.and_then(Maybe::into_option), - system: flag(system, no_system), - strict: flag(strict, no_strict), + system: flag(system, no_system, "system"), + strict: flag(strict, no_strict, "strict"), ..PipOptions::from(fetch) }, filesystem, @@ -2393,8 +2426,8 @@ impl PipShowSettings { settings: PipSettings::combine( PipOptions { python: python.and_then(Maybe::into_option), - system: flag(system, no_system), - strict: flag(strict, no_strict), + system: flag(system, no_system, "system"), + strict: flag(strict, no_strict, "strict"), ..PipOptions::default() }, filesystem, @@ -2442,8 +2475,8 @@ impl PipTreeSettings { settings: PipSettings::combine( PipOptions { python: python.and_then(Maybe::into_option), - system: flag(system, no_system), - strict: flag(strict, no_strict), + system: flag(system, no_system, "system"), + strict: flag(strict, no_strict, "strict"), ..PipOptions::from(fetch) }, filesystem, @@ -2471,7 +2504,7 @@ impl PipCheckSettings { settings: PipSettings::combine( PipOptions { python: python.and_then(Maybe::into_option), - system: flag(system, no_system), + system: flag(system, no_system, "system"), ..PipOptions::default() }, filesystem, @@ -2538,15 +2571,15 @@ impl BuildSettings { sdist, wheel, list, - build_logs: flag(build_logs, no_build_logs).unwrap_or(true), + build_logs: flag(build_logs, no_build_logs, "build-logs").unwrap_or(true), build_constraints: build_constraints .into_iter() .filter_map(Maybe::into_option) .collect(), force_pep517, hash_checking: HashCheckingMode::from_args( - flag(require_hashes, no_require_hashes), - flag(verify_hashes, no_verify_hashes), + flag(require_hashes, no_require_hashes, "require-hashes"), + flag(verify_hashes, no_verify_hashes, "verify-hashes"), ), python: python.and_then(Maybe::into_option), refresh: Refresh::from(refresh), @@ -2605,7 +2638,7 @@ impl VenvSettings { settings: PipSettings::combine( PipOptions { python: python.and_then(Maybe::into_option), - system: flag(system, no_system), + system: flag(system, no_system, "system"), index_strategy, keyring_provider, exclude_newer, diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index 91da3ce81..76b108a81 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -11486,3 +11486,25 @@ fn pep_751_dependency() -> Result<()> { Ok(()) } + +/// Test that we show an error instead of panicking for conflicting arguments in different levels, +/// which are not caught by clap. +#[test] +fn conflicting_flags_clap_bug() { + let context = TestContext::new("3.12"); + + uv_snapshot!(context.filters(), context.command() + .arg("pip") + .arg("--offline") + .arg("install") + .arg("--no-offline") + .arg("tqdm"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: `--offline` and `--no-offline` cannot be used together. Boolean flags on different levels are currently not supported (https://github.com/clap-rs/clap/issues/6049) + " + ); +} From 87e9ccfb92e2761433bebddbdd68895de0e20abd Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 1 Jul 2025 15:30:44 -0500 Subject: [PATCH 126/349] Bump version to 0.7.18 (#14402) --- CHANGELOG.md | 34 +++++++++++++++++++++++++++ Cargo.lock | 6 ++--- crates/uv-build/Cargo.toml | 2 +- crates/uv-build/pyproject.toml | 2 +- crates/uv-version/Cargo.toml | 2 +- crates/uv/Cargo.toml | 2 +- docs/concepts/build-backend.md | 2 +- 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/pre-commit.md | 10 ++++---- pyproject.toml | 2 +- 13 files changed, 58 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc982ad71..2605248ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,40 @@ + +## 0.7.18 + +### Python + +- Added arm64 Windows Python 3.11, 3.12, 3.13, and 3.14 + + These are not downloaded by default, since x86-64 Python has broader ecosystem support on Windows. + However, they can be requested with `cpython--windows-aarch64`. + +### Enhancements + +- Keep track of retries in `ManagedPythonDownload::fetch_with_retry` ([#14378](https://github.com/astral-sh/uv/pull/14378)) +- Reuse build (virtual) environments across resolution and installation ([#14338](https://github.com/astral-sh/uv/pull/14338)) +- Improve trace message for cached Python interpreter query ([#14328](https://github.com/astral-sh/uv/pull/14328)) +- Use parsed URLs for conflicting URL error message ([#14380](https://github.com/astral-sh/uv/pull/14380)) + +### Preview features + +- Ignore invalid build backend settings when not building ([#14372](https://github.com/astral-sh/uv/pull/14372)) + +### Bug fixes + +- Fix equals-star and tilde-equals with `python_version` and `python_full_version` ([#14271](https://github.com/astral-sh/uv/pull/14271)) +- Include the canonical path in the interpreter query cache key ([#14331](https://github.com/astral-sh/uv/pull/14331)) +- Only drop build directories on program exit ([#14304](https://github.com/astral-sh/uv/pull/14304)) +- Error instead of panic on conflict between global and subcommand flags ([#14368](https://github.com/astral-sh/uv/pull/14368)) +- Consistently normalize trailing slashes on URLs with no path segments ([#14349](https://github.com/astral-sh/uv/pull/14349)) + +### Documentation + +- Add instructions for publishing to JFrog's Artifactory ([#14253](https://github.com/astral-sh/uv/pull/14253)) +- Edits to the build backend documentation ([#14376](https://github.com/astral-sh/uv/pull/14376)) + ## 0.7.17 ### Bug fixes diff --git a/Cargo.lock b/Cargo.lock index 802b069f5..881cd8423 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4603,7 +4603,7 @@ dependencies = [ [[package]] name = "uv" -version = "0.7.17" +version = "0.7.18" dependencies = [ "anstream", "anyhow", @@ -4767,7 +4767,7 @@ dependencies = [ [[package]] name = "uv-build" -version = "0.7.17" +version = "0.7.18" dependencies = [ "anyhow", "uv-build-backend", @@ -5957,7 +5957,7 @@ dependencies = [ [[package]] name = "uv-version" -version = "0.7.17" +version = "0.7.18" [[package]] name = "uv-virtualenv" diff --git a/crates/uv-build/Cargo.toml b/crates/uv-build/Cargo.toml index 8897a421d..83c37cd15 100644 --- a/crates/uv-build/Cargo.toml +++ b/crates/uv-build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-build" -version = "0.7.17" +version = "0.7.18" edition.workspace = true rust-version.workspace = true homepage.workspace = true diff --git a/crates/uv-build/pyproject.toml b/crates/uv-build/pyproject.toml index ae0ecde47..f48e0ddff 100644 --- a/crates/uv-build/pyproject.toml +++ b/crates/uv-build/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uv-build" -version = "0.7.17" +version = "0.7.18" description = "The uv build backend" authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }] requires-python = ">=3.8" diff --git a/crates/uv-version/Cargo.toml b/crates/uv-version/Cargo.toml index 2d0c3d096..c17f17695 100644 --- a/crates/uv-version/Cargo.toml +++ b/crates/uv-version/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-version" -version = "0.7.17" +version = "0.7.18" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml index 5f0ea848e..1b8d878ee 100644 --- a/crates/uv/Cargo.toml +++ b/crates/uv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv" -version = "0.7.17" +version = "0.7.18" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } diff --git a/docs/concepts/build-backend.md b/docs/concepts/build-backend.md index dd7685756..c78fa55a9 100644 --- a/docs/concepts/build-backend.md +++ b/docs/concepts/build-backend.md @@ -24,7 +24,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.7.17,<0.8.0"] +requires = ["uv_build>=0.7.18,<0.8.0"] build-backend = "uv_build" ``` diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 62f9f50ae..227b4df43 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.7.17/install.sh | sh + $ curl -LsSf https://astral.sh/uv/0.7.18/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.7.17/install.ps1 | iex" + PS> powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/0.7.18/install.ps1 | iex" ``` !!! tip diff --git a/docs/guides/integration/aws-lambda.md b/docs/guides/integration/aws-lambda.md index a0ee99671..db45f1016 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.7.17 AS uv +FROM ghcr.io/astral-sh/uv:0.7.18 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.7.17 AS uv +FROM ghcr.io/astral-sh/uv:0.7.18 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 48d7b6399..27416a0fa 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.7.17` +- `ghcr.io/astral-sh/uv:{major}.{minor}.{patch}`, e.g., `ghcr.io/astral-sh/uv:0.7.18` - `ghcr.io/astral-sh/uv:{major}.{minor}`, e.g., `ghcr.io/astral-sh/uv:0.7` (the latest patch version) @@ -75,7 +75,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.7.17-alpine`. +`ghcr.io/astral-sh/uv:{major}.{minor}-{base}`, e.g., `ghcr.io/astral-sh/uv:0.7.18-alpine`. For more details, see the [GitHub Container](https://github.com/astral-sh/uv/pkgs/container/uv) page. @@ -113,7 +113,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.7.17 /uv /uvx /bin/ +COPY --from=ghcr.io/astral-sh/uv:0.7.18 /uv /uvx /bin/ ``` !!! tip @@ -131,7 +131,7 @@ COPY --from=ghcr.io/astral-sh/uv:0.7.17 /uv /uvx /bin/ Or, with the installer: ```dockerfile -ADD https://astral.sh/uv/0.7.17/install.sh /uv-installer.sh +ADD https://astral.sh/uv/0.7.18/install.sh /uv-installer.sh ``` ### Installing a project @@ -557,5 +557,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.7.17`, or (even better) the specific image digest, + version tag, e.g., `ghcr.io/astral-sh/uv:0.7.18`, 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 f58fe2782..60a18b8b4 100644 --- a/docs/guides/integration/github.md +++ b/docs/guides/integration/github.md @@ -47,7 +47,7 @@ jobs: uses: astral-sh/setup-uv@v5 with: # Install a specific version of uv. - version: "0.7.17" + version: "0.7.18" ``` ## Setting up Python diff --git a/docs/guides/integration/pre-commit.md b/docs/guides/integration/pre-commit.md index 71fe7394f..dbbc88462 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.7.17 + rev: 0.7.18 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.7.17 + rev: 0.7.18 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.7.17 + rev: 0.7.18 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.7.17 + rev: 0.7.18 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.7.17 + rev: 0.7.18 hooks: # Compile requirements - id: pip-compile diff --git a/pyproject.toml b/pyproject.toml index 078bd4ebd..7e73fa84c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "uv" -version = "0.7.17" +version = "0.7.18" 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" From e40d3d5dffe03ce727ae554e9479d4c5b7c0cd53 Mon Sep 17 00:00:00 2001 From: John Mumm Date: Wed, 2 Jul 2025 13:50:35 +0200 Subject: [PATCH 127/349] Re-enable Artifactory in the registries integration test (#14408) Having worked out the account issue, I've re-enabled Artifactory in the registries test. --- .github/workflows/ci.yml | 6 +++--- scripts/registries-test.py | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 701699239..35ceb6875 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1570,9 +1570,9 @@ jobs: run: ./uv run -p ${{ env.PYTHON_VERSION }} scripts/registries-test.py --uv ./uv --color always --all env: RUST_LOG: uv=debug - # UV_TEST_ARTIFACTORY_TOKEN: ${{ secrets.UV_TEST_ARTIFACTORY_TOKEN }} - # UV_TEST_ARTIFACTORY_URL: ${{ secrets.UV_TEST_ARTIFACTORY_URL }} - # UV_TEST_ARTIFACTORY_USERNAME: ${{ secrets.UV_TEST_ARTIFACTORY_USERNAME }} + UV_TEST_ARTIFACTORY_TOKEN: ${{ secrets.UV_TEST_ARTIFACTORY_TOKEN }} + UV_TEST_ARTIFACTORY_URL: ${{ secrets.UV_TEST_ARTIFACTORY_URL }} + UV_TEST_ARTIFACTORY_USERNAME: ${{ secrets.UV_TEST_ARTIFACTORY_USERNAME }} UV_TEST_AWS_URL: ${{ secrets.UV_TEST_AWS_URL }} UV_TEST_AWS_USERNAME: aws UV_TEST_AZURE_TOKEN: ${{ secrets.UV_TEST_AZURE_TOKEN }} diff --git a/scripts/registries-test.py b/scripts/registries-test.py index b6bbf9b59..2d4c1d2aa 100644 --- a/scripts/registries-test.py +++ b/scripts/registries-test.py @@ -56,8 +56,7 @@ DEFAULT_TIMEOUT = 30 DEFAULT_PKG_NAME = "astral-registries-test-pkg" KNOWN_REGISTRIES = [ - # TODO(john): Restore this when subscription starts up again - # "artifactory", + "artifactory", "azure", "aws", "cloudsmith", From bf5dcf99294588015feacebb0720f6da54af3c9a Mon Sep 17 00:00:00 2001 From: konsti Date: Wed, 2 Jul 2025 15:25:56 +0200 Subject: [PATCH 128/349] Reduce index credential stashing code duplication (#14419) Reduces some duplicate code around index credentials. --- crates/uv-distribution-types/src/index_url.rs | 13 ++++++ crates/uv/src/commands/build_frontend.rs | 12 +---- crates/uv/src/commands/pip/compile.rs | 12 +---- crates/uv/src/commands/pip/install.rs | 12 +---- crates/uv/src/commands/pip/sync.rs | 12 +---- crates/uv/src/commands/project/add.rs | 11 +---- crates/uv/src/commands/project/lock.rs | 11 +---- crates/uv/src/commands/project/mod.rs | 44 ++----------------- crates/uv/src/commands/project/sync.rs | 11 +---- crates/uv/src/commands/venv.rs | 11 +---- 10 files changed, 25 insertions(+), 124 deletions(-) diff --git a/crates/uv-distribution-types/src/index_url.rs b/crates/uv-distribution-types/src/index_url.rs index eb91e852e..0290018f1 100644 --- a/crates/uv-distribution-types/src/index_url.rs +++ b/crates/uv-distribution-types/src/index_url.rs @@ -462,6 +462,19 @@ impl<'a> IndexLocations { indexes } } + + /// Add all authenticated sources to the cache. + pub fn cache_index_credentials(&self) { + for index in self.allowed_indexes() { + if let Some(credentials) = index.credentials() { + let credentials = Arc::new(credentials); + uv_auth::store_credentials(index.raw_url(), credentials.clone()); + if let Some(root_url) = index.root_url() { + uv_auth::store_credentials(&root_url, credentials.clone()); + } + } + } + } } impl From<&IndexLocations> for uv_auth::Indexes { diff --git a/crates/uv/src/commands/build_frontend.rs b/crates/uv/src/commands/build_frontend.rs index 9b97b40b1..bccb99fae 100644 --- a/crates/uv/src/commands/build_frontend.rs +++ b/crates/uv/src/commands/build_frontend.rs @@ -3,7 +3,6 @@ use std::fmt::Write as _; use std::io::Write as _; use std::path::{Path, PathBuf}; use std::str::FromStr; -use std::sync::Arc; use std::{fmt, io}; use anyhow::{Context, Result}; @@ -504,16 +503,7 @@ async fn build_package( .await? .into_interpreter(); - // Add all authenticated sources to the cache. - for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials() { - let credentials = Arc::new(credentials); - uv_auth::store_credentials(index.raw_url(), credentials.clone()); - if let Some(root_url) = index.root_url() { - uv_auth::store_credentials(&root_url, credentials.clone()); - } - } - } + index_locations.cache_index_credentials(); // Read build constraints. let build_constraints = diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs index db80c2a8a..3aa3fb0e9 100644 --- a/crates/uv/src/commands/pip/compile.rs +++ b/crates/uv/src/commands/pip/compile.rs @@ -3,7 +3,6 @@ use std::env; use std::ffi::OsStr; use std::path::{Path, PathBuf}; use std::str::FromStr; -use std::sync::Arc; use anyhow::{Result, anyhow}; use itertools::Itertools; @@ -388,16 +387,7 @@ pub(crate) async fn pip_compile( no_index, ); - // Add all authenticated sources to the cache. - for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials() { - let credentials = Arc::new(credentials); - uv_auth::store_credentials(index.raw_url(), credentials.clone()); - if let Some(root_url) = index.root_url() { - uv_auth::store_credentials(&root_url, credentials.clone()); - } - } - } + index_locations.cache_index_credentials(); // Determine the PyTorch backend. let torch_backend = torch_backend diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs index a92c36665..b7d32dd94 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -1,7 +1,6 @@ use std::collections::{BTreeMap, BTreeSet}; use std::fmt::Write; use std::path::PathBuf; -use std::sync::Arc; use anyhow::Context; use itertools::Itertools; @@ -334,16 +333,7 @@ pub(crate) async fn pip_install( no_index, ); - // Add all authenticated sources to the cache. - for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials() { - let credentials = Arc::new(credentials); - uv_auth::store_credentials(index.raw_url(), credentials.clone()); - if let Some(root_url) = index.root_url() { - uv_auth::store_credentials(&root_url, credentials.clone()); - } - } - } + index_locations.cache_index_credentials(); // Determine the PyTorch backend. let torch_backend = torch_backend diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index e5145400a..ab4c42ce5 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -1,6 +1,5 @@ use std::collections::{BTreeMap, BTreeSet}; use std::fmt::Write; -use std::sync::Arc; use anyhow::{Context, Result}; use owo_colors::OwoColorize; @@ -267,16 +266,7 @@ pub(crate) async fn pip_sync( no_index, ); - // Add all authenticated sources to the cache. - for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials() { - let credentials = Arc::new(credentials); - uv_auth::store_credentials(index.raw_url(), credentials.clone()); - if let Some(root_url) = index.root_url() { - uv_auth::store_credentials(&root_url, credentials.clone()); - } - } - } + index_locations.cache_index_credentials(); // Determine the PyTorch backend. let torch_backend = torch_backend diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index bd3b49a3f..719821df5 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -374,16 +374,7 @@ pub(crate) async fn add( let hasher = HashStrategy::default(); let sources = SourceStrategy::Enabled; - // Add all authenticated sources to the cache. - for index in settings.resolver.index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials() { - let credentials = Arc::new(credentials); - uv_auth::store_credentials(index.raw_url(), credentials.clone()); - if let Some(root_url) = index.root_url() { - uv_auth::store_credentials(&root_url, credentials.clone()); - } - } - } + settings.resolver.index_locations.cache_index_credentials(); // Initialize the registry client. let client = RegistryClientBuilder::try_from(client_builder)? diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index 2bcb68eb3..cd4242833 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -593,16 +593,7 @@ async fn do_lock( .keyring(*keyring_provider) .allow_insecure_host(network_settings.allow_insecure_host.clone()); - // Add all authenticated sources to the cache. - for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials() { - let credentials = Arc::new(credentials); - uv_auth::store_credentials(index.raw_url(), credentials.clone()); - if let Some(root_url) = index.root_url() { - uv_auth::store_credentials(&root_url, credentials.clone()); - } - } - } + index_locations.cache_index_credentials(); for index in target.indexes() { if let Some(credentials) = index.credentials() { diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index c84253eaa..5ed6293e2 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -1626,16 +1626,7 @@ pub(crate) async fn resolve_names( .keyring(*keyring_provider) .allow_insecure_host(network_settings.allow_insecure_host.clone()); - // Add all authenticated sources to the cache. - for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials() { - let credentials = Arc::new(credentials); - uv_auth::store_credentials(index.raw_url(), credentials.clone()); - if let Some(root_url) = index.root_url() { - uv_auth::store_credentials(&root_url, credentials.clone()); - } - } - } + index_locations.cache_index_credentials(); // Initialize the registry client. let client = RegistryClientBuilder::try_from(client_builder)? @@ -1797,16 +1788,7 @@ pub(crate) async fn resolve_environment( let marker_env = interpreter.resolver_marker_environment(); let python_requirement = PythonRequirement::from_interpreter(interpreter); - // Add all authenticated sources to the cache. - for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials() { - let credentials = Arc::new(credentials); - uv_auth::store_credentials(index.raw_url(), credentials.clone()); - if let Some(root_url) = index.root_url() { - uv_auth::store_credentials(&root_url, credentials.clone()); - } - } - } + index_locations.cache_index_credentials(); // Initialize the registry client. let client = RegistryClientBuilder::try_from(client_builder)? @@ -1978,16 +1960,7 @@ pub(crate) async fn sync_environment( let interpreter = venv.interpreter(); let tags = venv.interpreter().tags()?; - // Add all authenticated sources to the cache. - for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials() { - let credentials = Arc::new(credentials); - uv_auth::store_credentials(index.raw_url(), credentials.clone()); - if let Some(root_url) = index.root_url() { - uv_auth::store_credentials(&root_url, credentials.clone()); - } - } - } + index_locations.cache_index_credentials(); // Initialize the registry client. let client = RegistryClientBuilder::try_from(client_builder)? @@ -2193,16 +2166,7 @@ pub(crate) async fn update_environment( } } - // Add all authenticated sources to the cache. - for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials() { - let credentials = Arc::new(credentials); - uv_auth::store_credentials(index.raw_url(), credentials.clone()); - if let Some(root_url) = index.root_url() { - uv_auth::store_credentials(&root_url, credentials.clone()); - } - } - } + index_locations.cache_index_credentials(); // Initialize the registry client. let client = RegistryClientBuilder::try_from(client_builder)? diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index 19848ee02..dc9f0dcbb 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -682,16 +682,7 @@ pub(super) async fn do_sync( // If necessary, convert editable to non-editable distributions. let resolution = apply_editable_mode(resolution, editable); - // Add all authenticated sources to the cache. - for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials() { - let credentials = Arc::new(credentials); - uv_auth::store_credentials(index.raw_url(), credentials.clone()); - if let Some(root_url) = index.root_url() { - uv_auth::store_credentials(&root_url, credentials.clone()); - } - } - } + index_locations.cache_index_credentials(); // Populate credentials from the target. store_credentials_from_target(target); diff --git a/crates/uv/src/commands/venv.rs b/crates/uv/src/commands/venv.rs index fe20634d0..9334d844d 100644 --- a/crates/uv/src/commands/venv.rs +++ b/crates/uv/src/commands/venv.rs @@ -242,16 +242,7 @@ async fn venv_impl( python.into_interpreter() }; - // Add all authenticated sources to the cache. - for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials() { - let credentials = Arc::new(credentials); - uv_auth::store_credentials(index.raw_url(), credentials.clone()); - if let Some(root_url) = index.root_url() { - uv_auth::store_credentials(&root_url, credentials.clone()); - } - } - } + index_locations.cache_index_credentials(); // Check if the discovered Python version is incompatible with the current workspace if let Some(requires_python) = requires_python { From b0db548c8070bf4c1fc78c3f65e34f30ab1d3b64 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 2 Jul 2025 08:39:58 -0500 Subject: [PATCH 129/349] Bump the test timeout from 90s -> 120s (#14170) In hopes of resolving https://github.com/astral-sh/uv/issues/14158 We should also see why the tests are so slow though. --- .config/nextest.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.config/nextest.toml b/.config/nextest.toml index c063bb861..cc1a18dbe 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -1,4 +1,4 @@ [profile.default] # Mark tests that take longer than 10s as slow. -# Terminate after 90s as a stop-gap measure to terminate on deadlock. -slow-timeout = { period = "10s", terminate-after = 9 } +# Terminate after 120s as a stop-gap measure to terminate on deadlock. +slow-timeout = { period = "10s", terminate-after = 12 } From a7aa46acc571f20cf3fb2ce6fb26219ccbbd44f6 Mon Sep 17 00:00:00 2001 From: konsti Date: Wed, 2 Jul 2025 16:02:03 +0200 Subject: [PATCH 130/349] Add a "choosing a build backend" section to the docs (#14295) I think the build backend docs as a whole are now ready for review. I only made a small change here. --------- Co-authored-by: Zanie Blue --- docs/concepts/build-backend.md | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/docs/concepts/build-backend.md b/docs/concepts/build-backend.md index c78fa55a9..d70f00282 100644 --- a/docs/concepts/build-backend.md +++ b/docs/concepts/build-backend.md @@ -12,13 +12,26 @@ uv supports all build backends (as specified by [PEP 517](https://peps.python.or also provides a native build backend (`uv_build`) that integrates tightly with uv to improve performance and user experience. +## Choosing a build backend + +The uv build backend is a good choice for most Python projects that are using uv. It has reasonable +defaults, with the goal of requiring zero configuration for most users, but provides flexible +configuration that allows most Python project structures. It integrates tightly with uv, to improve +messaging and user experience. It validates project metadata and structures, preventing common +mistakes. And, finally, it's very fast. + +The uv build backend currently **only supports pure Python code**. An alternative backend is +required to build a +[library with extension modules](../concepts/projects/init.md#projects-with-extension-modules). + +!!! tip + + While the backend supports a number of options for configuring your project structure, when build scripts or + a more flexible project layout are required, consider using the + [hatchling](https://hatch.pypa.io/latest/config/build/#build-system) build backend instead. + ## Using the uv build backend -!!! important - - The uv build backend currently **only supports pure Python code**. An alternative backend is to - build a [library with extension modules](../concepts/projects/init.md#projects-with-extension-modules). - To use uv as a build backend in an existing project, add `uv_build` to the [`[build-system]`](../concepts/projects/config.md#build-systems) section in your `pyproject.toml`: From 43f67a4a4cbbd6d8b5c0451d2e79dfca935910dc Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 2 Jul 2025 09:08:45 -0500 Subject: [PATCH 131/349] Update the tilde version specifier warning to include more context (#14335) Follows https://github.com/astral-sh/uv/pull/14008 --- .../src/requires_python.rs | 25 +----- crates/uv-pep440/src/lib.rs | 2 +- crates/uv-pep440/src/version_specifier.rs | 89 +++++++++++++++++-- crates/uv/src/commands/project/mod.rs | 26 +++++- crates/uv/tests/it/lock.rs | 6 +- 5 files changed, 115 insertions(+), 33 deletions(-) diff --git a/crates/uv-distribution-types/src/requires_python.rs b/crates/uv-distribution-types/src/requires_python.rs index cedfff843..49a4fd5c4 100644 --- a/crates/uv-distribution-types/src/requires_python.rs +++ b/crates/uv-distribution-types/src/requires_python.rs @@ -5,11 +5,10 @@ use version_ranges::Ranges; use uv_distribution_filename::WheelFilename; use uv_pep440::{ LowerBound, UpperBound, Version, VersionSpecifier, VersionSpecifiers, - release_specifier_to_range, release_specifiers_to_ranges, + release_specifiers_to_ranges, }; use uv_pep508::{MarkerExpression, MarkerTree, MarkerValueVersion}; use uv_platform_tags::{AbiTag, LanguageTag}; -use uv_warnings::warn_user_once; /// The `Requires-Python` requirement specifier. /// @@ -67,27 +66,7 @@ impl RequiresPython { ) -> Option { // Convert to PubGrub range and perform an intersection. let range = specifiers - .map(|specs| { - // Warn if there’s exactly one `~=` specifier without a patch. - if let [spec] = &specs[..] { - if spec.is_tilde_without_patch() { - if let Some((lo_b, hi_b)) = release_specifier_to_range(spec.clone(), false) - .bounding_range() - .map(|(l, u)| (l.cloned(), u.cloned())) - { - let lo_spec = LowerBound::new(lo_b).specifier().unwrap(); - let hi_spec = UpperBound::new(hi_b).specifier().unwrap(); - warn_user_once!( - "The release specifier (`{spec}`) contains a compatible release \ - match without a patch version. This will be interpreted as \ - `{lo_spec}, {hi_spec}`. Did you mean `{spec}.0` to freeze the \ - minor version?" - ); - } - } - } - release_specifiers_to_ranges(specs.clone()) - }) + .map(|specs| release_specifiers_to_ranges(specs.clone())) .reduce(|acc, r| acc.intersection(&r))?; // If the intersection is empty, return `None`. diff --git a/crates/uv-pep440/src/lib.rs b/crates/uv-pep440/src/lib.rs index 3d2e256ae..0e8b50e72 100644 --- a/crates/uv-pep440/src/lib.rs +++ b/crates/uv-pep440/src/lib.rs @@ -34,7 +34,7 @@ pub use { VersionPatternParseError, }, version_specifier::{ - VersionSpecifier, VersionSpecifierBuildError, VersionSpecifiers, + TildeVersionSpecifier, VersionSpecifier, VersionSpecifierBuildError, VersionSpecifiers, VersionSpecifiersParseError, }, }; diff --git a/crates/uv-pep440/src/version_specifier.rs b/crates/uv-pep440/src/version_specifier.rs index bbbe440bf..e111c5118 100644 --- a/crates/uv-pep440/src/version_specifier.rs +++ b/crates/uv-pep440/src/version_specifier.rs @@ -665,11 +665,6 @@ impl VersionSpecifier { | Operator::NotEqual => false, } } - - /// Returns true if this is a `~=` specifier without a patch version (e.g. `~=3.11`). - pub fn is_tilde_without_patch(&self) -> bool { - self.operator == Operator::TildeEqual && self.version.release().len() == 2 - } } impl FromStr for VersionSpecifier { @@ -893,6 +888,90 @@ pub(crate) fn parse_version_specifiers( Ok(version_ranges) } +/// A simple `~=` version specifier with a major, minor and (optional) patch version, e.g., `~=3.13` +/// or `~=3.13.0`. +#[derive(Clone, Debug)] +pub struct TildeVersionSpecifier<'a> { + inner: Cow<'a, VersionSpecifier>, +} + +impl<'a> TildeVersionSpecifier<'a> { + /// Create a new [`TildeVersionSpecifier`] from a [`VersionSpecifier`] value. + /// + /// If a [`Operator::TildeEqual`] is not used, or the version includes more than minor and patch + /// segments, this will return [`None`]. + pub fn from_specifier(specifier: VersionSpecifier) -> Option> { + TildeVersionSpecifier::new(Cow::Owned(specifier)) + } + + /// Create a new [`TildeVersionSpecifier`] from a [`VersionSpecifier`] reference. + /// + /// See [`TildeVersionSpecifier::from_specifier`]. + pub fn from_specifier_ref( + specifier: &'a VersionSpecifier, + ) -> Option> { + TildeVersionSpecifier::new(Cow::Borrowed(specifier)) + } + + fn new(specifier: Cow<'a, VersionSpecifier>) -> Option { + if specifier.operator != Operator::TildeEqual { + return None; + } + if specifier.version().release().len() < 2 || specifier.version().release().len() > 3 { + return None; + } + if specifier.version().any_prerelease() + || specifier.version().is_local() + || specifier.version().is_post() + { + return None; + } + Some(Self { inner: specifier }) + } + + /// Whether a patch version is present in this tilde version specifier. + pub fn has_patch(&self) -> bool { + self.inner.version.release().len() == 3 + } + + /// Construct the lower and upper bounding version specifiers for this tilde version specifier, + /// e.g., for `~=3.13` this would return `>=3.13` and `<4` and for `~=3.13.0` it would + /// return `>=3.13.0` and `<3.14`. + pub fn bounding_specifiers(&self) -> (VersionSpecifier, VersionSpecifier) { + let release = self.inner.version().release(); + let lower = self.inner.version.clone(); + let upper = if self.has_patch() { + Version::new([release[0], release[1] + 1]) + } else { + Version::new([release[0] + 1]) + }; + ( + VersionSpecifier::greater_than_equal_version(lower), + VersionSpecifier::less_than_version(upper), + ) + } + + /// Construct a new tilde `VersionSpecifier` with the given patch version appended. + pub fn with_patch_version(&self, patch: u64) -> TildeVersionSpecifier { + let mut release = self.inner.version.release().to_vec(); + if self.has_patch() { + release.pop(); + } + release.push(patch); + TildeVersionSpecifier::from_specifier( + VersionSpecifier::from_version(Operator::TildeEqual, Version::new(release)) + .expect("We should always derive a valid new version specifier"), + ) + .expect("We should always derive a new tilde version specifier") + } +} + +impl std::fmt::Display for TildeVersionSpecifier<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.inner) + } +} + #[cfg(test)] mod tests { use std::{cmp::Ordering, str::FromStr}; diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index 5ed6293e2..a768650d7 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -25,7 +25,7 @@ use uv_fs::{CWD, LockedFile, Simplified}; use uv_git::ResolvedRepositoryReference; use uv_installer::{SatisfiesResult, SitePackages}; use uv_normalize::{DEV_DEPENDENCIES, DefaultGroups, ExtraName, GroupName, PackageName}; -use uv_pep440::{Version, VersionSpecifiers}; +use uv_pep440::{TildeVersionSpecifier, Version, VersionSpecifiers}; use uv_pep508::MarkerTreeContents; use uv_pypi_types::{ConflictPackage, ConflictSet, Conflicts}; use uv_python::{ @@ -421,6 +421,30 @@ pub(crate) fn find_requires_python( if requires_python.is_empty() { return Ok(None); } + for ((package, group), specifiers) in &requires_python { + if let [spec] = &specifiers[..] { + if let Some(spec) = TildeVersionSpecifier::from_specifier_ref(spec) { + if spec.has_patch() { + continue; + } + let (lower, upper) = spec.bounding_specifiers(); + let spec_0 = spec.with_patch_version(0); + let (lower_0, upper_0) = spec_0.bounding_specifiers(); + warn_user_once!( + "The `requires-python` specifier (`{spec}`) in `{package}{group}` \ + uses the tilde specifier (`~=`) without a patch version. This will be \ + interpreted as `{lower}, {upper}`. Did you mean `{spec_0}` to constrain the \ + version as `{lower_0}, {upper_0}`? We recommend only using \ + the tilde specifier with a patch version to avoid ambiguity.", + group = if let Some(group) = group { + format!(":{group}") + } else { + String::new() + }, + ); + } + } + } match RequiresPython::intersection(requires_python.iter().map(|(.., specifiers)| specifiers)) { Some(requires_python) => Ok(Some(requires_python)), None => Err(ProjectError::DisjointRequiresPython(requires_python)), diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index 1c375b2e3..82754381b 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -4551,15 +4551,15 @@ fn lock_requires_python_compatible_specifier() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.lock(), @r###" + uv_snapshot!(context.filters(), context.lock(), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - warning: The release specifier (`~=3.13`) contains a compatible release match without a patch version. This will be interpreted as `>=3.13, <4`. Did you mean `~=3.13.0` to freeze the minor version? + warning: The `requires-python` specifier (`~=3.13`) in `warehouse` uses the tilde specifier (`~=`) without a patch version. This will be interpreted as `>=3.13, <4`. Did you mean `~=3.13.0` to constrain the version as `>=3.13.0, <3.14`? We recommend only using the tilde specifier with a patch version to avoid ambiguity. Resolved 1 package in [TIME] - "###); + "); pyproject_toml.write_str( r#" From a9ea756d141d935ab321a1aabdb78720d536073a Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Wed, 2 Jul 2025 11:11:51 -0400 Subject: [PATCH 132/349] Ignore Python patch version for `--universal` pip compile (#14405) ## Summary The idea here is that if a user runs `uv pip compile --universal`, we should ignore the patch version on the current interpreter. I think this makes sense... `--universal` tries to resolve for all future versions, so it seems a bit odd that we'd start at the _current_ patch version. Closes https://github.com/astral-sh/uv/issues/14397. --- crates/uv/src/commands/pip/compile.rs | 13 +++-- crates/uv/tests/it/pip_compile.rs | 76 +++++++++++++++++++++------ 2 files changed, 66 insertions(+), 23 deletions(-) diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs index 3aa3fb0e9..a1846d418 100644 --- a/crates/uv/src/commands/pip/compile.rs +++ b/crates/uv/src/commands/pip/compile.rs @@ -337,13 +337,12 @@ pub(crate) async fn pip_compile( // Determine the Python requirement, if the user requested a specific version. let python_requirement = if universal { - let requires_python = RequiresPython::greater_than_equal_version( - if let Some(python_version) = python_version.as_ref() { - &python_version.version - } else { - interpreter.python_version() - }, - ); + let requires_python = if let Some(python_version) = python_version.as_ref() { + RequiresPython::greater_than_equal_version(&python_version.version) + } else { + let version = interpreter.python_minor_version(); + RequiresPython::greater_than_equal_version(&version) + }; PythonRequirement::from_requires_python(&interpreter, requires_python) } else if let Some(python_version) = python_version.as_ref() { PythonRequirement::from_python_version(&interpreter, python_version) diff --git a/crates/uv/tests/it/pip_compile.rs b/crates/uv/tests/it/pip_compile.rs index c80027761..ff135d959 100644 --- a/crates/uv/tests/it/pip_compile.rs +++ b/crates/uv/tests/it/pip_compile.rs @@ -16345,7 +16345,7 @@ fn pep_751_compile_registry_wheel() -> Result<()> { # uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml lock-version = "1.0" created-by = "uv" - requires-python = ">=3.12.[X]" + requires-python = ">=3.12" [[packages]] name = "iniconfig" @@ -16394,7 +16394,7 @@ fn pep_751_compile_registry_sdist() -> Result<()> { # uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml lock-version = "1.0" created-by = "uv" - requires-python = ">=3.12.[X]" + requires-python = ">=3.12" [[packages]] name = "source-distribution" @@ -16478,7 +16478,7 @@ fn pep_751_compile_directory() -> Result<()> { # uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml lock-version = "1.0" created-by = "uv" - requires-python = ">=3.12.[X]" + requires-python = ">=3.12" [[packages]] name = "anyio" @@ -16549,7 +16549,7 @@ fn pep_751_compile_git() -> Result<()> { # uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml lock-version = "1.0" created-by = "uv" - requires-python = ">=3.12.[X]" + requires-python = ">=3.12" [[packages]] name = "uv-public-pypackage" @@ -16599,7 +16599,7 @@ fn pep_751_compile_url_wheel() -> Result<()> { # uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml lock-version = "1.0" created-by = "uv" - requires-python = ">=3.12.[X]" + requires-python = ">=3.12" [[packages]] name = "anyio" @@ -16663,7 +16663,7 @@ fn pep_751_compile_url_sdist() -> Result<()> { # uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml lock-version = "1.0" created-by = "uv" - requires-python = ">=3.12.[X]" + requires-python = ">=3.12" [[packages]] name = "anyio" @@ -16732,7 +16732,7 @@ fn pep_751_compile_path_wheel() -> Result<()> { # uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml lock-version = "1.0" created-by = "uv" - requires-python = ">=3.12.[X]" + requires-python = ">=3.12" [[packages]] name = "iniconfig" @@ -16770,7 +16770,7 @@ fn pep_751_compile_path_wheel() -> Result<()> { # uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o nested/pylock.toml lock-version = "1.0" created-by = "uv" - requires-python = ">=3.12.[X]" + requires-python = ">=3.12" [[packages]] name = "iniconfig" @@ -16811,7 +16811,7 @@ fn pep_751_compile_path_sdist() -> Result<()> { # uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml lock-version = "1.0" created-by = "uv" - requires-python = ">=3.12.[X]" + requires-python = ">=3.12" [[packages]] name = "iniconfig" @@ -16850,7 +16850,7 @@ fn pep_751_compile_path_sdist() -> Result<()> { # uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o nested/pylock.toml lock-version = "1.0" created-by = "uv" - requires-python = ">=3.12.[X]" + requires-python = ">=3.12" [[packages]] name = "iniconfig" @@ -16887,7 +16887,7 @@ fn pep_751_compile_preferences() -> Result<()> { # uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml lock-version = "1.0" created-by = "uv" - requires-python = ">=3.12.[X]" + requires-python = ">=3.12" [[packages]] name = "anyio" @@ -16928,7 +16928,7 @@ fn pep_751_compile_preferences() -> Result<()> { # uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml lock-version = "1.0" created-by = "uv" - requires-python = ">=3.12.[X]" + requires-python = ">=3.12" [[packages]] name = "anyio" @@ -16968,7 +16968,7 @@ fn pep_751_compile_preferences() -> Result<()> { # uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml lock-version = "1.0" created-by = "uv" - requires-python = ">=3.12.[X]" + requires-python = ">=3.12" [[packages]] name = "anyio" @@ -17007,7 +17007,7 @@ fn pep_751_compile_preferences() -> Result<()> { # uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml lock-version = "1.0" created-by = "uv" - requires-python = ">=3.12.[X]" + requires-python = ">=3.12" [[packages]] name = "anyio" @@ -17055,7 +17055,7 @@ fn pep_751_compile_warn() -> Result<()> { # uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml --emit-index-url lock-version = "1.0" created-by = "uv" - requires-python = ">=3.12.[X]" + requires-python = ">=3.12" [[packages]] name = "iniconfig" @@ -17268,7 +17268,7 @@ fn pep_751_compile_no_emit_package() -> Result<()> { # uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml --no-emit-package idna lock-version = "1.0" created-by = "uv" - requires-python = ">=3.12.[X]" + requires-python = ">=3.12" [[packages]] name = "anyio" @@ -17562,3 +17562,47 @@ fn git_path_transitive_dependency() -> Result<()> { Ok(()) } + +/// Ensure that `--emit-index-annotation` plays nicely with `--annotation-style=line`. +#[test] +fn omit_python_patch_universal() -> Result<()> { + let context = TestContext::new("3.11"); + + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("redis")?; + + uv_snapshot!(context.filters(), context.pip_compile() + .arg("requirements.in"), @r" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.in + redis==5.0.3 + # via -r requirements.in + + ----- stderr ----- + Resolved 1 package in [TIME] + " + ); + + uv_snapshot!(context.filters(), context.pip_compile() + .arg("requirements.in") + .arg("--universal"), @r" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.in --universal + async-timeout==4.0.3 ; python_full_version < '3.11.[X]' + # via redis + redis==5.0.3 + # via -r requirements.in + + ----- stderr ----- + Resolved 2 packages in [TIME] + " + ); + + Ok(()) +} From 2f53ea5c5c3f9939d87ab1aa75c5208a953532c1 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 2 Jul 2025 12:25:19 -0500 Subject: [PATCH 133/349] Add a migration guide from pip to uv projects (#12382) [Rendered](https://github.com/astral-sh/uv/blob/zb/pip-wip/docs/guides/migration/pip-to-project.md) --------- Co-authored-by: samypr100 <3933065+samypr100@users.noreply.github.com> Co-authored-by: Mathieu Kniewallner Co-authored-by: Aria Desires --- docs/guides/migration/index.md | 14 + docs/guides/migration/pip-to-project.md | 472 ++++++++++++++++++++++++ mkdocs.template.yml | 3 + 3 files changed, 489 insertions(+) create mode 100644 docs/guides/migration/index.md create mode 100644 docs/guides/migration/pip-to-project.md diff --git a/docs/guides/migration/index.md b/docs/guides/migration/index.md new file mode 100644 index 000000000..aa5f0f44f --- /dev/null +++ b/docs/guides/migration/index.md @@ -0,0 +1,14 @@ +# Migration guides + +Learn how to migrate from other tools to uv: + +- [Migrate from pip to uv projects](./pip-to-project.md) + +!!! note + + Other guides, such as migrating from another project management tool, or from pip to `uv pip` + are not yet available. See [#5200](https://github.com/astral-sh/uv/issues/5200) to track + progress. + +Or, explore the [integration guides](../integration/index.md) to learn how to use uv with other +software. diff --git a/docs/guides/migration/pip-to-project.md b/docs/guides/migration/pip-to-project.md new file mode 100644 index 000000000..a2be9ccd3 --- /dev/null +++ b/docs/guides/migration/pip-to-project.md @@ -0,0 +1,472 @@ +# Migrating from pip to a uv project + +This guide will discuss converting from a `pip` and `pip-tools` workflow centered on `requirements` +files to uv's project workflow using a `pyproject.toml` and `uv.lock` file. + +!!! note + + If you're looking to migrate from `pip` and `pip-tools` to uv's drop-in interface or from an + existing workflow where you're already using a `pyproject.toml`, those guides are not yet + written. See [#5200](https://github.com/astral-sh/uv/issues/5200) to track progress. + +We'll start with an overview of developing with `pip`, then discuss migrating to uv. + +!!! tip + + If you're familiar with the ecosystem, you can jump ahead to the + [requirements file import](#importing-requirements-files) instructions. + +## Understanding pip workflows + +### Project dependencies + +When you want to use a package in your project, you need to install it first. `pip` supports +imperative installation of packages, e.g.: + +```console +$ pip install fastapi +``` + +This installs the package into the environment that `pip` is installed in. This may be a virtual +environment, or, the global environment of your system's Python installation. + +Then, you can run a Python script that requires the package: + +```python title="example.py" +import fastapi +``` + +It's best practice to create a virtual environment for each project, to avoid mixing packages +between them. For example: + +```console +$ python -m venv +$ source .venv/bin/activate +$ pip ... +``` + +We will revisit this topic in the [project environments section](#project-environments) below. + +### Requirements files + +When sharing projects with others, it's useful to declare all the packages you require upfront. +`pip` supports installing requirements from a file, e.g.: + +```python title="requirements.txt" +fastapi +``` + +```console +$ pip install -r requirements.txt +``` + +Notice above that `fastapi` is not "locked" to a specific version — each person working on the +project may have a different version of `fastapi` installed. `pip-tools` was created to improve this +experience. + +When using `pip-tools`, requirements files specify both the dependencies for your project and lock +dependencies to a specific version — the file extension is used to differentiate between the two. +For example, if you require `fastapi` and `pydantic`, you'd specify these in a `requirements.in` +file: + +```python title="requirements.in" +fastapi +pydantic>2 +``` + +Notice there's a version constraint on `pydantic` — this means only `pydantic` versions later than +`2.0.0` can be used. In contrast, `fastapi` does not have a version constraint — any version can be +used. + +These dependencies can be compiled into a `requirements.txt` file: + +```console +$ pip-compile requirements.in -o requirements.txt +``` + +```python title="requirements.txt" +annotated-types==0.7.0 + # via pydantic +anyio==4.8.0 + # via starlette +fastapi==0.115.11 + # via -r requirements.in +idna==3.10 + # via anyio +pydantic==2.10.6 + # via + # -r requirements.in + # fastapi +pydantic-core==2.27.2 + # via pydantic +sniffio==1.3.1 + # via anyio +starlette==0.46.1 + # via fastapi +typing-extensions==4.12.2 + # via + # fastapi + # pydantic + # pydantic-core +``` + +Here, all the versions constraints are _exact_. Only a single version of each package can be used. +The above example was generated with `uv pip compile`, but could also be generated with +`pip-compile` from `pip-tools`. + +Though less common, the `requirements.txt` can also be generated using `pip freeze`, by first +installing the input dependencies into the environment then exporting the installed versions: + +```console +$ pip install -r requirements.in +$ pip freeze > requirements.txt +``` + +```python title="requirements.txt" +annotated-types==0.7.0 +anyio==4.8.0 +fastapi==0.115.11 +idna==3.10 +pydantic==2.10.6 +pydantic-core==2.27.2 +sniffio==1.3.1 +starlette==0.46.1 +typing-extensions==4.12.2 +``` + +After compiling dependencies into a locked set of versions, these files are committed to version +control and distributed with the project. + +Then, when someone wants to use the project, they install from the requirements file: + +```console +$ pip install -r requirements.txt +``` + + + +### Development dependencies + +The requirements file format can only describe a single set of dependencies at once. This means if +you have additional _groups_ of dependencies, such as development dependencies, they need separate +files. For example, we'll create a `-dev` dependency file: + +```python title="requirements-dev.in" +-r requirements.in +-c requirements.txt + +pytest +``` + +Notice the base requirements are included with `-r requirements.in`. This ensures your development +environment considers _all_ of the dependencies together. The `-c requirements.txt` _constrains_ the +package version to ensure that the `requirements-dev.txt` uses the same versions as +`requirements.txt`. + +!!! note + + It's common to use `-r requirements.txt` directly instead of using both + `-r requirements.in`, and `-c requirements.txt`. There's no difference in the resulting package + versions, but using both files produces annotations which allow you to determine which + dependencies are _direct_ (annotated with `-r requirements.in`) and which are _indirect_ (only + annotated with `-c requirements.txt`). + +The compiled development dependencies look like: + +```python title="requirements-dev.txt" +annotated-types==0.7.0 + # via + # -c requirements.txt + # pydantic +anyio==4.8.0 + # via + # -c requirements.txt + # starlette +fastapi==0.115.11 + # via + # -c requirements.txt + # -r requirements.in +idna==3.10 + # via + # -c requirements.txt + # anyio +iniconfig==2.0.0 + # via pytest +packaging==24.2 + # via pytest +pluggy==1.5.0 + # via pytest +pydantic==2.10.6 + # via + # -c requirements.txt + # -r requirements.in + # fastapi +pydantic-core==2.27.2 + # via + # -c requirements.txt + # pydantic +pytest==8.3.5 + # via -r requirements-dev.in +sniffio==1.3.1 + # via + # -c requirements.txt + # anyio +starlette==0.46.1 + # via + # -c requirements.txt + # fastapi +typing-extensions==4.12.2 + # via + # -c requirements.txt + # fastapi + # pydantic + # pydantic-core +``` + +As with the base dependency files, these are committed to version control and distributed with the +project. When someone wants to work on the project, they'll install from the requirements file: + +```console +$ pip install -r requirements-dev.txt +``` + +### Platform-specific dependencies + +When compiling dependencies with `pip` or `pip-tools`, the result is only usable on the same +platform as it is generated on. This poses a problem for projects which need to be usable on +multiple platforms, such as Windows and macOS. + +For example, take a simple dependency: + +```python title="requirements.in" +tqdm +``` + +On Linux, this compiles to: + +```python title="requirements-linux.txt" +tqdm==4.67.1 + # via -r requirements.in +``` + +While on Windows, this compiles to: + +```python title="requirements-win.txt" +colorama==0.4.6 + # via tqdm +tqdm==4.67.1 + # via -r requirements.in +``` + +`colorama` is a Windows-only dependency of `tqdm`. + +When using `pip` and `pip-tools`, a project needs to declare a requirements lock file for each +supported platform. + +!!! note + + uv's resolver can compile dependencies for multiple platforms at once (see ["universal resolution"](../../concepts/resolution.md#universal-resolution)), + allowing you to use a single `requirements.txt` for all platforms: + + ```console + $ uv pip compile --universal requirements.in + ``` + + ```python title="requirements.txt" + colorama==0.4.6 ; sys_platform == 'win32' + # via tqdm + tqdm==4.67.1 + # via -r requirements.in + ``` + + This resolution mode is also used when using a `pyproject.toml` and `uv.lock`. + +## Migrating to a uv project + +### The `pyproject.toml` + +The `pyproject.toml` is a standardized file for Python project metadata. It replaces +`requirements.in` files, allowing you to represent arbitrary groups of project dependencies. It also +provides a centralized location for metadata about your project, such as the build system or tool +settings. + + + +For example, the `requirements.in` and `requirements-dev.in` files above can be translated to a +`pyproject.toml` as follows: + +```toml title="pyproject.toml" +[project] +name = "example" +version = "0.0.1" +dependencies = [ + "fastapi", + "pydantic>2" +] + +[dependency-groups] +dev = ["pytest"] +``` + +We'll discuss the commands necessary to automate these imports below. + +### The uv lockfile + +uv uses a lockfile (`uv.lock`) file to lock package versions. The format of this file is specific to +uv, allowing uv to support advanced features. It replaces `requirements.txt` files. + +The lockfile will be automatically created and populated when adding dependencies, but you can +explicitly create it with `uv lock`. + +Unlike `requirements.txt` files, the `uv.lock` file can represent arbitrary groups of dependencies, +so multiple files are not needed to lock development dependencies. + +The uv lockfile is always [universal](../../concepts/resolution.md#universal-resolution), so +multiple files are not needed to +[lock dependencies for each platform](#platform-specific-dependencies). This ensures that all +developers + +The uv lockfile also supports concepts like +[pinning packages to specific indexes](../../concepts/indexes.md#pinning-a-package-to-an-index), +which is not representable in `requirements.txt` files. + +!!! tip + + If you only need to lock for a subset of platforms, use the + [`tool.uv.environments`](../../concepts/resolution.md#limited-resolution-environments) setting + to limit the resolution and lockfile. + +To learn more, see the [lockfile](../../concepts/projects/layout.md#the-lockfile) documentation. + +### Importing requirements files + +First, create a `pyproject.toml` if you have not already: + +```console +$ uv init +``` + +Then, the easiest way to import requirements is with `uv add`: + +```console +$ uv add -r requirements.in +``` + +However, there is some nuance to this transition. Notice we used the `requirements.in` file, which +does not pin to exact versions of packages so uv will solve for new versions of these packages. You +may want to continue using your previously locked versions from your `requirements.txt` so, when +switching over to uv, none of your dependency versions change. + +The solution is to add your locked versions as _constraints_. uv supports using these on `add` to +preserve locked versions: + +```console +$ uv add -r requirements.in -c requirements.txt +``` + +Your existing versions will be retained when producing a `uv.lock` file. + +#### Importing platform-specific constraints + +If your platform-specific dependencies have been compiled into separate files, you can still +transition to a universal lockfile. However, you cannot just use `-c` to specify constraints from +your existing platform-specific `requirements.txt` files because they do not include markers +describing the environment and will consequently conflict. + +To add the necessary markers, use `uv pip compile` to convert your existing files. For example, +given the following: + +```python title="requirements-win.txt" +colorama==0.4.6 + # via tqdm +tqdm==4.67.1 + # via -r requirements.in +``` + +The markers can be added with: + +```console +$ uv pip compile requirements.in -o requirements-win.txt --python-platform windows --no-strip-markers +``` + +Notice the resulting output includes a Windows marker on `colorama`: + +```python title="requirements-win.txt" +colorama==0.4.6 ; sys_platform == 'win32' + # via tqdm +tqdm==4.67.1 + # via -r requirements.in +``` + +When using `-o`, uv will constrain the versions to match the existing output file, if it can. + +Markers can be added for other platforms by changing the `--python-platform` and `-o` values for +each requirements file you need to import, e.g., to `linux` and `macos`. + +Once each `requirements.txt` file has been transformed, the dependencies can be imported to the +`pyproject.toml` and `uv.lock` with `uv add`: + +```console +$ uv add -r requirements.in -c requirements-win.txt -c requirements-linux.txt +``` + +#### Importing development dependency files + +As discussed in the [development dependencies](#development-dependencies) section, it's common to +have groups of dependencies for development purposes. + +To import development dependencies, use the `--dev` flag during `uv add`: + +```console +$ uv add --dev -r requirements-dev.in -c requirements-dev.txt +``` + +If the `requirements-dev.in` includes the parent `requirements.in` via `-r`, it will need to be +stripped to avoid adding the base requirements to the `dev` dependency group. The following example +uses `sed` to strip lines that start with `-r`, then pipes the result to `uv add`: + +```console +$ sed '/^-r /d' requirements-dev.in | uv add --dev -r - -c requirements-dev.txt +``` + +In addition to the `dev` dependency group, uv supports arbitrary group names. For example, if you +also have a dedicated set of dependencies for building your documentation, those can be imported to +a `docs` group: + +```console +$ uv add -r requirements-docs.in -c requirements-docs.txt --group docs +``` + +### Project environments + +Unlike `pip`, uv is not centered around the concept of an "active" virtual environment. Instead, uv +uses a dedicated virtual environment for each project in a `.venv` directory. This environment is +automatically managed, so when you run a command, like `uv add`, the environment is synced with the +project dependencies. + +The preferred way to execute commands in the environment is with `uv run`, e.g.: + +```console +$ uv run pytest +``` + +Prior to every `uv run` invocation, uv will verify that the lockfile is up-to-date with the +`pyproject.toml`, and that the environment is up-to-date with the lockfile, keeping your project +in-sync without the need for manual intervention. `uv run` guarantees that your command is run in a +consistent, locked environment. + +The project environment can also be explicitly created with `uv sync`, e.g., for use with editors. + +!!! note + + When in projects, uv will prefer a `.venv` in the project directory and ignore the active + environment as declared by the `VIRTUAL_ENV` variable by default. You can opt-in to using the + active environment with the `--active` flag. + +To learn more, see the +[project environment](../../concepts/projects/layout.md#the-project-environment) documentation. + +## Next steps + +Now that you've migrated to uv, take a look at the +[project concept](../../concepts/projects/index.md) page for more details about uv projects. diff --git a/mkdocs.template.yml b/mkdocs.template.yml index 0b2ee6623..69a299b5b 100644 --- a/mkdocs.template.yml +++ b/mkdocs.template.yml @@ -174,6 +174,9 @@ nav: - Using tools: guides/tools.md - Working on projects: guides/projects.md - Publishing packages: guides/package.md + - Migration: + - guides/migration/index.md + - From pip to a uv project: guides/migration/pip-to-project.md - Integrations: - guides/integration/index.md - Docker: guides/integration/docker.md From 743260b1f53e4ba11cc904ced2c717f509a7b0e2 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Wed, 2 Jul 2025 14:03:43 -0400 Subject: [PATCH 134/349] Make project and interpreter lock acquisition non-fatal (#14404) ## Summary If we fail to acquire a lock on an environment, uv shouldn't fail; we should just warn. In some cases, users run uv with read-only permissions for their projects, etc. For now, I kept any locks acquired _in the cache_ as hard failures, since we always need write-access to the cache. Closes https://github.com/astral-sh/uv/issues/14411. --- crates/uv-build-frontend/src/lib.rs | 10 +++-- crates/uv/src/commands/pip/install.rs | 10 ++++- crates/uv/src/commands/pip/sync.rs | 10 ++++- crates/uv/src/commands/pip/uninstall.rs | 10 ++++- crates/uv/src/commands/project/add.rs | 10 ++++- crates/uv/src/commands/project/mod.rs | 15 ++++++- crates/uv/src/commands/project/remove.rs | 10 ++++- crates/uv/src/commands/project/run.rs | 24 ++++++++-- crates/uv/src/commands/project/sync.rs | 9 +++- crates/uv/tests/it/sync.rs | 56 +++++++++++++++++++++++- 10 files changed, 143 insertions(+), 21 deletions(-) diff --git a/crates/uv-build-frontend/src/lib.rs b/crates/uv-build-frontend/src/lib.rs index 06c07425c..5cbaece2e 100644 --- a/crates/uv-build-frontend/src/lib.rs +++ b/crates/uv-build-frontend/src/lib.rs @@ -25,7 +25,7 @@ use tempfile::TempDir; use tokio::io::AsyncBufReadExt; use tokio::process::Command; use tokio::sync::{Mutex, Semaphore}; -use tracing::{Instrument, debug, info_span, instrument}; +use tracing::{Instrument, debug, info_span, instrument, warn}; use uv_cache_key::cache_digest; use uv_configuration::PreviewMode; @@ -456,8 +456,12 @@ impl SourceBuild { "uv-setuptools-{}.lock", cache_digest(&canonical_source_path) )); - source_tree_lock = - Some(LockedFile::acquire(lock_path, self.source_tree.to_string_lossy()).await?); + source_tree_lock = LockedFile::acquire(lock_path, self.source_tree.to_string_lossy()) + .await + .inspect_err(|err| { + warn!("Failed to acquire build lock: {err}"); + }) + .ok(); } Ok(source_tree_lock) } diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs index b7d32dd94..aa6e6a6c9 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -5,7 +5,7 @@ use std::path::PathBuf; use anyhow::Context; use itertools::Itertools; use owo_colors::OwoColorize; -use tracing::{Level, debug, enabled}; +use tracing::{Level, debug, enabled, warn}; use uv_cache::Cache; use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder}; @@ -236,7 +236,13 @@ pub(crate) async fn pip_install( } } - let _lock = environment.lock().await?; + let _lock = environment + .lock() + .await + .inspect_err(|err| { + warn!("Failed to acquire environment lock: {err}"); + }) + .ok(); // Determine the markers to use for the resolution. let interpreter = environment.interpreter(); diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index ab4c42ce5..8f26aaea2 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -3,7 +3,7 @@ use std::fmt::Write; use anyhow::{Context, Result}; use owo_colors::OwoColorize; -use tracing::debug; +use tracing::{debug, warn}; use uv_cache::Cache; use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder}; @@ -211,7 +211,13 @@ pub(crate) async fn pip_sync( } } - let _lock = environment.lock().await?; + let _lock = environment + .lock() + .await + .inspect_err(|err| { + warn!("Failed to acquire environment lock: {err}"); + }) + .ok(); let interpreter = environment.interpreter(); diff --git a/crates/uv/src/commands/pip/uninstall.rs b/crates/uv/src/commands/pip/uninstall.rs index 4424fee37..835e7de65 100644 --- a/crates/uv/src/commands/pip/uninstall.rs +++ b/crates/uv/src/commands/pip/uninstall.rs @@ -3,7 +3,7 @@ use std::fmt::Write; use anyhow::Result; use itertools::{Either, Itertools}; use owo_colors::OwoColorize; -use tracing::debug; +use tracing::{debug, warn}; use uv_cache::Cache; use uv_client::BaseClientBuilder; @@ -100,7 +100,13 @@ pub(crate) async fn pip_uninstall( } } - let _lock = environment.lock().await?; + let _lock = environment + .lock() + .await + .inspect_err(|err| { + warn!("Failed to acquire environment lock: {err}"); + }) + .ok(); // Index the current `site-packages` directory. let site_packages = uv_installer::SitePackages::from_environment(&environment)?; diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index 719821df5..04fd7d822 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -10,7 +10,7 @@ use anyhow::{Context, Result, bail}; use itertools::Itertools; use owo_colors::OwoColorize; use rustc_hash::{FxBuildHasher, FxHashMap}; -use tracing::debug; +use tracing::{debug, warn}; use url::Url; use uv_cache::Cache; @@ -319,7 +319,13 @@ pub(crate) async fn add( } }; - let _lock = target.acquire_lock().await?; + let _lock = target + .acquire_lock() + .await + .inspect_err(|err| { + warn!("Failed to acquire environment lock: {err}"); + }) + .ok(); let client_builder = BaseClientBuilder::new() .connectivity(network_settings.connectivity) diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index a768650d7..c327e8a44 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -1244,7 +1244,12 @@ impl ProjectEnvironment { preview: PreviewMode, ) -> Result { // Lock the project environment to avoid synchronization issues. - let _lock = ProjectInterpreter::lock(workspace).await?; + let _lock = ProjectInterpreter::lock(workspace) + .await + .inspect_err(|err| { + warn!("Failed to acquire project environment lock: {err}"); + }) + .ok(); let upgradeable = preview.is_enabled() && python @@ -1462,7 +1467,13 @@ impl ScriptEnvironment { preview: PreviewMode, ) -> Result { // Lock the script environment to avoid synchronization issues. - let _lock = ScriptInterpreter::lock(script).await?; + let _lock = ScriptInterpreter::lock(script) + .await + .inspect_err(|err| { + warn!("Failed to acquire script environment lock: {err}"); + }) + .ok(); + let upgradeable = python_request .as_ref() .is_none_or(|request| !request.includes_patch()); diff --git a/crates/uv/src/commands/project/remove.rs b/crates/uv/src/commands/project/remove.rs index 29b5f0bc0..6bc04160e 100644 --- a/crates/uv/src/commands/project/remove.rs +++ b/crates/uv/src/commands/project/remove.rs @@ -5,7 +5,7 @@ use std::str::FromStr; use anyhow::{Context, Result}; use owo_colors::OwoColorize; -use tracing::debug; +use tracing::{debug, warn}; use uv_cache::Cache; use uv_configuration::{ @@ -281,7 +281,13 @@ pub(crate) async fn remove( } }; - let _lock = target.acquire_lock().await?; + let _lock = target + .acquire_lock() + .await + .inspect_err(|err| { + warn!("Failed to acquire environment lock: {err}"); + }) + .ok(); // Determine the lock mode. let mode = if locked { diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index 6ece28eaf..a6ea4c0e0 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -240,7 +240,13 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl .await? .into_environment()?; - let _lock = environment.lock().await?; + let _lock = environment + .lock() + .await + .inspect_err(|err| { + warn!("Failed to acquire environment lock: {err}"); + }) + .ok(); // Determine the lock mode. let mode = if frozen { @@ -386,7 +392,13 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl ) }); - let _lock = environment.lock().await?; + let _lock = environment + .lock() + .await + .inspect_err(|err| { + warn!("Failed to acquire environment lock: {err}"); + }) + .ok(); match update_environment( environment, @@ -699,7 +711,13 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl .map(|lock| (lock, project.workspace().install_path().to_owned())); } } else { - let _lock = venv.lock().await?; + let _lock = venv + .lock() + .await + .inspect_err(|err| { + warn!("Failed to acquire environment lock: {err}"); + }) + .ok(); // Determine the lock mode. let mode = if frozen { diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index dc9f0dcbb..6e057446e 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -6,6 +6,7 @@ use std::sync::Arc; use anyhow::{Context, Result}; use itertools::Itertools; use owo_colors::OwoColorize; +use tracing::warn; use uv_cache::Cache; use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder}; @@ -169,7 +170,13 @@ pub(crate) async fn sync( ), }; - let _lock = environment.lock().await?; + let _lock = environment + .lock() + .await + .inspect_err(|err| { + warn!("Failed to acquire environment lock: {err}"); + }) + .ok(); // Notify the user of any environment changes. match &environment { diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index da59682ab..a97775d9f 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -3,13 +3,14 @@ use assert_cmd::prelude::*; use assert_fs::{fixture::ChildPath, prelude::*}; use indoc::{formatdoc, indoc}; use insta::assert_snapshot; - -use crate::common::{TestContext, download_to_disk, packse_index_url, uv_snapshot, venv_bin_path}; use predicates::prelude::predicate; use tempfile::tempdir_in; + use uv_fs::Simplified; use uv_static::EnvVars; +use crate::common::{TestContext, download_to_disk, packse_index_url, uv_snapshot, venv_bin_path}; + #[test] fn sync() -> Result<()> { let context = TestContext::new("3.12"); @@ -9989,3 +9990,54 @@ fn sync_url_with_query_parameters() -> Result<()> { Ok(()) } + +#[test] +#[cfg(unix)] +fn read_only() -> Result<()> { + use std::os::unix::fs::PermissionsExt; + + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["iniconfig"] + "#, + )?; + + uv_snapshot!(context.filters(), context.sync(), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + iniconfig==2.0.0 + "###); + + assert!(context.temp_dir.child("uv.lock").exists()); + + // Remove the flock. + fs_err::remove_file(context.venv.child(".lock"))?; + + // Make the virtual environment read and execute (but not write). + fs_err::set_permissions(&context.venv, std::fs::Permissions::from_mode(0o555))?; + + uv_snapshot!(context.filters(), context.sync(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + Audited 1 package in [TIME] + "); + + Ok(()) +} From a6bb65c78deaf6fef7b901d4d24d350368db2723 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 2 Jul 2025 13:11:17 -0500 Subject: [PATCH 135/349] Clarify behavior and hint on tool install when no executables are available (#14423) Closes https://github.com/astral-sh/uv/issues/14416 --- crates/uv/src/commands/tool/common.rs | 12 ++++++++---- crates/uv/tests/it/tool_install.rs | 22 +++++++++++----------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/crates/uv/src/commands/tool/common.rs b/crates/uv/src/commands/tool/common.rs index 166b4fc6f..ffc1b5645 100644 --- a/crates/uv/src/commands/tool/common.rs +++ b/crates/uv/src/commands/tool/common.rs @@ -218,7 +218,7 @@ pub(crate) fn finalize_tool_install( if target_entry_points.is_empty() { writeln!( printer.stdout(), - "No executables are provided by `{from}`", + "No executables are provided by package `{from}`; removing tool", from = name.cyan() )?; @@ -354,7 +354,9 @@ fn hint_executable_from_dependency( let command = format!("uv tool install {}", package.name()); writeln!( printer.stdout(), - "However, an executable with the name `{}` is available via dependency `{}`.\nDid you mean `{}`?", + "{}{} An executable with the name `{}` is available via dependency `{}`.\n Did you mean `{}`?", + "hint".bold().cyan(), + ":".bold(), name.cyan(), package.name().cyan(), command.bold(), @@ -363,7 +365,9 @@ fn hint_executable_from_dependency( packages => { writeln!( printer.stdout(), - "However, an executable with the name `{}` is available via the following dependencies::", + "{}{} An executable with the name `{}` is available via the following dependencies::", + "hint".bold().cyan(), + ":".bold(), name.cyan(), )?; @@ -372,7 +376,7 @@ fn hint_executable_from_dependency( } writeln!( printer.stdout(), - "Did you mean to install one of them instead?" + " Did you mean to install one of them instead?" )?; } } diff --git a/crates/uv/tests/it/tool_install.rs b/crates/uv/tests/it/tool_install.rs index 0da627552..deb935f9b 100644 --- a/crates/uv/tests/it/tool_install.rs +++ b/crates/uv/tests/it/tool_install.rs @@ -448,13 +448,13 @@ fn tool_install_suggest_other_packages_with_executable() { uv_snapshot!(filters, context.tool_install() .arg("fastapi==0.111.0") .env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str()) - .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r###" + .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r" success: false exit_code: 1 ----- stdout ----- - No executables are provided by `fastapi` - However, an executable with the name `fastapi` is available via dependency `fastapi-cli`. - Did you mean `uv tool install fastapi-cli`? + No executables are provided by package `fastapi`; removing tool + hint: An executable with the name `fastapi` is available via dependency `fastapi-cli`. + Did you mean `uv tool install fastapi-cli`? ----- stderr ----- Resolved 35 packages in [TIME] @@ -494,7 +494,7 @@ fn tool_install_suggest_other_packages_with_executable() { + uvicorn==0.29.0 + watchfiles==0.21.0 + websockets==12.0 - "###); + "); } /// Test installing a tool at a version @@ -821,11 +821,11 @@ fn tool_install_remove_on_empty() -> Result<()> { .arg(black.path()) .env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str()) .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()) - .env(EnvVars::PATH, bin_dir.as_os_str()), @r###" + .env(EnvVars::PATH, bin_dir.as_os_str()), @r" success: false exit_code: 1 ----- stdout ----- - No executables are provided by `black` + No executables are provided by package `black`; removing tool ----- stderr ----- Resolved 1 package in [TIME] @@ -839,7 +839,7 @@ fn tool_install_remove_on_empty() -> Result<()> { - packaging==24.0 - pathspec==0.12.1 - platformdirs==4.2.0 - "###); + "); // Re-request `black`. It should reinstall, without requiring `--force`. uv_snapshot!(context.filters(), context.tool_install() @@ -1649,18 +1649,18 @@ fn tool_install_no_entrypoints() { .arg("iniconfig") .env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str()) .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()) - .env(EnvVars::PATH, bin_dir.as_os_str()), @r###" + .env(EnvVars::PATH, bin_dir.as_os_str()), @r" success: false exit_code: 1 ----- stdout ----- - No executables are provided by `iniconfig` + No executables are provided by package `iniconfig`; removing tool ----- stderr ----- Resolved 1 package in [TIME] Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); // Ensure the tool environment is not created. tool_dir From ec54dce9191c8cde770572afde78ab4ce4001f6a Mon Sep 17 00:00:00 2001 From: Jack O'Connor Date: Wed, 2 Jul 2025 12:40:18 -0700 Subject: [PATCH 136/349] Includes `sys.prefix` in cached environment keys to avoid `--with` collisions across projects (#14403) Fixes https://github.com/astral-sh/uv/issues/12889. --------- Co-authored-by: Zanie Blue --- crates/uv/src/commands/project/environment.rs | 39 +++++++++++++++---- crates/uv/tests/it/run.rs | 22 +++++++---- 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/crates/uv/src/commands/project/environment.rs b/crates/uv/src/commands/project/environment.rs index f5a9713d2..f43587ff0 100644 --- a/crates/uv/src/commands/project/environment.rs +++ b/crates/uv/src/commands/project/environment.rs @@ -44,13 +44,15 @@ impl CachedEnvironment { printer: Printer, preview: PreviewMode, ) -> Result { - let interpreter = Self::base_interpreter(interpreter, cache)?; + // Resolve the "base" interpreter, which resolves to an underlying parent interpreter if the + // given interpreter is a virtual environment. + let base_interpreter = Self::base_interpreter(interpreter, cache)?; // Resolve the requirements with the interpreter. let resolution = Resolution::from( resolve_environment( spec, - &interpreter, + &base_interpreter, build_constraints.clone(), &settings.resolver, network_settings, @@ -73,13 +75,34 @@ impl CachedEnvironment { hash_digest(&distributions) }; - // Hash the interpreter based on its path. - // TODO(charlie): Come up with a robust hash for the interpreter. - let interpreter_hash = - cache_digest(&canonicalize_executable(interpreter.sys_executable())?); + // Construct a hash for the environment. + // + // Use the canonicalized base interpreter path since that's the interpreter we performed the + // resolution with and the interpreter the environment will be created with. + // + // We also include the canonicalized `sys.prefix` of the non-base interpreter, that is, the + // virtual environment's path. Originally, we shared cached environments independent of the + // environment they'd be layered on top of. However, this causes collisions as the overlay + // `.pth` file can be overridden by another instance of uv. Including this element in the key + // avoids this problem at the cost of creating separate cached environments for identical + // `--with` invocations across projects. We use `sys.prefix` rather than `sys.executable` so + // we can canonicalize it without invalidating the purpose of the element — it'd probably be + // safe to just use the absolute `sys.executable` as well. + // + // TODO(zanieb): Since we're not sharing these environmments across projects, we should move + // [`CachedEvnvironment::set_overlay`] etc. here since the values there should be constant + // now. + // + // TODO(zanieb): We should include the version of the base interpreter in the hash, so if + // the interpreter at the canonicalized path changes versions we construct a new + // environment. + let environment_hash = cache_digest(&( + &canonicalize_executable(base_interpreter.sys_executable())?, + &interpreter.sys_prefix().canonicalize()?, + )); // Search in the content-addressed cache. - let cache_entry = cache.entry(CacheBucket::Environments, interpreter_hash, resolution_hash); + let cache_entry = cache.entry(CacheBucket::Environments, environment_hash, resolution_hash); if cache.refresh().is_none() { if let Ok(root) = cache.resolve_link(cache_entry.path()) { @@ -93,7 +116,7 @@ impl CachedEnvironment { let temp_dir = cache.venv_dir()?; let venv = uv_virtualenv::create_venv( temp_dir.path(), - interpreter, + base_interpreter, uv_virtualenv::Prompt::None, false, false, diff --git a/crates/uv/tests/it/run.rs b/crates/uv/tests/it/run.rs index 82f3c2b0b..98c2adbfe 100644 --- a/crates/uv/tests/it/run.rs +++ b/crates/uv/tests/it/run.rs @@ -4777,7 +4777,7 @@ fn run_groups_include_requires_python() -> Result<()> { bar = ["iniconfig"] baz = ["iniconfig"] dev = ["sniffio", {include-group = "foo"}, {include-group = "baz"}] - + [tool.uv.dependency-groups] foo = {requires-python="<3.13"} @@ -4876,7 +4876,7 @@ fn exit_status_signal() -> Result<()> { #[test] fn run_repeated() -> Result<()> { - let context = TestContext::new_with_versions(&["3.13"]); + let context = TestContext::new_with_versions(&["3.13", "3.12"]); let pyproject_toml = context.temp_dir.child("pyproject.toml"); pyproject_toml.write_str(indoc! { r#" @@ -4923,22 +4923,25 @@ fn run_repeated() -> Result<()> { Resolved 1 package in [TIME] "###); - // Re-running as a tool shouldn't require reinstalling `typing-extensions`, since the environment is cached. + // Re-running as a tool does require reinstalling `typing-extensions`, since the base venv is + // different. uv_snapshot!( context.filters(), - context.tool_run().arg("--with").arg("typing-extensions").arg("python").arg("-c").arg("import typing_extensions; import iniconfig"), @r###" + context.tool_run().arg("--with").arg("typing-extensions").arg("python").arg("-c").arg("import typing_extensions; import iniconfig"), @r#" success: false exit_code: 1 ----- stdout ----- ----- stderr ----- Resolved 1 package in [TIME] + Installed 1 package in [TIME] + + typing-extensions==4.10.0 Traceback (most recent call last): File "", line 1, in import typing_extensions; import iniconfig ^^^^^^^^^^^^^^^^ ModuleNotFoundError: No module named 'iniconfig' - "###); + "#); Ok(()) } @@ -4979,22 +4982,25 @@ fn run_without_overlay() -> Result<()> { + typing-extensions==4.10.0 "###); - // Import `iniconfig` in the context of a `tool run` command, which should fail. + // Import `iniconfig` in the context of a `tool run` command, which should fail. Note that + // typing-extensions gets installed again, because the venv is not shared. uv_snapshot!( context.filters(), - context.tool_run().arg("--with").arg("typing-extensions").arg("python").arg("-c").arg("import typing_extensions; import iniconfig"), @r###" + context.tool_run().arg("--with").arg("typing-extensions").arg("python").arg("-c").arg("import typing_extensions; import iniconfig"), @r#" success: false exit_code: 1 ----- stdout ----- ----- stderr ----- Resolved 1 package in [TIME] + Installed 1 package in [TIME] + + typing-extensions==4.10.0 Traceback (most recent call last): File "", line 1, in import typing_extensions; import iniconfig ^^^^^^^^^^^^^^^^ ModuleNotFoundError: No module named 'iniconfig' - "###); + "#); // Re-running in the context of the project should reset the overlay. uv_snapshot!( From 3bb8ac610ca5690fce0eea9da792dbe6eecaad05 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 2 Jul 2025 14:51:17 -0500 Subject: [PATCH 137/349] Sync latest Python releases (#14426) Automated update for Python releases. Co-authored-by: zanieb <2586601+zanieb@users.noreply.github.com> --- .../uv-dev/src/generate_sysconfig_mappings.rs | 4 +- crates/uv-python/download-metadata.json | 936 +++++++++--------- .../src/sysconfig/generated_mappings.rs | 8 +- crates/uv-python/src/sysconfig/mod.rs | 2 +- 4 files changed, 472 insertions(+), 478 deletions(-) diff --git a/crates/uv-dev/src/generate_sysconfig_mappings.rs b/crates/uv-dev/src/generate_sysconfig_mappings.rs index e35d2c3fc..f556922c6 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/20250630/cpython-unix/targets.yml"; +const TARGETS_YML_URL: &str = "https://raw.githubusercontent.com/astral-sh/python-build-standalone/refs/tags/20250702/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/download-metadata.json b/crates/uv-python/download-metadata.json index 4035a6a48..b697da9c8 100644 --- a/crates/uv-python/download-metadata.json +++ b/crates/uv-python/download-metadata.json @@ -11,8 +11,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "56f9d76823f75e6853d23dc893dbe9c5e8b4d120ba1e568ceb62f7aff7285625", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "0b948f37363193fcf5e20c2e887183467907f1b6d04420fc5a0c0c7c421e7b12", "variant": null }, "cpython-3.14.0b3-darwin-x86_64-none": { @@ -27,8 +27,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "135c7119d5ce56c97171769f51fee3be0e6ebfdab13c34dc28af46dbf00dffd1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "47f21cf35481e5ba8e4e6b35c4dd549b0463d0f1dc24134d6e7fcc832a292869", "variant": null }, "cpython-3.14.0b3-linux-aarch64-gnu": { @@ -43,8 +43,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "046cfd0b5132d1ac441eaa81d20d4e71f113c0f4ca1831be35fc2581171a2e47", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "2935079dd417d8940955f0b083be698ae27a1d65f947614c36ce5e4ea509c812", "variant": null }, "cpython-3.14.0b3-linux-armv7-gnueabi": { @@ -59,8 +59,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "7922cceb24b1c7bbff5da8fa663ea3d75f0622cca862806d7e06080051d87919", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "b64dfec2a7016ae5fa5340298f46c05df0c93a30021c009fd3db9b97a5cad92b", "variant": null }, "cpython-3.14.0b3-linux-armv7-gnueabihf": { @@ -75,8 +75,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "4b3450a8ae7f19c682563f44105f9af946bd5504f585fc135f78b066c11290b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "7139f66c73f09f8ed3fcd840e08b85dc591fe8df048cfa5c48dc695a68f74149", "variant": null }, "cpython-3.14.0b3-linux-powerpc64le-gnu": { @@ -91,8 +91,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "34c66ea418df6bff2f144a8317a8940827bf5545bb41e21ebf4ae991bc0d0fa7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "5210b912d9dc1e7ee9fc215972c7c254ddaf9d64ad293f42af1a819896a4cbed", "variant": null }, "cpython-3.14.0b3-linux-riscv64-gnu": { @@ -107,8 +107,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "952a785c85ae9b19ef5933bc406ed2ae622f535862f31f1d9d83579ebd7f1ab5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "66a8f6825c5e1b289bfd62370b4cc6c9b5212a91b0440dcf5408c4e3bcfcdddd", "variant": null }, "cpython-3.14.0b3-linux-s390x-gnu": { @@ -123,8 +123,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "09a6f67c1c8c1a056ac873c99b8dc9b393013fc06bee46b9a4f841686ac39948", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "2af3a5d27e7fd49b5796a35c1f4a17848d9e5d40c946b9e690d7c27e527d99d8", "variant": null }, "cpython-3.14.0b3-linux-x86_64-gnu": { @@ -139,8 +139,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "771f78fb170b706c769742712143c6e3d0ed23e6b906bfd6e0bd7e5f253a8c15", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "17643efc55b6b68b4fa7b3a5e43abb0ea31b4f03942e2d17bd04c5cd5be52c52", "variant": null }, "cpython-3.14.0b3-linux-x86_64-musl": { @@ -155,8 +155,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "b5d645032acef89e5a112746bd0d7db5da2c04c72eebb5b7ca2dcd627c66502f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "1c35d7e5ac357d012d3c265da406e331535bf9fa5e29454b190ac8cc0c57dd40", "variant": null }, "cpython-3.14.0b3-linux-x86_64_v2-gnu": { @@ -171,8 +171,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "bba4f53b8f9d93957bacecceb70e39199fe235836d5f7898f10c061fe4f21a19", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "8d7c283a6f9e18377776968c5d5fcce9ff0a9c833c4f6c64d8f804da743e0e9d", "variant": null }, "cpython-3.14.0b3-linux-x86_64_v2-musl": { @@ -187,8 +187,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "7692fedeff8c378c2fd046204296d4e9c921151176bcae80bff51d9cff6217d6", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "75d5b65bae7b39f3e35a30070a7ccef0c773b1976e764c7fb68ba840a3ad0594", "variant": null }, "cpython-3.14.0b3-linux-x86_64_v3-gnu": { @@ -203,8 +203,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "7d220528664f7b86555fed946ce29e9522db63b49f7c952c7df9bdee0a002466", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "db25121d9a35f1613e281ead33903a7e6489d0506207451ef49d82eb71d722df", "variant": null }, "cpython-3.14.0b3-linux-x86_64_v3-musl": { @@ -219,8 +219,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "8188269487389fe6b1c441593257e5bd8bf6b8877a27ea0388d1a0282a2206f9", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "31cbe24575231d706937802a8f81536d11dd79f8c9cd7981b8f93b970a8e8481", "variant": null }, "cpython-3.14.0b3-linux-x86_64_v4-gnu": { @@ -235,8 +235,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "da2699921d0b1af5527fbe7484534a5afbc40bfd50b2b1d4c296ced849ae3c3f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "3c98b94dfc77c9d6369c3cdc09e03abc0dad2ead2f40a6b52d1b119bdcb33ab7", "variant": null }, "cpython-3.14.0b3-linux-x86_64_v4-musl": { @@ -251,8 +251,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "059cab36f0a7fc71d3bf401ba73b41dc2df40b8f593e46c983cc5f2cd531ad19", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "0742eb6b381fdb6b57983f8a5918dd9e154953f959f2be5a203699e5b1901c1b", "variant": null }, "cpython-3.14.0b3-windows-aarch64-none": { @@ -267,8 +267,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "fa9c906275b89bf2068945394edb75be580ba8a3a868d647bcde2a3b958cf7ad", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "62dc6ff21cbbf2c216f1b9f573ed8e0433c0f7185280a13b2b2f3a81ac862b90", "variant": null }, "cpython-3.14.0b3-windows-i686-none": { @@ -283,8 +283,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "e1d5ae8b19492bd7520dd5d377740afcfebe222b5dc52452a69b06678df02deb", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "0fc98664753360e23eaf3aa169657627ca5766141a49e1cfb0397895cbb47826", "variant": null }, "cpython-3.14.0b3-windows-x86_64-none": { @@ -299,8 +299,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "d01bb3382f9d4e7e956914df3c9efef6817aaf8915afa3a51f965a41d32bf8de", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "5b5ef4c03b4e2aaab389f10b973914780d76bd82eeaeb3c305239a57aba2e367", "variant": null }, "cpython-3.14.0b3+freethreaded-darwin-aarch64-none": { @@ -315,8 +315,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "475280f8fe3882e89cc4fa944a74dfaa5e4cfa5303b40803f4d782a08bf983a3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "d19213021f5fd039d7021ccb41698cc99ca313064d7c1cc9b5ef8f831abb9961", "variant": "freethreaded" }, "cpython-3.14.0b3+freethreaded-darwin-x86_64-none": { @@ -331,8 +331,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "79f93e3f673400764f00f5eb5f9d7c7c8ed20889a9a05f068b1cdc45141821c5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-x86_64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "26ec6697bbb38c3fa6275e79e110854b2585914ca503c65916478e7ca8d0491b", "variant": "freethreaded" }, "cpython-3.14.0b3+freethreaded-linux-aarch64-gnu": { @@ -347,8 +347,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-aarch64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "14ea3ce113f8eff97282c917c29dda2b2e9d9add07100b0e9b48377f1b5691cf", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-aarch64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "b01cc74173515cc3733f0af62b7d574364c1c68daf3ad748bca47e4328770cde", "variant": "freethreaded" }, "cpython-3.14.0b3+freethreaded-linux-armv7-gnueabi": { @@ -363,8 +363,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-armv7-unknown-linux-gnueabi-freethreaded%2Blto-full.tar.zst", - "sha256": "57d60ebe0a930faf07c7d2a59eadf3d5e59cf663492d0cadb91b4a936ec77319", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-armv7-unknown-linux-gnueabi-freethreaded%2Blto-full.tar.zst", + "sha256": "199ff8d1366007d666840a7320b0a44e6bab0aa0ee1e13e9247d3ec610ed9d45", "variant": "freethreaded" }, "cpython-3.14.0b3+freethreaded-linux-armv7-gnueabihf": { @@ -379,8 +379,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-armv7-unknown-linux-gnueabihf-freethreaded%2Blto-full.tar.zst", - "sha256": "966e2ec436d34cfb5c485c8a45029de8c7ab2501e97717a361c10bdf464e2287", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-armv7-unknown-linux-gnueabihf-freethreaded%2Blto-full.tar.zst", + "sha256": "e62adb4c3c7549bb909556457ac7863b98073bdcf5e6d9ffec52182b0fe32ccd", "variant": "freethreaded" }, "cpython-3.14.0b3+freethreaded-linux-powerpc64le-gnu": { @@ -395,8 +395,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-ppc64le-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "9a036e9fff85e35dc7d9488604675bd139434e34020738b6e61a904e47567a50", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-ppc64le-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "1f093e0c3532e27744e3fb73a8c738355910b6bfa195039e4f73b4f48c1bc4fc", "variant": "freethreaded" }, "cpython-3.14.0b3+freethreaded-linux-riscv64-gnu": { @@ -411,8 +411,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-riscv64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "875b77a12acf56f5b4349995271f387f0dd82e11c97b70d268070855b39b4bc4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-riscv64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "73162a5da31cc1e410d456496114f8e5ee7243bc7bbe0e087b1ea50f0fdc6774", "variant": "freethreaded" }, "cpython-3.14.0b3+freethreaded-linux-s390x-gnu": { @@ -427,8 +427,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-s390x-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "328fb2c96de92f38c69f5031ed0dd72e4d317593ac13dde0c1daadb2e554317b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-s390x-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "045017e60f1298111e8ccfec6afbe47abe56f82997258c8754009269a5343736", "variant": "freethreaded" }, "cpython-3.14.0b3+freethreaded-linux-x86_64-gnu": { @@ -443,8 +443,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "fff6d39b2b03ea19fe191b1c64e67732b3f2d8f374bc6c6fd9bd94954d6287f2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-x86_64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "081f0147d8f4479764d6a3819f67275be3306003366eda9ecb9ee844f2f611be", "variant": "freethreaded" }, "cpython-3.14.0b3+freethreaded-linux-x86_64-musl": { @@ -459,8 +459,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "b227cf474974ea4a92bc2a633e288456403f564251bf07b1a1ae288e0fac5551", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-x86_64-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "3e20f3c4757ca3d3738e2b4ed9bb7ce1b6b868b0f92e1766549b58bdfdf6ad79", "variant": "freethreaded" }, "cpython-3.14.0b3+freethreaded-linux-x86_64_v2-gnu": { @@ -475,8 +475,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v2-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "b9b4a72b211d97cafb37b66cb4a918b72af24bb4d5b957a96e978cad22a5cd67", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-x86_64_v2-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "7b50ca3a919531e6d175308d53efa0ccd3d21438ac735a51c7fdcd74c5316f99", "variant": "freethreaded" }, "cpython-3.14.0b3+freethreaded-linux-x86_64_v2-musl": { @@ -491,8 +491,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v2-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "f7898473617e67d3d6fd428a8c340ae340eb043d35e1d1584ed0497b3b933cb6", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-x86_64_v2-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "6787ae8dfa33268ae3336d9f2ff7107bb9da5714757cab2aed20bf916835888f", "variant": "freethreaded" }, "cpython-3.14.0b3+freethreaded-linux-x86_64_v3-gnu": { @@ -507,8 +507,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v3-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "7844c24cec06402fd6e437c62c347ea37ff685e2e1fda0fbc3076a70b2963296", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-x86_64_v3-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "6f16bffec9ad3717498b379b5640956abeb39b830ae390bb650585beac14b974", "variant": "freethreaded" }, "cpython-3.14.0b3+freethreaded-linux-x86_64_v3-musl": { @@ -523,8 +523,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v3-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "f39381f98e18a06fdc35137471f267d6de018bf0f6209e993a769c494b3e7fcd", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-x86_64_v3-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "651aef6d3640db60dbb0c28c68d194846053b3d08085427e1c9c76eb13de5e46", "variant": "freethreaded" }, "cpython-3.14.0b3+freethreaded-linux-x86_64_v4-gnu": { @@ -539,8 +539,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v4-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "c19941d7c0d7002ebad3d13947ad0231215ed1d6a9e93c8318e71dbaf6a9bbdd", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-x86_64_v4-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "637097da9317bd1af34a2f3baab76d98fb11aee3fb887dec4e829616d944cdb8", "variant": "freethreaded" }, "cpython-3.14.0b3+freethreaded-linux-x86_64_v4-musl": { @@ -555,8 +555,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v4-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "e7d6c051de697ed6f95a0560fde3eb79c20b2ea9ca94ef02c4f7ec406f813ec4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-x86_64_v4-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "f607cd590190311cbe5f85d82d4220eb5b71416486b827e99b93ca1c341f2045", "variant": "freethreaded" }, "cpython-3.14.0b3+freethreaded-windows-aarch64-none": { @@ -571,8 +571,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-aarch64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", - "sha256": "986456a0e936f332a78a4130ea1114684d10b42b92b92d93b63da974767ae426", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-aarch64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "331816d79cd78eaadba5ae6cdd3a243771199d0ca07057e7a452158dd4a7edcc", "variant": "freethreaded" }, "cpython-3.14.0b3+freethreaded-windows-i686-none": { @@ -587,8 +587,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-i686-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", - "sha256": "dc630b0715cd1c7b23291715ce4fa62506ccd0578c4b186c5fd0a92a34cc6f0e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-i686-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "2e55b7204f391fbe653168e6004daf5ed624d890ab7dd7d5aa7f7234e271ae47", "variant": "freethreaded" }, "cpython-3.14.0b3+freethreaded-windows-x86_64-none": { @@ -603,8 +603,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", - "sha256": "d136ddc9fcf1f27e5791dd3dd5edf755830f1eef65066af749760f350cea41c8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-x86_64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "8de6235b29396e3b25fc3ade166c49506171ec464cda46987ef9641dd9a44071", "variant": "freethreaded" }, "cpython-3.14.0b3+debug-linux-aarch64-gnu": { @@ -619,8 +619,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "9bef0ad9e9c8c477c5a632074cb34d8ab3c25acaff95d7d7052dbabb952459c7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "9bc39c6c669aaba047690395bf0955062aa80edb4fd92c59ada03a18a3df1234", "variant": "debug" }, "cpython-3.14.0b3+debug-linux-armv7-gnueabi": { @@ -635,8 +635,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "28324fddb6699ce11ae62dc6458e963c44e82f045550ced84ad383b12a810472", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "544dc6759e2d7af367eeb5d3c45116c52c33054a730e120a8ea442e6e8b9d091", "variant": "debug" }, "cpython-3.14.0b3+debug-linux-armv7-gnueabihf": { @@ -651,8 +651,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "74e896ddc8ec4b34f2f5c274132a96bb191b1524bc1627929084bec64ce2db62", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "3e91cd08cefd404d55003ec25347ae9d591e72ee77a00e2a172ce206c34f5ecc", "variant": "debug" }, "cpython-3.14.0b3+debug-linux-powerpc64le-gnu": { @@ -667,8 +667,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "127450fe5cb01a313870aa32d97740b0a5b3eaac2119a1590cabf66409fb31a4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "b5679a4176431600ce146a6783046bbac84721d99ff91ead0b8eef1538514369", "variant": "debug" }, "cpython-3.14.0b3+debug-linux-riscv64-gnu": { @@ -683,8 +683,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "7289b088a22731a0530c25395dd0617fe3d21fddc0c806206daa35b33d16f4ac", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "9724b0ebf2a8f80c1dd76bcb9880297bb2a95010bc707868145d9a1cfa0857de", "variant": "debug" }, "cpython-3.14.0b3+debug-linux-s390x-gnu": { @@ -699,8 +699,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "356b05d8d5c612bbd29acaed04dc3c54d8ea42af0d31608370b04cce5093c4e7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "23ca40a78ad8a61fc820d58b71e5aeb3b5f88ed7e449a04c0515b37041e8e644", "variant": "debug" }, "cpython-3.14.0b3+debug-linux-x86_64-gnu": { @@ -715,8 +715,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "3092e467a21a4f00d030f0ac3efb97e49939debe3c00ed8c80cfd25c0db238f7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "84129181fc24fd5fd39a8dc83c5eb4dd7c51a9f105bd1b22733dba2d52da9f38", "variant": "debug" }, "cpython-3.14.0b3+debug-linux-x86_64-musl": { @@ -731,8 +731,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "d8391e345823d2eaf0745ddb77982a35c032674e1d08ec29321568c21737a15e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "bfaaabee0e9cab4a7967be9759140830de1994c8f87e8e05bee5ec7fd6a99b69", "variant": "debug" }, "cpython-3.14.0b3+debug-linux-x86_64_v2-gnu": { @@ -747,8 +747,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "3f497ae10d574532c0a79b4704ba7944ee41586e86997ec1933c99efdfe19ec3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "c14586447c4ef79ab875b7b7b8a13e6d05eaec8627f187067e02f4b026023db6", "variant": "debug" }, "cpython-3.14.0b3+debug-linux-x86_64_v2-musl": { @@ -763,8 +763,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "da154e3008bc4bd4185993bc04b355b79fbcd63a900b049d4595ea3615aef76c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "69c477df92e4332382e9a1b3177155b1c2c9e6612366b385409bd17f18c49a70", "variant": "debug" }, "cpython-3.14.0b3+debug-linux-x86_64_v3-gnu": { @@ -779,8 +779,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "a64d5b16c684700b58cff51164c0faca6dc4de3dc6509ea958f975217fb1f804", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "5a9c969834b90307152a8bdcef27a2797288fdfecb92911e0ebc17ec5747ccbf", "variant": "debug" }, "cpython-3.14.0b3+debug-linux-x86_64_v3-musl": { @@ -795,8 +795,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "ced306fbaf48996b422c5e45125de61578fe1a7b3f458bd4173f462b6b691fd2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "02fad0b21f30b42742468107fe33eb23d307ba2c5670b0baa11e33fc30160fba", "variant": "debug" }, "cpython-3.14.0b3+debug-linux-x86_64_v4-gnu": { @@ -811,8 +811,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "68a7b564d38fafd2c0b864adcdc3595830d58714093951a5412ad72c021b302a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "4e110ee96813a907c7468f5c1317046c5e5ba10e2fe23b2c3d30f1ee6b4bc5c7", "variant": "debug" }, "cpython-3.14.0b3+debug-linux-x86_64_v4-musl": { @@ -827,8 +827,8 @@ "minor": 14, "patch": 0, "prerelease": "b3", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.14.0b3%2B20250630-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "7213fcbc7628c5a15cf3fb1c8bff43d0b8a5473fd86cf46941f7ee00f2b9d136", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.14.0b3%2B20250702-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "6015df86031d23765c7f4c8a02e1aa3e3b5e4a5fe9f2747fcdc37d28e3f8a0f5", "variant": "debug" }, "cpython-3.14.0b2-darwin-aarch64-none": { @@ -5563,8 +5563,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "a36fb4e711c2c7987d541aa5682db80fc7e7b72205bae6c330bf992d23db576f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "5a7888b6e0bbc2abf7327a786d50f46f36b941f43268ce05d6ef6f1f733734ca", "variant": null }, "cpython-3.13.5-darwin-x86_64-none": { @@ -5579,8 +5579,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "81b51eec47af8cd63d7fbcc809bd0ae3e07966a549620b159598525788961fdc", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "691282c117b01de296d70bd3f2ec2d7316620233626440b35fa2271ddbcc07dc", "variant": null }, "cpython-3.13.5-linux-aarch64-gnu": { @@ -5595,8 +5595,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "906ffb000e53469921b0c3cfbcdab88e7fbf8a29cd48dec8cb9a9b8146947e1d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "d7ab196fefb0cacb44869774dd6afcaed2edc90219b67601ec1875002378219f", "variant": null }, "cpython-3.13.5-linux-armv7-gnueabi": { @@ -5611,8 +5611,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "5cae766871606d74466e54fe8260ce81d74e1d545eb78da8d4986a910d8c418a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "b90aac358d49e0c278843b83b5c8935036effe10f004ecec903313fea199badf", "variant": null }, "cpython-3.13.5-linux-armv7-gnueabihf": { @@ -5627,8 +5627,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "50e3cd7c633991c1b1d48a00a708ff002437f96e4b8400f93f97e237b2777208", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "69fe828cd149b21a6fda0937f73ef654dd8237d567916508acb328f24f9368c7", "variant": null }, "cpython-3.13.5-linux-powerpc64le-gnu": { @@ -5643,8 +5643,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "5f2b7dbecf27e21afa5c3a6e203be0bd36a325b422b12bc3526de475e2566c3f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "1a69f799fc5c0eb61708202ec5ba1514d6e5f3a547c07c53d40415d93084b903", "variant": null }, "cpython-3.13.5-linux-riscv64-gnu": { @@ -5659,8 +5659,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "713da45173f786f38e22f56d49b495647d93713dd9a0ec8bd8aa1f9e8e87ac1e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "2ce3570c6f3f8204e4b5e8e35896c87c71ddc038ca9a60f1111e2ea468b78f08", "variant": null }, "cpython-3.13.5-linux-s390x-gnu": { @@ -5675,8 +5675,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "c016223dcbfb5f3d686db67ba6d489426de21076d356640dcb0f8bf002573b43", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "c974786ad18943fc3d5fbe4eca7bd43ceb07e725d2d513ac4dc0e3b6dd11a89e", "variant": null }, "cpython-3.13.5-linux-x86_64-gnu": { @@ -5691,8 +5691,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "3057ed944be60b0d636aebfdde12dedb62c6554f33b5b50b8806dbc15a502324", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "4444b5e95217c2c686bf3a689ab9655d47ea3b11c1f74714eceab021d50b7d74", "variant": null }, "cpython-3.13.5-linux-x86_64-musl": { @@ -5707,8 +5707,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "e26d9d405c2f224ac6d989b82d99fd1e3468d2dfaf288ac5041f743fdea327c0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "5125ef4d58b3dddbb0946c29f443160de95d6e8ea79bbe9562f9dd2873651d12", "variant": null }, "cpython-3.13.5-linux-x86_64_v2-gnu": { @@ -5723,8 +5723,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "3041155088ef88fd28981f268de5b47d243ac9a2cffa3582fc4c1a6c633bb653", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "91cf219324d090e7b2814f33c2b7fbf4930aa01c6a0fd8960eab8874f3b8babd", "variant": null }, "cpython-3.13.5-linux-x86_64_v2-musl": { @@ -5739,8 +5739,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "1ec20ff2606e5510b30e0cb56ebe06b806b08c13712007d7550d26a35bddc9bd", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "392dd5fd090f9aa5759318795733e37026cf13d554bcf5f90315d0f448a07228", "variant": null }, "cpython-3.13.5-linux-x86_64_v3-gnu": { @@ -5755,8 +5755,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "e62614725c2b31060063a5a54f24d01b90cd0f2418c6ff14c61cfad0f81cfefd", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "c1bd09a8f83a78dd92a6d0e2a8dbf30b99d6ca31269fd1c80e14f0769b67df3f", "variant": null }, "cpython-3.13.5-linux-x86_64_v3-musl": { @@ -5771,8 +5771,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "9a02ef405e8d811c4cf703aa237c428f9460b4857465f6b4a5c23fce4948e887", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "77350988d48699f3172a25aad33b229604b6faab9f1df35589ad7aca10ec10a8", "variant": null }, "cpython-3.13.5-linux-x86_64_v4-gnu": { @@ -5787,8 +5787,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "f06da893a220d6e97f845292c7623743b79167047c0d97ead6b2baa927d9d26b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "f84aafa52b22484de42edb7c9965cafc52718fc03ac5f8d5ad6a92eb46ff3008", "variant": null }, "cpython-3.13.5-linux-x86_64_v4-musl": { @@ -5803,8 +5803,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "a16a169ac9675e82ab190528e93cfbc7ab86f446485748afe1138010753eebd4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "9510ffc50a28a1a06d5c1ed2bfd18fa0f469d5e93982d7a9289ab0ac4c8a2eee", "variant": null }, "cpython-3.13.5-windows-aarch64-none": { @@ -5819,8 +5819,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "bc82935e2102d265b298fb1618930b36930fc1820d8dc2f6e3c2e34767917f44", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "2459713eff80372e0bfcce42b23b9892eb7e3b21ea6ae5cb5e504b8c0f12e6dd", "variant": null }, "cpython-3.13.5-windows-i686-none": { @@ -5835,8 +5835,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "7ffe48c8242d24b029ae880a0651076ec3eb9b2a23a71f9ad170bee37cc40f42", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "76287476f3a2b8658d29da67e05d550fbf2db33b9e9730c6d071bd239211ffe8", "variant": null }, "cpython-3.13.5-windows-x86_64-none": { @@ -5851,8 +5851,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "5f7b96c86d4bea7a823d0fbce8e139a39acad3560bb0b8ffaae236c0bf8251f9", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "f5838b42985c644d0597a1a6a54fb185647bb57d4f06cbc7d3ac8dfb53326521", "variant": null }, "cpython-3.13.5+freethreaded-darwin-aarch64-none": { @@ -5867,8 +5867,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "71772559da8e7155ae34ee9e625126bb76df42d66184b533f82c74b7acd0b9f0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "52e582cc89d654c565297b4ff9c3bd4bed5c3e81cad46f41c62485e700faf8bd", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-darwin-x86_64-none": { @@ -5883,8 +5883,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "e31c161b78c141e9fde65e7a37ac0e944ac2f8fb0e001b49244deae824ed0343", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "5aed6d5950514004149d514f81a1cd426ac549696a563b8e47d32f7eba3b4be3", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-aarch64-gnu": { @@ -5899,8 +5899,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-aarch64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "578fcc7c2f8e9816ccd7302a5f06f3b295e1059249f594b70a15eb6b744405e9", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-aarch64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "461832e4fb5ec1d719dc40f6490f9a639414dfa6769158187fa85d4b424b57cd", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-armv7-gnueabi": { @@ -5915,8 +5915,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-armv7-unknown-linux-gnueabi-freethreaded%2Blto-full.tar.zst", - "sha256": "60be2a6390408fe4ff1def8b5841240749f6e036be0719aa4120dc6412657467", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-armv7-unknown-linux-gnueabi-freethreaded%2Blto-full.tar.zst", + "sha256": "469fc30158fbcb5c3dc7e65e0d7d9e9e0f4de7dffdc97492083781f5f6216356", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-armv7-gnueabihf": { @@ -5931,8 +5931,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-armv7-unknown-linux-gnueabihf-freethreaded%2Blto-full.tar.zst", - "sha256": "e477210f0b1c66e3811433bb1d45d7336e9e0bb52f4ec2a4d6a789dbdd9de9c2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-armv7-unknown-linux-gnueabihf-freethreaded%2Blto-full.tar.zst", + "sha256": "9db3d9dbb529068d24b46c0616307f3c278e59c0087d7a1637105afde3bc5685", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-powerpc64le-gnu": { @@ -5947,8 +5947,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-ppc64le-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "4d00d210cf083c83950e5af59df044c34538c15f823a6b07c667df2479bbcb21", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-ppc64le-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "c65c75edb450de830f724afdc774a215c2d3255097e0d670f709d2271fd6fd52", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-riscv64-gnu": { @@ -5963,8 +5963,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-riscv64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "63408df03d6d6ec22a9d4e01701a1519247ceb35804161d64f0aed0d73154087", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-riscv64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "716e6e3fad24fb9931b93005000152dd9da4c3343b88ca54b5c01a7ab879d734", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-s390x-gnu": { @@ -5979,8 +5979,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-s390x-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "af58974a23754b02301043d6e810fb3ce533d1c34c23d197a6690f3eb2f00fe1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-s390x-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "27276aee426a51f4165fac49391aedc5a9e301ae217366c77b65826122bb30fc", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64-gnu": { @@ -5995,8 +5995,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "976133de595393596207fa3c943d4e8e43ddb5d51b864766fc666a1752963e04", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "f5eb29604c0b7afa2097fca094a06eb7a1f3ca4e194264c34f342739cae78202", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64-musl": { @@ -6011,8 +6011,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "f11c199765a4c96e93bd5ffc65477dc7eb4c4edd03f20ecf4d51ea0afc3be0c2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "61f5960849c547313ff7142990ec8a8c1e299ccf3fcba00600bc8ee50fbb0db9", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v2-gnu": { @@ -6027,8 +6027,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v2-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "ff8066512111ac80962bdd094043f600eb9dbdd35e93f059f5ab34f9c4ff412e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v2-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "6348a6ca86e8cfe30557fecfc15d6facefeeecb55aba33c338d6aa5830495e5b", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v2-musl": { @@ -6043,8 +6043,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v2-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "26ab90c03672187ae4a330e59bf5142cbe7ed24e576edfd3b869fa108a3b3fc7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v2-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "4200aa24f5ca3b1621185fe0aee642f84e91ec353e0da2ca47a62c369855d07a", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v3-gnu": { @@ -6059,8 +6059,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v3-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "15e614a96bf2d789500c9bee03c7330e61278c1b9b4f3138f38bfa05bbe4bd68", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v3-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "17ada48150f49c1d9aefc10839708d6f31f2665fa625103d45ccf88af46c9674", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v3-musl": { @@ -6075,8 +6075,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v3-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "448f46715607f34cc217ce2a51d0782417d99077b5ecd8330d2c53b74e893a0c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v3-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "b8a951e4eb04042af91864f3934e8e8b7527e390720ba68507a4b9fe4143032b", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v4-gnu": { @@ -6091,8 +6091,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v4-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "6620f3bbd38732ce232d84abbad1836400a680a1208916af502bbeebc8ae825d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v4-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "7bb14c995b2bc7b0a330a8e7b33d103d9f99ecb9c30ff8ca621d9d066bb63d9f", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v4-musl": { @@ -6107,8 +6107,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v4-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "895e650967c266182f64c8f3a3fd395d336bbd95218fd0c7affc82f3c9465899", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v4-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "a78845959c6f816f9a0fa602403b339d67d7125515f5b0fbe5c0ef393e4ce4e9", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-windows-aarch64-none": { @@ -6123,8 +6123,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-aarch64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", - "sha256": "4dc24bdf390356281dd23a4644565e7f49187894015b8292c0257a0f5cd2fd43", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-aarch64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "97041594d903d6a1de1e55e9a3e5c613384aa7b900a93096f372732d9953f52a", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-windows-i686-none": { @@ -6139,8 +6139,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-i686-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", - "sha256": "009f56db3de1351dd616fb1fd8b04343637dd84b01c1eb37af8559fa87c6b423", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-i686-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "02d20b1521420ef84160c3a781928cdc49cd2e39b3850fb26f01e4e643b8379e", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-windows-x86_64-none": { @@ -6155,8 +6155,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", - "sha256": "7387f13148676858b9ab61ff7f03b82b88d81d0342346d1a156233b9478f3c81", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "39e19dcb823a2ed47d9510753a642ba468802f1c5e15771c6c22814f4acada94", "variant": "freethreaded" }, "cpython-3.13.5+debug-linux-aarch64-gnu": { @@ -6171,8 +6171,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "0698a989ead503f3181118a8c2793b431707190a1ef82fe2a5386a268ebe58d6", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "fc7e5a8765892887b887b31eaa03b952405c98ad0b79bf412489810ab4872a18", "variant": "debug" }, "cpython-3.13.5+debug-linux-armv7-gnueabi": { @@ -6187,8 +6187,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "0a5d4cec664ede542ad92115f2c90318bed8020d093ec98a9cf935374d0b684e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "61e5a7e1c6bd86db10302899fe1053f54815a4d3e846ad3e8d4ebc5a858aa1ae", "variant": "debug" }, "cpython-3.13.5+debug-linux-armv7-gnueabihf": { @@ -6203,8 +6203,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "6be78a4c8df0a4ee420e2a51b6f5581d7edf960b38bcf38c1a4f3ecee018b248", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "e400e3e2ae88108034e62076edbc5518974eb76a46d236b5322fa7b2aa2110f4", "variant": "debug" }, "cpython-3.13.5+debug-linux-powerpc64le-gnu": { @@ -6219,8 +6219,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "bba8b2e8c0a6095460c519c273d7e101c616c96e2992407301018d00a5d7b686", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "1216b39a34397f25065f80bb6f3ffa56f364da9dae09a519df9d384c3a1c7505", "variant": "debug" }, "cpython-3.13.5+debug-linux-riscv64-gnu": { @@ -6235,8 +6235,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "10be77859e5f99a83c07e4a521776d2a8860dd46ce8a46c1e3c2544d2593702a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "f2212f189076e6631e4447cc0c37872d03fc39eb92bb717a922899852e17765b", "variant": "debug" }, "cpython-3.13.5+debug-linux-s390x-gnu": { @@ -6251,8 +6251,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "05e668c47ba1c75f0df0c01b6afb3af39ae9d17a90d02f7f6508c094de79aa3c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "f672e057e98d5909b6ef94558018036254d4d4e40660cfb1654ce2c3b87bcd82", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64-gnu": { @@ -6267,8 +6267,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "5d8f83f26f8f203eb888ae42e02d6f85c26ac0306d65ad311e79ca2120095ce1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "3e798c809c4a9fc7308626ff72540036d5f01f9ac85ce6176acbdd581e973302", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64-musl": { @@ -6283,8 +6283,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "70705fd017908822cbfad83b444f4fc46301467f13e17c97de8e456ee9f08173", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "1d5a3c193673b52eab5c5a362a18e6e184e4298a36a809fe5e21be6f01f9b76f", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v2-gnu": { @@ -6299,8 +6299,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "064e7eb23cce3b5ceb22d974d14c80a0ffbe309178d60d56ee910deda69c3bb2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "8886ff6fd5483254a234e4ce52b4707147bc493f6556fa9869da4d1264af9440", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v2-musl": { @@ -6315,8 +6315,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "f875a778f5c1f4a3f8c0c37ebdd2437eea9abcfa3eff4d161ee8d761d3018892", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "8894486f97353fd0279fd7e4d107625aa57c68010c6fc8fcba6a549e3c4aa499", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v3-gnu": { @@ -6331,8 +6331,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "7024d12c2241595cb3c66a0c291d416cc431198aa01d8c2d642fad3266b4d2c8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "914baf954e3decbe182413b38a8a5c347246e889a4d34a91db3f4466945dba0a", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v3-musl": { @@ -6347,8 +6347,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "36ce0d9ca8163798c83d027d0abee2ed78ae0a91df96346ed5b7c26700131f38", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "9c5de2ef49b4b871157e81cd7a0f4e881971d0c16873e6ad6376ace2914c07c5", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v4-gnu": { @@ -6363,8 +6363,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "31c2c46f031fd32b2a8955a9193365f8e56f06e4b598b72d34441b4461ca55e7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "48f0eaeeac55dbe85593e659962e6ea44cc638f74cc0aab1d943da805a4eca39", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v4-musl": { @@ -6379,8 +6379,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.13.5%2B20250630-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "1129ba59de94be77f3ed9d4fccb5f48f9f297600b71aeea79d20f170f84b4323", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "d0b7928f0e56c3509d541ecb5538d93d0dd673ba6460159f0d05c6a453c575c4", "variant": "debug" }, "cpython-3.13.4-darwin-aarch64-none": { @@ -10603,8 +10603,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "52f103b1bae4105c8c3ba415fbfb992fc80672795c58262b96719efde97c49d9", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "0f3a56aeca07b511050210932e035a3b325abb032fca1e6b5d571f19cc93bc5b", "variant": null }, "cpython-3.12.11-darwin-x86_64-none": { @@ -10619,8 +10619,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "5e7229d72ec588b77f753ee043fcefe62f89190e84b8b43d20b1be4b4e6e8540", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "1543bcace1e0aadc5cdcc4a750202a7faa9be21fb50782aee67824f26f2668ad", "variant": null }, "cpython-3.12.11-linux-aarch64-gnu": { @@ -10635,8 +10635,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "117b1d2daa96486005827dd50029fecd61e99141396583ea8e57733f7cf0abad", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "1b7260c6aa4d3c7d27c5fc5750e6ece2312cf24e56c60239d55b5ad7a96b17cb", "variant": null }, "cpython-3.12.11-linux-armv7-gnueabi": { @@ -10651,8 +10651,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "878d29bab42a2911927948636a1bdd1e83ac1d31dbe4b857a214b04eed2046de", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "b977bb031eeffcf07b7373c509601dd26963df1a42964196fccf193129be6e3b", "variant": null }, "cpython-3.12.11-linux-armv7-gnueabihf": { @@ -10667,8 +10667,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "8a3ae17b0ecaa0334ca1bbd3d2cc7ea16d43ead269c03cdaedee54bfd6949962", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "bf67827338f2b17443e6df04b19827ed2e8e4072850b18d4feca70ba26ba2d56", "variant": null }, "cpython-3.12.11-linux-powerpc64le-gnu": { @@ -10683,8 +10683,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "bb0689d5101a4a85eaca7e8443912d196519103695937fb8e4fc819d66e48167", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "22f894d8e6d6848c4bc9ead18961febeaaecfea65bcf97ccc3ca1bd4fdcd4f70", "variant": null }, "cpython-3.12.11-linux-riscv64-gnu": { @@ -10699,8 +10699,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "59323d58c55f3afc90d93d185f57bdfc9bbfcf1e713290df6eda91fbabf91a90", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "96c5c30c57d5fd1bdb705bfe73170462201a519f8a27cc0a394cd4ed875ae535", "variant": null }, "cpython-3.12.11-linux-s390x-gnu": { @@ -10715,8 +10715,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "925c52b00a55b52492c197ebc7499317e4403cf603f1670a6e655b9426d286f2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "1967e03dd3d1f8137d29556eec8f7a51890816fd71c8b37060bd061bce74715a", "variant": null }, "cpython-3.12.11-linux-x86_64-gnu": { @@ -10731,8 +10731,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "358ad7bf46260a2e1b31726adc62dc04258c7ea7469352e155ffdea0f168499e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "2eed351d3f6e99b4b6d1fe1f4202407fe041d799585dffdf6d93c49d1f899e37", "variant": null }, "cpython-3.12.11-linux-x86_64-musl": { @@ -10747,8 +10747,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "63bcf41a095c298d412efdb9ceba0009e8544809fe0fa9b1ba51dc918f3dc3ee", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "8b201f157e437f4f3777e469b50f8e23dfa02f1c6757dfb2a19bde9f1bae9e0a", "variant": null }, "cpython-3.12.11-linux-x86_64_v2-gnu": { @@ -10763,8 +10763,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "49de0e676faa6e72a988ebe41d67cd433d074ab4fd3b555935d0d4f7437f7168", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "978249b5f885216be70d6723f51f5d6ad17628bacc2b1b98078e1273326ef847", "variant": null }, "cpython-3.12.11-linux-x86_64_v2-musl": { @@ -10779,8 +10779,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "66f322f7eb520a441ef341b166e9cbd45df83e5841fd4998181f431f719f5a8c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "b81b771718ed550c4476df3c07d38383a2c9341e2e912fd58c224820cb18195c", "variant": null }, "cpython-3.12.11-linux-x86_64_v3-gnu": { @@ -10795,8 +10795,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "be9ba63f7905b925d958aebcb8fcec40a2ba94552e2bd3d91818d97fc3d68ecb", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "651f0da21ac842276f9c3f955a3f3f87d0ad6ec1bba7c3bb8079c3f4752355b3", "variant": null }, "cpython-3.12.11-linux-x86_64_v3-musl": { @@ -10811,8 +10811,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "598661a23fd5c0f2870c99194938732a3e1a4909e41b8a54401b821f1594e66b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "4d7cbbf546b75623d0f7510befd2cf0a942b8bc0a38d82876f0844383aa27ba2", "variant": null }, "cpython-3.12.11-linux-x86_64_v4-gnu": { @@ -10827,8 +10827,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "b76430b697fa0c1ef1b2b04361decf36b7108da719c48ca2098e6aa0cd9a7130", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "b368b2dd0939f9e847acb5b0851081dcf2151e553bea4ac6f266a6ca0daeca01", "variant": null }, "cpython-3.12.11-linux-x86_64_v4-musl": { @@ -10843,8 +10843,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "88121676a946fe506d24821fc89439bc79a89a4eea9ffb5c903e9d7bc1c427d3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "da76b72b295b230022a839d42edfde36f79ebfd70c9b381f6ed551066c3942bd", "variant": null }, "cpython-3.12.11-windows-aarch64-none": { @@ -10859,8 +10859,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "45e121ad8e82eb9204cc1da53d0fdf125923f7caed18bf282b3c00dcfae89486", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "b5e56ebce5ea3cc0add5e460a254da1e095fdcf962552dceea1be314c45115bf", "variant": null }, "cpython-3.12.11-windows-i686-none": { @@ -10875,8 +10875,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "d80c67dd88aca4c94a57a8b2fe63de524a9e1f2308e5ecd2ca83a7961b9b9b17", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "0db0a69bab9aa6159f62d99074918b67e2a81c84b445570befeb583053663b58", "variant": null }, "cpython-3.12.11-windows-x86_64-none": { @@ -10891,8 +10891,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "91f7196d5d103eb3d1214f74606144adb326f0770d80c636cb11b96670477736", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "195033883da90a35a57aecce522eb068b9b0a36908e6e07b91929f0acf646c8f", "variant": null }, "cpython-3.12.11+debug-linux-aarch64-gnu": { @@ -10907,8 +10907,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "70a49e31e1f15036715e67ec08ef63df321171c7b922f16db9b462ed9d1ccbc6", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "b0f04667686f489a410bb3e641b6abefa75dad033cd6d2725ab49a40413e15b7", "variant": "debug" }, "cpython-3.12.11+debug-linux-armv7-gnueabi": { @@ -10923,8 +10923,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "87f27b3efb66a226f7a03029f14cbabdc512974f8489b003cb848dec3fde869c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "d882d16f304b858173b40cca5c681e8f9c9f13774c26390303bd7e7657a1d73c", "variant": "debug" }, "cpython-3.12.11+debug-linux-armv7-gnueabihf": { @@ -10939,8 +10939,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "28a431937f2b5467a0e74425d64bf27ca2e0c37681e3e93f71c19386cf457237", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "5934f6214d60a958fa3cb3147dad1941d912e0a9f37280de911cbf51a2a231be", "variant": "debug" }, "cpython-3.12.11+debug-linux-powerpc64le-gnu": { @@ -10955,8 +10955,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "e08f6f7faabda320f0a38a3388056f29641d7375a6247a6aebf885907a3baf2b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "a2c821f4a83c3a80d8ec25cf3ca5380aa749488d87db5c99f1c3100069309f5f", "variant": "debug" }, "cpython-3.12.11+debug-linux-riscv64-gnu": { @@ -10971,8 +10971,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "3537f9e61a46d50bc500310c0056a7b99d08a09b5ba29142c783d226a7712c4e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "d1ac376b8756a057ba0d885171caa72bc7cd7ab7436ebc93bd7c0c47cff01d05", "variant": "debug" }, "cpython-3.12.11+debug-linux-s390x-gnu": { @@ -10987,8 +10987,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "859d643152c2b86621a6aa9cfb9d1f50654b402e852ce9cdc864a2f07652c4f7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "8a57e27c920d1c93d45a3703c9f3fe047bac6305660a6d5ce2df51b0f7cfef26", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64-gnu": { @@ -11003,8 +11003,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "59aaff170ce2a6efe4a34e5ed50db83c4e29951e3c83ac7fa031f4b8247a440e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "6b1e42f30f027dc793f6edaf35c2ff857c63b0f72278c886917e99b6edd064b1", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64-musl": { @@ -11019,8 +11019,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "9b43f05f37cd39ff45021d8a13b219cb6ad970130c1c32463cd563002aaff9a7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "62b9039f765e56538de58cb37be6baaf2d9da35bb6d95c5e734b432ccec474f8", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v2-gnu": { @@ -11035,8 +11035,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "37ba61db27c6a1f9d540db6404ea2dfe1f0a221d08332b35476e2d88dd517a0c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "e1fb28c54a76f4e91f4d53fd5fd1840c7f0045049f7fca29f59c4d7bdfa8134d", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v2-musl": { @@ -11051,8 +11051,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "1c1fc0c7185caf9eaa5914b00176cbf9a2c4bc6922e6dde52587f3125c8f9db4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "c8d4fc92c668c0455a3dce10b2c107651a0d0676e449d30f2d4b6bb3cf2dac1d", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v3-gnu": { @@ -11067,8 +11067,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "2c938382641e5d73c4b148396289f5c047ddbaa9c765ec541983c212e1e02407", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "6cd111fa008f0a30345d0475e53f99148dc1aab3a39af73b7029ef4fc17c2717", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v3-musl": { @@ -11083,8 +11083,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "ca3e3795de99768fef314cd40c9b1afe52a4ee6e1409121e8b8d84f524043716", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "3f4595219aaa4b55f3169f71895bac0f63563a2e27c3653ba5008249d7eb4ed0", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v4-gnu": { @@ -11099,8 +11099,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "6c8cfad74f3ff99f275717096401021a15b92939fac1ce2ba0803d78beb8fa45", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "8976e1ef981ac4ceb186cb9bf367c279361060f468237a191f2ca2e52fd7a08b", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v4-musl": { @@ -11115,8 +11115,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.12.11%2B20250630-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "f4ec5b942fc2ddf55f26ec6a25b2ddfcda94fe095e158582a8f523258ee09cc4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "9008ed69a57d76a2e23b6119603217782a8ea3d30efebb550292348223ca87a5", "variant": "debug" }, "cpython-3.12.10-darwin-aarch64-none": { @@ -15163,8 +15163,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "89079bf9233e4305fac31fceef3e11f955ab78e3e3b0eedd8dabda39ca21558d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "7b32a1368af181ef16b2739f65849bb74d4ef1f324613ad9022d6f6fe3bb25f0", "variant": null }, "cpython-3.11.13-darwin-x86_64-none": { @@ -15179,8 +15179,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "441c0ef2ed9ee20d762c489dc3f2489d53d5a2b811af675fec2c0786273dd301", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "7e9a250b61d7c5795dfe564f12869bef52898612220dfda462da88cdcf20031c", "variant": null }, "cpython-3.11.13-linux-aarch64-gnu": { @@ -15195,8 +15195,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "7d6fb24f7d81af6a89d1a4358cc007ed513747c5beda578fb62a41e139ce0035", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "e39b0b3d68487020cbd90e8ab66af334769059b9d4200901da6a6d0af71a0033", "variant": null }, "cpython-3.11.13-linux-armv7-gnueabi": { @@ -15211,8 +15211,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "cb04fcda5b56cc274893e85f01ce536e5cc540c43352fc47f8b66280ffa1afaa", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "48c8d43677ffbdff82c286c8a3afb472eba533070f2e064c7d9df9cbb2f6decf", "variant": null }, "cpython-3.11.13-linux-armv7-gnueabihf": { @@ -15227,8 +15227,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "542cd5b270c16f841993fb63ecdb8ab3d85d6087cfef929304011949f3b6902e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "51e2914bb9846c2d88da69043371250f1fb7c1cafbc511d34794dbec5052cf98", "variant": null }, "cpython-3.11.13-linux-powerpc64le-gnu": { @@ -15243,8 +15243,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "fa361259ac188fb6ee21c6928acfbb0ae287af5c5acbb01c8c55880d72d53d22", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "be21c281b42b4fc250337111f8867d4cc7ced4f409303cc8dd5a56c6c6a820c7", "variant": null }, "cpython-3.11.13-linux-riscv64-gnu": { @@ -15259,8 +15259,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "23970dd65b2481eb8c7ddd211ba9841f7eb0927a9a3107a520a75ca97be3df3b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "ab9d02b521ca79f82f25391e84f35f0a984b949da088091f65744fcf9a83add9", "variant": null }, "cpython-3.11.13-linux-s390x-gnu": { @@ -15275,8 +15275,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "c0d3c3a102df12839c7632fb82e2210ccc0045c7b6cc3fc355e0fda30a9e6745", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "2ab4713ea357a1da9da835712ea73b40aa93fe7f55528366e10ea61d8edb4bd0", "variant": null }, "cpython-3.11.13-linux-x86_64-gnu": { @@ -15291,8 +15291,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "07c15c63bdf6d14d80a50ec3ed9a0e81d04b9cf9671decfdec3d508ab252a713", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "096a6301b7029d11db44b1fd4364a3d474a3f5c7f2cd9317521bc58abf40b990", "variant": null }, "cpython-3.11.13-linux-x86_64-musl": { @@ -15307,8 +15307,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "224de8d55c04df9cbf9555b57810dc6f87af82f0902c0883fcf6ed164c99b2cb", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "7aba64af498dfc9056405e21d5857ebf7e2dc88550de2f9b97efc5d67f100d18", "variant": null }, "cpython-3.11.13-linux-x86_64_v2-gnu": { @@ -15323,8 +15323,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "37bc8eb56569dcb033efb69ffcb3a7dcc19220c9b6c88a47e2df1a4bcbc5bce3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "806db935974b0d1c442c297fcb9e9d87b692e8f81bd4d887927449bb7eef70bf", "variant": null }, "cpython-3.11.13-linux-x86_64_v2-musl": { @@ -15339,8 +15339,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "d64c66b290eff19f4c200f07553f80e6d61afdb3d834b3ac989cda21731f2f59", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "e1dfc3b7064af3cbc68b95bdefcb88178fa9b3493f2a276b5b8e8610440ad9f3", "variant": null }, "cpython-3.11.13-linux-x86_64_v3-gnu": { @@ -15355,8 +15355,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "3099ad95267bd3154697079c1c057e84e206bdfc6cdb728f0d47c824f82db260", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "c429bb26841da0b104644c1ab11dc8a76863e107436ad06e806f6bb54f7ec126", "variant": null }, "cpython-3.11.13-linux-x86_64_v3-musl": { @@ -15371,8 +15371,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "231358a2adc7a132c07470577dfffa23353aca7384c1af6e87d0174e859df133", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "c618c57d50dd9bdd0f989d71dec9b76a742b051c1ae94111ca15515e183f87ee", "variant": null }, "cpython-3.11.13-linux-x86_64_v4-gnu": { @@ -15387,8 +15387,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "27c9bec499de6ff14909f030c2d788e41e69d5b5ee2b9deb1f78666f630d6da4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "c1cf678915eb527b824464841c2c56508836bf8595778f39a9bbb7975d59806d", "variant": null }, "cpython-3.11.13-linux-x86_64_v4-musl": { @@ -15403,8 +15403,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "0d7bffc0778796def75b663e31c138592237cd205e358540b4bafd43df109c09", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "d527c8614340ac46905969ac80e2c62327b7e987fbd448cfd74d66578ab42c67", "variant": null }, "cpython-3.11.13-windows-aarch64-none": { @@ -15419,8 +15419,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "e5d8f83e8728506ad08382c7f68328b4204dd52c3f9cb4337087b1f21b68ee2a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "3af266f887d83e4705e2ceb2eb7d770f9c74454d676e739e768097d3ff9dc148", "variant": null }, "cpython-3.11.13-windows-i686-none": { @@ -15435,8 +15435,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "228e47684fe989db07085f32b8dca766027ba34672252e9140f5bd367e0ba23f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "b38912438477ed7a7cb69aa92a5a834ffbb88d8fa5026eb626f1530adb3e00c7", "variant": null }, "cpython-3.11.13-windows-x86_64-none": { @@ -15451,8 +15451,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "abde9c8b8b1230f1a21b52efd2e41a43b5af0a754255d294e180238bf6d291e0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "edb3eb9c646997de50b27932fdf10d8853614bdbd7d651c686459fc227776c1a", "variant": null }, "cpython-3.11.13+debug-linux-aarch64-gnu": { @@ -15467,8 +15467,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "c07c910236c121818decbdfd947f5033741afc8610f347256179cbda9cee0ccf", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "5a58a85c773dcfd33b88b345fc899ab983e689fe5bf5ca6682fe62d1f3b65694", "variant": "debug" }, "cpython-3.11.13+debug-linux-armv7-gnueabi": { @@ -15483,8 +15483,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "2b15db81177c7975393cbae2a5b7989b59318084c0e8ae3798cb5982606bf5d1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "6b24708d696e86792db8214cb20d7c1bd9a0d03f926542cde7a5251a466977d8", "variant": "debug" }, "cpython-3.11.13+debug-linux-armv7-gnueabihf": { @@ -15499,8 +15499,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "b3f948f00201905cf6a7448030a40a51213b3f937f29f501114354f4cc419886", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "ddd27f58a436b31bf1a3f39d53c670ab0ed481f677b1602d5fb0a5a89f471069", "variant": "debug" }, "cpython-3.11.13+debug-linux-powerpc64le-gnu": { @@ -15515,8 +15515,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "7f2f6ceb4f242e71977eefd5e0e702c5e87d6600342d79e6dadaf2e59ed5476f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "5306fced1a898247f9e3cc897a28f05b647d8b70ed3ece80ea9f7fa525459d94", "variant": "debug" }, "cpython-3.11.13+debug-linux-riscv64-gnu": { @@ -15531,8 +15531,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "7454ccaae24cf8742ea55ac74b0b1d147be3f99bf290c7c5a02f0027a36805ac", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "fee8c8cb156c0aa84f41759b277bc27c6ce004c1bbfd03a202c8b0347ea29e50", "variant": "debug" }, "cpython-3.11.13+debug-linux-s390x-gnu": { @@ -15547,8 +15547,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "9fa9c461b14dd142e15cf95db1b0ca7310ea603fec250eb1952a8d31b64027f0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "eff457ef514ffaf954fa2bfd63fde5fc128a908e5a0d72fe8dab0e4146867f54", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64-gnu": { @@ -15563,8 +15563,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "533d8ccdb77fc43847227850361809c9bfb85223a08d689e31087df34d8260ac", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "23c7d6c58a3e9eb0055b847a72053082e1250b04c39ee0026738d0a2298d6dbb", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64-musl": { @@ -15579,8 +15579,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "0f4dfe78c30d56acb5e6df28de4058177ac188ddd6ea1f2d3a18db7fcfe7ff1b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "c51dbba70ae11f65a0399d5690a4c1fbb52d9772fc8b1467ed836247225db3af", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v2-gnu": { @@ -15595,8 +15595,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "03a7b763a2e5deec621813a9081858aad3ed0f24368ff94bf4a78eb652ffa8a0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "479bc0f7b9bae4dde42ec848e508ecd8095f28ee4e89ef1f18e95ec2e29aa19d", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v2-musl": { @@ -15611,8 +15611,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "b5e1e4c7fc8a3eff8a85f4b4dd004f7d835a8ac79a982da65054d605f106c5eb", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "a25ddc1e2588842ada52fdf4211939d5e598defd3d45702ec0d9dfa30797060a", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v3-gnu": { @@ -15627,8 +15627,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "d020b9ad0fb3afc95ccf6c9737092f8ea4e977d8f6c0bd2503cd60457113fbfa", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "d9f819fe8cbd7895c9b9d591e55ca67b500875c945cc0a1278149267d8cdd803", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v3-musl": { @@ -15643,8 +15643,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "2f04db73b5fb332f37e409ec048e09a60fd1da729893f629ae37c4991f354d0e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "dd22dd11e9bc4bbc716c1af20885c01a3d032eb1ce7bb74f9f939f6a08545ddc", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v4-gnu": { @@ -15659,8 +15659,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "8a5ea7c66a05947522d528cb47e03dd795ab108e78b0926039119e9f8341e5b0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "ab7171c7e0dcfdf7135aaed53169e71222cddc8c4133b7d51f898842bb924f0e", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v4-musl": { @@ -15675,8 +15675,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.11.13%2B20250630-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "9829333c2b776ff11ab2e4be31237dc6c36cb2e13c04c361c7c57f4d54c6af3b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "c02f0ef29ce93442ac3a61bbf3560c24d74d34b8edb46b166724ff139cde8f26", "variant": "debug" }, "cpython-3.11.12-darwin-aarch64-none": { @@ -19467,8 +19467,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "c34bfae5fe85a28c1b13c2d160268eafee18b66f25c29b139958e164897a2527", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "4ad4b0c3b60c14750fb8d0ad758829cd1a54df318dc6d83c239c279443bb854c", "variant": null }, "cpython-3.10.18-darwin-x86_64-none": { @@ -19483,8 +19483,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "f2676d7b52e94334c4564c441c0aeabda9858f29384cbaf824c8a091b574f938", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "9c9833f4f13eed050a1440204b0477d652ae76c8e749bc26021928d5c6fcba2b", "variant": null }, "cpython-3.10.18-linux-aarch64-gnu": { @@ -19499,8 +19499,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "b6f3243caa1fdcf12d62e1711e8a8255ae419632e1e98b6b1fa9809564af82f0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "6c315a5ed0b77457c494048269e71e36e0fae2a9354da0bbfc65f3d583a306fa", "variant": null }, "cpython-3.10.18-linux-armv7-gnueabi": { @@ -19515,8 +19515,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "c091409b05c986623be140e87bedea2a7f2d005dbf1564b9b529d3b3727f5608", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "c0aa7dfaef03330a1009fae6ed3696062a9c6b6a879de57643222911801f6b14", "variant": null }, "cpython-3.10.18-linux-armv7-gnueabihf": { @@ -19531,8 +19531,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "326c378407486a185c516d927652978728f056af6d62150c70678f6b590c1df9", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "6a772781facf097fb9bb00fc16b9c77680fc583dbb04ef4f935f1139f5a3a818", "variant": null }, "cpython-3.10.18-linux-powerpc64le-gnu": { @@ -19547,8 +19547,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "3ec27bd1f7f428fb12d9973d84fe268eca476097ab3ab650b4b73fd21861832a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "8bbc7cd369d3c3788ca46a66b0c9f0d227054f99b7db3966a547faa7e0ede99c", "variant": null }, "cpython-3.10.18-linux-riscv64-gnu": { @@ -19563,8 +19563,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "9da0a695abfd3a18355642055f79bc0d227e05c03d0430c308e02143fc7dbf9d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "954603b1e72f7b697812bb821b9820f2d1ab21b9fb166201c068df28577f3967", "variant": null }, "cpython-3.10.18-linux-s390x-gnu": { @@ -19579,8 +19579,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "8192b48982c0f0c0ff642bf84bcf26003812eaac5ef4853ba9b7b6e80745923a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "8d02a604f4ef13541a678b8f32b2955003f53471489460f867de3bbbd0b7b0a2", "variant": null }, "cpython-3.10.18-linux-x86_64-gnu": { @@ -19595,8 +19595,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "11b27504b26ea1977bf7b7e6ef6c9da4d648b78707fa34fe0538f08744978a4b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "780e5199279301cec595590e1a12549e615f5863e794db171897b996deb5db2b", "variant": null }, "cpython-3.10.18-linux-x86_64-musl": { @@ -19611,8 +19611,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "f5302f08043468f9c9a47fa7884393c27de66b19b3e46096984245d8f4d8be8f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "fac56a0d55b28dfada5b1b1ad12c38bca7fda14621f84d4dba599dfb697d0f6a", "variant": null }, "cpython-3.10.18-linux-x86_64_v2-gnu": { @@ -19627,8 +19627,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "e979cf0596697fc729d42c5d6d37e1d591374ac56c42d1b5db9e05750984f122", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "b47af0eb09bd0ae5d5b33e0bfd3a121dd8bf042ffe61d03d54be27629db55a78", "variant": null }, "cpython-3.10.18-linux-x86_64_v2-musl": { @@ -19643,8 +19643,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "d9d0c7e28bc0f5f2a8ce0a9f4d57f8fe263361f5fd7d8afd6f8eeecc7ce00f96", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "c59eac8b665419cc94c24807fd2654cc424f7f926a6b107a7e22a9599ba416ea", "variant": null }, "cpython-3.10.18-linux-x86_64_v3-gnu": { @@ -19659,8 +19659,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "3438a6066eb9f801f5a3153d7ee5fb0d1bf7769e44a610effd2340e846a9fd44", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "f2042831ec67633ad96f27407fee67b671bb5a589c8c8491dbb9420f58246db8", "variant": null }, "cpython-3.10.18-linux-x86_64_v3-musl": { @@ -19675,8 +19675,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "ae4eeba3037b6810eb9e96814d3980e28a5a3de5277f470100742a48391c6df7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "2205ef12cd51afe189ac152b9413788eccc5e0d8c86b78f6c2209ab8d5ead0b8", "variant": null }, "cpython-3.10.18-linux-x86_64_v4-gnu": { @@ -19691,8 +19691,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "c8cde20b14e0ef0e564d5a1cbd0c6003ae178dfea9d446cbf3ee15d75ac8a9d8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "f80c94a23c67b2cd7695fb58d3dd3bb4509cbe94bf3da9200dcc7f5c06939067", "variant": null }, "cpython-3.10.18-linux-x86_64_v4-musl": { @@ -19707,8 +19707,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "46da91101e88f68f5fa21a2aeadeeb91072cbe9d9f1caa08e34481180ec3dea3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "2356bc9f121cb555921a10155126b53ca92e471e35e93644feae37ef6adbe91d", "variant": null }, "cpython-3.10.18-windows-i686-none": { @@ -19723,8 +19723,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "faab88604275a0c869cf3fb19f63b0158bd8356d74fff15ebd05114908683fb1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "16379aad0f72dffdcedc32240bceacf8c341c8ac9c49f1634a94bef3eb34ff91", "variant": null }, "cpython-3.10.18-windows-x86_64-none": { @@ -19739,8 +19739,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "ac56903c7ae726f154b5b000f04db72ddb52775812c638cc67b5090146267502", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "1d9028a8b16a2dddfd0334a12195eb37653e4ba3dd4691059a58dc18c9c2bad5", "variant": null }, "cpython-3.10.18+debug-linux-aarch64-gnu": { @@ -19755,8 +19755,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "fcbbf8d8e274f7983a70e104bf708ff21effc28bb594314429317e73960f85ff", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "ba86c13891bba5395db087bad08e2175d4fe4f7c2592f4228c8302e89b1876ae", "variant": "debug" }, "cpython-3.10.18+debug-linux-armv7-gnueabi": { @@ -19771,8 +19771,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "400ab706d0f21b68793379040f9fa96fce9cacfff25217aaa3f37cac8d15fea5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "da75e3b55503f9cc33e1476e4933457b42c5ac0a765321a8056278056f2c6032", "variant": "debug" }, "cpython-3.10.18+debug-linux-armv7-gnueabihf": { @@ -19787,8 +19787,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "48a8a9cbf0b6a9bfd95af64af97dda167d99240cd3f66265011006f2d2a54e34", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "65c4d23b2507715b60f8157fda6651ad0490d38d3a354aa5e85c5401f7b791b5", "variant": "debug" }, "cpython-3.10.18+debug-linux-powerpc64le-gnu": { @@ -19803,8 +19803,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "8571d2b81798162c1effb57370359fe178d96f3a5a6bb15f5bd88c83caf8c6b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "1ea4b070dc8795316798e5dde4a45f9bcbd3b8907ece534e73164e9e82902817", "variant": "debug" }, "cpython-3.10.18+debug-linux-riscv64-gnu": { @@ -19819,8 +19819,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "dedf8c8bdae3b3d4d8ec028c28bb8ad12f9c78064828426f6570322eba490015", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "2b9381ee30e69b0a7722a1b0917a4be8abc9b22d3542c918c8810d3bf10144f8", "variant": "debug" }, "cpython-3.10.18+debug-linux-s390x-gnu": { @@ -19835,8 +19835,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "ffd41140c4f55b9700be4381a0ef774067486572156ec17839e38f360512cc3e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "3ea8a041ed81fbc11e2781cc6b57ef0abf2ecd874374603153213d316da19e5e", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64-gnu": { @@ -19851,8 +19851,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "ec0b1c02cb0813db9d9bfb1c41633c15c73446d74406d07df0de041dadcd2d7b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "afd58d81e22c5f96c7021c27aedb89bc3be3c40d58625035a5b7158bb464a89f", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64-musl": { @@ -19867,8 +19867,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "448962b7ac159470130fa32817f917c5db659d5adde6c300c93335fdb0915d92", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "c82f5cb37140257016a05c92a83813c8ad85f108898c6076750b4bfc8e49052d", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v2-gnu": { @@ -19883,8 +19883,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "a6d0b714ee3b0b0aae36dbb6c5a28378717c99070797ffda12ddd433a8f8588c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "fdd72ff3418b1dd74fdc5514d392e309fe615739aafeeeed80276bfa28646e93", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v2-musl": { @@ -19899,8 +19899,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "09903f2c08fccddce4f7a5d52c2956d898a70a7e47bb07825581e77ad9a77829", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "d13113baa9f5749b5f70a2e4b396393363df1bba14c4fca6d13455ab92786f16", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v3-gnu": { @@ -19915,8 +19915,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "c453e8adeed1f0dc8253bbd6ebd72d9803b88fbfd015049b3d281ce915b0dfbf", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "7ac330ff09a193ef7e4a93751dd1bc243a8a2d35debdb9f1f4c967ee98be7c9b", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v3-musl": { @@ -19931,8 +19931,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "054fdb4b6d27155371cdfa9188a93c4d58325ebb9a061ded8ad7b044cc177064", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "ffa713da073c0ac6b9d46e8c34f682c936c1ee6ecacfdaa369617d621bc5f800", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v4-gnu": { @@ -19947,8 +19947,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "0e47c951e3800a10f531ef4eb4d3307822f76674dbe863c11a48547ae51f0e27", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "d22dc14204be742df984cd74b086c5bce23ea6071bbccf66e0a4e9373fb7e1fc", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v4-musl": { @@ -19963,8 +19963,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.10.18%2B20250630-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "7f625673e2d06abac133fd9588b6492c3b6b69b6cacd1b1bb480b9476316ab74", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "f8b309b55988356eeb43b1d6daaaed44c3f2c7615abb015145e750cc81c84f13", "variant": "debug" }, "cpython-3.10.17-darwin-aarch64-none": { @@ -24907,8 +24907,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "f47a96f6bcf29ef0e803c8524b027c4c1b411d27934e6521ebe3381f9d674f7b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "901b88f69f92c93991b03315f6e9853fdf6e57796b7f46eae2578f8e6cec7f79", "variant": null }, "cpython-3.9.23-darwin-x86_64-none": { @@ -24923,8 +24923,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "04fe6e2fd53c9dc4dfbcf870174c44136f82690f7387451bf8863712314eb156", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "663403464b734f7fcb6697dc03a7bb4415f1bd7c29df8b0476d145e768b6e220", "variant": null }, "cpython-3.9.23-linux-aarch64-gnu": { @@ -24939,8 +24939,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "324e4aaba0d4749d9d9a2007a7fb2e55c6c206241c8607ade29e9a61a70f23c0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "fd037489d2d0006d9f74f20a751fd0369c61adf2c8ead32c9a572759162b3241", "variant": null }, "cpython-3.9.23-linux-armv7-gnueabi": { @@ -24955,8 +24955,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "5f88c66314c7820cb20ca3fef3c191850d0247c78d66cb366590ebb813b36b4d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "594b85658309561642361b1708aac18579817978ffdbb08f1c5f7040f9c30f28", "variant": null }, "cpython-3.9.23-linux-armv7-gnueabihf": { @@ -24971,8 +24971,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "2a4a348c42b424f5d01ff61882edde283e0135331b54349f5bc41f70282fc56f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "40eedb55eda5598dc9335728b70f7dff8b58be111b462e392cf2f8ba249c68ac", "variant": null }, "cpython-3.9.23-linux-powerpc64le-gnu": { @@ -24987,8 +24987,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "f80b1a541b74122260372e36504157a94b7b036626779b215f66e26174c16ba1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "4c294d9bd701ffaa60440e0e1871c5570c690051b7c8f1b674f8e7fc2239e8c9", "variant": null }, "cpython-3.9.23-linux-riscv64-gnu": { @@ -25003,8 +25003,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "4a57ad0c9a214e97cf18ed366f858b224e7a4c3da8519adcb0f757fb4939c64e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "41c2dd0ab80b4ddd60a22fc775d87bec1e49c533ee0b0aec757e432df17c06ea", "variant": null }, "cpython-3.9.23-linux-s390x-gnu": { @@ -25019,8 +25019,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "f76716832859664fb3bb89708fd60e55cf7bbb9fd4066a1746dda26c9baa633a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "530e5bb6e47f5e009768b96d9bed2d0c4fe21f1bc113a35571c6981922dd345f", "variant": null }, "cpython-3.9.23-linux-x86_64-gnu": { @@ -25035,8 +25035,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "a4c3137284a68bcae77e794873978703b079ed3355c86f2d9e3e1e71b2910ccb", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "b11d434321025e814b07e171e17cb183b4fe02bddbec5e036882c85fb7020b18", "variant": null }, "cpython-3.9.23-linux-x86_64-musl": { @@ -25051,8 +25051,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "a501bfae7b0c8f3fbfd4de76698face42af7f878395b22a7d567c9117f118c43", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "996f66c44d75bf681d6d5c5d2f6315b7f0fff9e9e56b628bdf0f4d865be69a31", "variant": null }, "cpython-3.9.23-linux-x86_64_v2-gnu": { @@ -25067,8 +25067,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "5a609ff46904d28acbc52cfa6b445066ec98ca690ffa8870fdd17537b72d98b0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "9355e74e4922c9ffd62fadfd0d8949a1de860c14ad16db8ec80e04552219eeaa", "variant": null }, "cpython-3.9.23-linux-x86_64_v2-musl": { @@ -25083,8 +25083,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "a041d91af09e4246f463361ec76de06080fd1cf05771f59e13a1fbbc42fb3d6f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "3548ddd479dc2ca6d108cba69c0e267a37664ff795d7ebc908836a3faacef9b1", "variant": null }, "cpython-3.9.23-linux-x86_64_v3-gnu": { @@ -25099,8 +25099,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "b7bd686ea5e00697498baead5a01abe0ceb4a0c9e6fbed5823b0c8225fb25176", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "4364cf01c55eee28f5ca918cc9c20f3130cec3d20c45156623576986729e7a9f", "variant": null }, "cpython-3.9.23-linux-x86_64_v3-musl": { @@ -25115,8 +25115,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "4daf37cac1f2fcc6696c64f0a9705f01121046f9b4a3c5424b83714a61161a5b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "ac0f0cca348f51d29c3e9201e8cb35251b0eceb0e6d29ce2b652fc2bd912bf7c", "variant": null }, "cpython-3.9.23-linux-x86_64_v4-gnu": { @@ -25131,8 +25131,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "4cf404087df0b1deb8332725583789078b868b5baa6aa5375e168d3cdb987e7f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "4622c9b7aad91c6aa9d3b251a5721b52725866defb6132e9d8b0c7b05ebdd317", "variant": null }, "cpython-3.9.23-linux-x86_64_v4-musl": { @@ -25147,8 +25147,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "f7e827cc26ce8051e77ec7a7bea2d658a1f924664e0725c21a67ab15d6f77bf8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "227544768f4214350a1051282a49e598a742bead5447ac7adfb1da488cf6b165", "variant": null }, "cpython-3.9.23-windows-i686-none": { @@ -25163,8 +25163,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "09e4db6bd06c7b26e437df662fda4d507f276ee1ba90b986e5596a823f5d980b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "0029b916ac37b330d40c6fa13f507249660f0ceaaa34415bc691e705925b6d1b", "variant": null }, "cpython-3.9.23-windows-x86_64-none": { @@ -25179,8 +25179,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "3c50ad97c45bcf0b2984479ebe896f61032222605d317ad059a481730a0ee72a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "fd864f5f2aff6727250bd9104409a458146552f88d6ae7b78427aed719506b9c", "variant": null }, "cpython-3.9.23+debug-linux-aarch64-gnu": { @@ -25195,8 +25195,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "751f68beb0dd5067bde56d9a4028b0233e2a71f84bf421e8627dc2abd068d954", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "080edc8aca719b776e62e1803948390cc75392db8a416f3ebc3fa1b6ec219c8e", "variant": "debug" }, "cpython-3.9.23+debug-linux-armv7-gnueabi": { @@ -25211,8 +25211,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "29ee738ac4d186bb431c4382a6d1cc550a0916a1ce99d58769fc5371faff8409", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "b187d469dd3c61efdac4ac4a9f9a17e01db860bef5e836251ad38e751bd2f2e9", "variant": "debug" }, "cpython-3.9.23+debug-linux-armv7-gnueabihf": { @@ -25227,8 +25227,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "afea4baf253f4bf91781c122308ca306d72fa1e76526abf2b78648772c979d2c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "d4f4ae11a45a4f7821caca519880fe79a052bb8191cbc7678965304d5efea5a3", "variant": "debug" }, "cpython-3.9.23+debug-linux-powerpc64le-gnu": { @@ -25243,8 +25243,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "0c29b50f57489b93b29a6fe00cb24fb84cd6713c065e35794d1d29d81485c01d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "2c389fc71513a2f75ef3a1a299a160d1a7d19f701f2a9635ece77454b2fddfb1", "variant": "debug" }, "cpython-3.9.23+debug-linux-riscv64-gnu": { @@ -25259,8 +25259,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "e4c17ce1f909fe47049fb3236301dde356d4ccc456f70e59e31df8f6b6e21f7d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "4c11855610bfe76f7dd003bcf3be0df9f656a41667c835df9da870b8ee91c465", "variant": "debug" }, "cpython-3.9.23+debug-linux-s390x-gnu": { @@ -25275,8 +25275,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "8f2783d56728a918568f8ec81d2b7c8b0e4060e605f42b4ae50b9f127925b54c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "90d4077a0907f4e491662b92184368b6b16f4b3623e618fdbd37ae6ceecb6813", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64-gnu": { @@ -25291,8 +25291,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "f4ef26643dfb13a80439373ce4e192f9c678ccffce8bda9455258052c29e0c85", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "5a062251e9ee9f765373cb5eae61943bc214f8363392e3cffd235ca1a751ef98", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64-musl": { @@ -25307,8 +25307,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "f74953fe519fbdbb35ce722c97521b89c915da7d630af5a82911dd352f5c8cec", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "e5e5ef74bd58d9f0994e583830811ec3be9149276a1434753b07bd19d77e9417", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v2-gnu": { @@ -25323,8 +25323,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "039ada74a3fea0f359a617a57e83ee92c43d5770175bb3c26d96ac65364f7f5c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "3fc2ad7307cd0fb5e360baea3b598ed9218313f51f83063b4d085fcf6c85c7e0", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v2-musl": { @@ -25339,8 +25339,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "64e72ce5b13bff5f7f9e44032ecf6ff70212b7384d2512ab5ed8b6edddd90e68", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "ec069be5c7b2705b885993ed8f15f3e0456f445beeee1f372b65fdd89afc7cd1", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v3-gnu": { @@ -25355,8 +25355,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "9a525d2a45db88cf7b450b2c0226204c1e5f634e3fb793f345ff6d44a9bac0fb", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "071b71c4a41da3cde092d877e36ce55f4906246c9d0755a3a349717ad4b1d7a5", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v3-musl": { @@ -25371,8 +25371,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "7370898765b907757b7220db752c91de9a1832460d665845c74c4c946bbdd4ec", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "cd3c0e2060fe94dcd346add4ee9f9053bcc35367cd2b69b46c126f4ac0681aed", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v4-gnu": { @@ -25387,8 +25387,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "c104d6d5d6943ecf53b9cc8560a32239a62c030a1170d1d4c686e87d7da60d58", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "3934b72131d7a00c5aeaec79c714315e6773bd4170596fb27265efb643444520", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v4-musl": { @@ -25403,8 +25403,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250630/cpython-3.9.23%2B20250630-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "933c85357368d1819a32bed32cdb871153ecc2a39b93565ae61e5fa0b509e2eb", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "dd0957b5c94d98f94a267e3d4e8e6acc3561f9b7970532d69d533b3eb59c72e6", "variant": "debug" }, "cpython-3.9.22-darwin-aarch64-none": { diff --git a/crates/uv-python/src/sysconfig/generated_mappings.rs b/crates/uv-python/src/sysconfig/generated_mappings.rs index 850b2c764..2611c1ac0 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)] @@ -15,7 +15,6 @@ use crate::sysconfig::replacements::{ReplacementEntry, ReplacementMode}; pub(crate) static DEFAULT_VARIABLE_UPDATES: LazyLock>> = LazyLock::new(|| { BTreeMap::from_iter([ ("BLDSHARED".to_string(), vec![ - ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/aarch64-linux-gnu-gcc".to_string() }, to: "cc".to_string() }, ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabi-gcc".to_string() }, to: "cc".to_string() }, ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabihf-gcc".to_string() }, to: "cc".to_string() }, ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/mips-linux-gnu-gcc".to_string() }, to: "cc".to_string() }, @@ -28,7 +27,6 @@ pub(crate) static DEFAULT_VARIABLE_UPDATES: LazyLock Date: Wed, 2 Jul 2025 15:11:50 -0500 Subject: [PATCH 138/349] Fix `workspace_unsatisfiable_member_dependencies` (#14429) --- crates/uv/tests/it/workspace.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/uv/tests/it/workspace.rs b/crates/uv/tests/it/workspace.rs index c52c4a2f1..631e4f6c3 100644 --- a/crates/uv/tests/it/workspace.rs +++ b/crates/uv/tests/it/workspace.rs @@ -1351,7 +1351,7 @@ fn workspace_unsatisfiable_member_dependencies() -> Result<()> { leaf.child("src/__init__.py").touch()?; // Resolving should fail. - uv_snapshot!(context.filters(), context.lock().current_dir(&workspace), @r###" + uv_snapshot!(context.filters(), context.lock().current_dir(&workspace), @r" success: false exit_code: 1 ----- stdout ----- @@ -1359,9 +1359,9 @@ fn workspace_unsatisfiable_member_dependencies() -> Result<()> { ----- stderr ----- Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] × No solution found when resolving dependencies: - ╰─▶ Because only httpx<=1.0.0b0 is available and leaf depends on httpx>9999, we can conclude that leaf's requirements are unsatisfiable. + ╰─▶ Because only httpx<=0.27.0 is available and leaf depends on httpx>9999, we can conclude that leaf's requirements are unsatisfiable. And because your workspace requires leaf, we can conclude that your workspace's requirements are unsatisfiable. - "### + " ); Ok(()) From 5f2857a1c71a8f9d7c18a8e697ae5bd93a448cdf Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 2 Jul 2025 15:18:46 -0500 Subject: [PATCH 139/349] Add linux aarch64 smoke tests (#14427) Testing https://github.com/astral-sh/uv/pull/14426 --- .github/workflows/ci.yml | 52 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 35ceb6875..d102f87cf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -470,6 +470,31 @@ jobs: ./target/debug/uvx retention-days: 1 + build-binary-linux-aarch64: + timeout-minutes: 10 + needs: determine_changes + if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }} + runs-on: github-ubuntu-24.04-aarch64-4 + name: "build binary | linux aarch64" + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - uses: rui314/setup-mold@v1 + + - uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 + + - name: "Build" + run: cargo build + + - name: "Upload binary" + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: uv-linux-aarch64-${{ github.sha }} + path: | + ./target/debug/uv + ./target/debug/uvx + retention-days: 1 + build-binary-linux-musl: timeout-minutes: 10 needs: determine_changes @@ -770,6 +795,33 @@ jobs: eval "$(./uv generate-shell-completion bash)" eval "$(./uvx --generate-shell-completion bash)" + smoke-test-linux-aarch64: + timeout-minutes: 10 + needs: build-binary-linux-aarch64 + name: "smoke test | linux aarch64" + runs-on: github-ubuntu-24.04-aarch64-2 + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: "Download binary" + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: uv-linux-aarch64-${{ github.sha }} + + - name: "Prepare binary" + run: | + chmod +x ./uv + chmod +x ./uvx + + - name: "Smoke test" + run: | + ./uv run scripts/smoke-test + + - name: "Test shell completions" + run: | + eval "$(./uv generate-shell-completion bash)" + eval "$(./uvx --generate-shell-completion bash)" + smoke-test-linux-musl: timeout-minutes: 10 needs: build-binary-linux-musl From 71b5ba13d76ad9e1b5bdf48941022fa7e89e4f3b Mon Sep 17 00:00:00 2001 From: konsti Date: Wed, 2 Jul 2025 22:37:43 +0200 Subject: [PATCH 140/349] Stabilize the uv build backend (#14311) The uv build backend has gone through some feedback cycles, we expect no more major configuration changes, and we're ready to take the next step: The uv build backend in stable. This PR stabilizes: * Using `uv_build` as build backend * The documentation of the uv build backend * The direct build fast path, where uv doesn't use PEP 517 if you're using `uv_build` in a compatible version. * `uv build --list`, which is limited to `uv_build`. It does not: * Make `uv_build` the default on `uv init` * Make `--package` the default on `uv init` --- crates/uv-build-backend/src/settings.rs | 4 ---- crates/uv-dispatch/src/lib.rs | 6 ------ crates/uv/src/commands/build_frontend.rs | 14 +------------- crates/uv/tests/it/build_backend.rs | 19 +++++-------------- docs/concepts/build-backend.md | 5 ++--- docs/reference/settings.md | 4 ---- uv.schema.json | 2 +- 7 files changed, 9 insertions(+), 45 deletions(-) diff --git a/crates/uv-build-backend/src/settings.rs b/crates/uv-build-backend/src/settings.rs index fc495c268..3b413e8e3 100644 --- a/crates/uv-build-backend/src/settings.rs +++ b/crates/uv-build-backend/src/settings.rs @@ -4,10 +4,6 @@ use uv_macros::OptionsMetadata; /// Settings for the uv build backend (`uv_build`). /// -/// !!! note -/// -/// The uv build backend is currently in preview and may change in any future release. -/// /// Note that those settings only apply when using the `uv_build` backend, other build backends /// (such as hatchling) have their own configuration. /// diff --git a/crates/uv-dispatch/src/lib.rs b/crates/uv-dispatch/src/lib.rs index 222450539..874e412e5 100644 --- a/crates/uv-dispatch/src/lib.rs +++ b/crates/uv-dispatch/src/lib.rs @@ -453,12 +453,6 @@ impl BuildContext for BuildDispatch<'_> { build_kind: BuildKind, version_id: Option<&'data str>, ) -> Result, BuildDispatchError> { - // Direct builds are a preview feature with the uv build backend. - if self.preview.is_disabled() { - trace!("Preview is disabled, not checking for direct build"); - return Ok(None); - } - let source_tree = if let Some(subdir) = subdirectory { source.join(subdir) } else { diff --git a/crates/uv/src/commands/build_frontend.rs b/crates/uv/src/commands/build_frontend.rs index bccb99fae..2cef9a406 100644 --- a/crates/uv/src/commands/build_frontend.rs +++ b/crates/uv/src/commands/build_frontend.rs @@ -187,15 +187,6 @@ async fn build_impl( printer: Printer, preview: PreviewMode, ) -> Result { - if list && preview.is_disabled() { - // We need the direct build for list and that is preview only. - writeln!( - printer.stderr(), - "The `--list` option is only available in preview mode; add the `--preview` flag to use `--list`" - )?; - return Ok(BuildResult::Failure); - } - // Extract the resolver settings. let ResolverSettings { index_locations, @@ -605,10 +596,7 @@ async fn build_package( } BuildAction::List - } else if preview.is_enabled() - && !force_pep517 - && check_direct_build(source.path(), source.path().user_display()) - { + } else if !force_pep517 && check_direct_build(source.path(), source.path().user_display()) { BuildAction::DirectBuild } else { BuildAction::Pep517 diff --git a/crates/uv/tests/it/build_backend.rs b/crates/uv/tests/it/build_backend.rs index 84b0ed8fe..b3bd337ae 100644 --- a/crates/uv/tests/it/build_backend.rs +++ b/crates/uv/tests/it/build_backend.rs @@ -224,7 +224,6 @@ fn preserve_executable_bit() -> Result<()> { .init() .arg("--build-backend") .arg("uv") - .arg("--preview") .arg(&project_dir) .assert() .success(); @@ -316,8 +315,7 @@ fn rename_module() -> Result<()> { uv_snapshot!(context .build_backend() .arg("build-wheel") - .arg(temp_dir.path()) - .env("UV_PREVIEW", "1"), @r###" + .arg(temp_dir.path()), @r###" success: true exit_code: 0 ----- stdout ----- @@ -391,8 +389,7 @@ fn rename_module_editable_build() -> Result<()> { uv_snapshot!(context .build_backend() .arg("build-editable") - .arg(temp_dir.path()) - .env("UV_PREVIEW", "1"), @r###" + .arg(temp_dir.path()), @r###" success: true exit_code: 0 ----- stdout ----- @@ -568,8 +565,7 @@ fn build_sdist_with_long_path() -> Result<()> { uv_snapshot!(context .build_backend() .arg("build-sdist") - .arg(temp_dir.path()) - .env("UV_PREVIEW", "1"), @r###" + .arg(temp_dir.path()), @r###" success: true exit_code: 0 ----- stdout ----- @@ -602,8 +598,7 @@ fn sdist_error_without_module() -> Result<()> { uv_snapshot!(context .build_backend() .arg("build-sdist") - .arg(temp_dir.path()) - .env("UV_PREVIEW", "1"), @r" + .arg(temp_dir.path()), @r" success: false exit_code: 2 ----- stdout ----- @@ -617,8 +612,7 @@ fn sdist_error_without_module() -> Result<()> { uv_snapshot!(context .build_backend() .arg("build-sdist") - .arg(temp_dir.path()) - .env("UV_PREVIEW", "1"), @r" + .arg(temp_dir.path()), @r" success: false exit_code: 2 ----- stdout ----- @@ -682,7 +676,6 @@ fn complex_namespace_packages() -> Result<()> { context .build() - .arg("--preview") .arg(project.path()) .arg("--out-dir") .arg(dist.path()) @@ -731,7 +724,6 @@ fn complex_namespace_packages() -> Result<()> { context.filters(), context .pip_install() - .arg("--preview") .arg("-e") .arg("complex-project-part_a") .arg("-e") @@ -778,7 +770,6 @@ fn symlinked_file() -> Result<()> { let project = context.temp_dir.child("project"); context .init() - .arg("--preview") .arg("--build-backend") .arg("uv") .arg(project.path()) diff --git a/docs/concepts/build-backend.md b/docs/concepts/build-backend.md index d70f00282..b4d276462 100644 --- a/docs/concepts/build-backend.md +++ b/docs/concepts/build-backend.md @@ -2,9 +2,8 @@ !!! note - The uv build backend is currently in preview and may change without warning. - - When preview mode is not enabled, uv uses [hatchling](https://pypi.org/project/hatchling/) as the default build backend. + Currently, the default build backend for `uv init` is + [hatchling](https://pypi.org/project/hatchling/). This will change to `uv` in a future version. A build backend transforms a source tree (i.e., a directory) into a source distribution or a wheel. diff --git a/docs/reference/settings.md b/docs/reference/settings.md index f681690f4..58948c80e 100644 --- a/docs/reference/settings.md +++ b/docs/reference/settings.md @@ -396,10 +396,6 @@ pydantic = { path = "/path/to/pydantic", editable = true } Settings for the uv build backend (`uv_build`). -!!! note - - The uv build backend is currently in preview and may change in any future release. - Note that those settings only apply when using the `uv_build` backend, other build backends (such as hatchling) have their own configuration. diff --git a/uv.schema.json b/uv.schema.json index 26d6ac7a3..dbc4f1168 100644 --- a/uv.schema.json +++ b/uv.schema.json @@ -644,7 +644,7 @@ ] }, "BuildBackendSettings": { - "description": "Settings for the uv build backend (`uv_build`).\n\n!!! note\n\n The uv build backend is currently in preview and may change in any future release.\n\nNote that those settings only apply when using the `uv_build` backend, other build backends\n(such as hatchling) have their own configuration.\n\nAll options that accept globs use the portable glob patterns from\n[PEP 639](https://packaging.python.org/en/latest/specifications/glob-patterns/).", + "description": "Settings for the uv build backend (`uv_build`).\n\nNote that those settings only apply when using the `uv_build` backend, other build backends\n(such as hatchling) have their own configuration.\n\nAll options that accept globs use the portable glob patterns from\n[PEP 639](https://packaging.python.org/en/latest/specifications/glob-patterns/).", "type": "object", "properties": { "data": { From 38ee6ec80096e69a244e82b9cdc3c12feceada7f Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 2 Jul 2025 16:19:52 -0500 Subject: [PATCH 141/349] Bump version to 0.7.19 (#14431) --- CHANGELOG.md | 39 +++++++++++++++++++++++++++ Cargo.lock | 6 ++--- crates/uv-build/Cargo.toml | 2 +- crates/uv-build/pyproject.toml | 2 +- crates/uv-version/Cargo.toml | 2 +- crates/uv/Cargo.toml | 2 +- docs/concepts/build-backend.md | 12 ++++----- 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/pre-commit.md | 10 +++---- pyproject.toml | 2 +- 13 files changed, 68 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2605248ba..c1c163331 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,43 @@ +## 0.7.19 + +The **[uv build backend](https://docs.astral.sh/uv/concepts/build-backend/) is now stable**, and considered ready for production use. + +The uv build backend is a great choice for pure Python projects. It has reasonable defaults, with the goal of requiring zero configuration for most users, but provides flexible configuration to accommodate most Python project structures. It integrates tightly with uv, to improve messaging and user experience. It validates project metadata and structures, preventing common mistakes. And, finally, it's very fast — `uv sync` on a new project (from `uv init`) is 10-30x faster than with other build backends. + +To use uv as a build backend in an existing project, add `uv_build` to the `[build-system]` section in your `pyproject.toml`: + +```toml +[build-system] +requires = ["uv_build>=0.7.19,<0.8.0"] +build-backend = "uv_build" +``` + +In a future release, it will replace `hatchling` as the default in `uv init`. As before, uv will remain compatible with all standards-compliant build backends. + +### Python + +- Add PGO distributions of Python for aarch64 Linux, which are more optimized for better performance + +See the [python-build-standalone release](https://github.com/astral-sh/python-build-standalone/releases/tag/20250702) for more details. + +### Enhancements + +- Ignore Python patch version for `--universal` pip compile ([#14405](https://github.com/astral-sh/uv/pull/14405)) +- Update the tilde version specifier warning to include more context ([#14335](https://github.com/astral-sh/uv/pull/14335)) +- Clarify behavior and hint on tool install when no executables are available ([#14423](https://github.com/astral-sh/uv/pull/14423)) + +### Bug fixes + +- Make project and interpreter lock acquisition non-fatal ([#14404](https://github.com/astral-sh/uv/pull/14404)) +- Includes `sys.prefix` in cached environment keys to avoid `--with` collisions across projects ([#14403](https://github.com/astral-sh/uv/pull/14403)) + +### Documentation + +- Add a migration guide from pip to uv projects ([#12382](https://github.com/astral-sh/uv/pull/12382)) + ## 0.7.18 ### Python @@ -12,6 +49,8 @@ These are not downloaded by default, since x86-64 Python has broader ecosystem support on Windows. However, they can be requested with `cpython--windows-aarch64`. +See the [python-build-standalone release](https://github.com/astral-sh/python-build-standalone/releases/tag/20250630) for more details. + ### Enhancements - Keep track of retries in `ManagedPythonDownload::fetch_with_retry` ([#14378](https://github.com/astral-sh/uv/pull/14378)) diff --git a/Cargo.lock b/Cargo.lock index 881cd8423..f9e51c47a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4603,7 +4603,7 @@ dependencies = [ [[package]] name = "uv" -version = "0.7.18" +version = "0.7.19" dependencies = [ "anstream", "anyhow", @@ -4767,7 +4767,7 @@ dependencies = [ [[package]] name = "uv-build" -version = "0.7.18" +version = "0.7.19" dependencies = [ "anyhow", "uv-build-backend", @@ -5957,7 +5957,7 @@ dependencies = [ [[package]] name = "uv-version" -version = "0.7.18" +version = "0.7.19" [[package]] name = "uv-virtualenv" diff --git a/crates/uv-build/Cargo.toml b/crates/uv-build/Cargo.toml index 83c37cd15..34dfa996a 100644 --- a/crates/uv-build/Cargo.toml +++ b/crates/uv-build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-build" -version = "0.7.18" +version = "0.7.19" edition.workspace = true rust-version.workspace = true homepage.workspace = true diff --git a/crates/uv-build/pyproject.toml b/crates/uv-build/pyproject.toml index f48e0ddff..660e95c95 100644 --- a/crates/uv-build/pyproject.toml +++ b/crates/uv-build/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uv-build" -version = "0.7.18" +version = "0.7.19" description = "The uv build backend" authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }] requires-python = ">=3.8" diff --git a/crates/uv-version/Cargo.toml b/crates/uv-version/Cargo.toml index c17f17695..9b9ccd9bd 100644 --- a/crates/uv-version/Cargo.toml +++ b/crates/uv-version/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-version" -version = "0.7.18" +version = "0.7.19" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml index 1b8d878ee..0a352d2b1 100644 --- a/crates/uv/Cargo.toml +++ b/crates/uv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv" -version = "0.7.18" +version = "0.7.19" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } diff --git a/docs/concepts/build-backend.md b/docs/concepts/build-backend.md index b4d276462..e68069ddb 100644 --- a/docs/concepts/build-backend.md +++ b/docs/concepts/build-backend.md @@ -13,11 +13,11 @@ performance and user experience. ## Choosing a build backend -The uv build backend is a good choice for most Python projects that are using uv. It has reasonable -defaults, with the goal of requiring zero configuration for most users, but provides flexible -configuration that allows most Python project structures. It integrates tightly with uv, to improve -messaging and user experience. It validates project metadata and structures, preventing common -mistakes. And, finally, it's very fast. +The uv build backend is a great choice for most Python projects. It has reasonable defaults, with +the goal of requiring zero configuration for most users, but provides flexible configuration to +accommodate most Python project structures. It integrates tightly with uv, to improve messaging and +user experience. It validates project metadata and structures, preventing common mistakes. And, +finally, it's very fast. The uv build backend currently **only supports pure Python code**. An alternative backend is required to build a @@ -36,7 +36,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.7.18,<0.8.0"] +requires = ["uv_build>=0.7.19,<0.8.0"] build-backend = "uv_build" ``` diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 227b4df43..a507a3ade 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.7.18/install.sh | sh + $ curl -LsSf https://astral.sh/uv/0.7.19/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.7.18/install.ps1 | iex" + PS> powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/0.7.19/install.ps1 | iex" ``` !!! tip diff --git a/docs/guides/integration/aws-lambda.md b/docs/guides/integration/aws-lambda.md index db45f1016..233969420 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.7.18 AS uv +FROM ghcr.io/astral-sh/uv:0.7.19 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.7.18 AS uv +FROM ghcr.io/astral-sh/uv:0.7.19 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 27416a0fa..bfbae2a7b 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.7.18` +- `ghcr.io/astral-sh/uv:{major}.{minor}.{patch}`, e.g., `ghcr.io/astral-sh/uv:0.7.19` - `ghcr.io/astral-sh/uv:{major}.{minor}`, e.g., `ghcr.io/astral-sh/uv:0.7` (the latest patch version) @@ -75,7 +75,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.7.18-alpine`. +`ghcr.io/astral-sh/uv:{major}.{minor}-{base}`, e.g., `ghcr.io/astral-sh/uv:0.7.19-alpine`. For more details, see the [GitHub Container](https://github.com/astral-sh/uv/pkgs/container/uv) page. @@ -113,7 +113,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.7.18 /uv /uvx /bin/ +COPY --from=ghcr.io/astral-sh/uv:0.7.19 /uv /uvx /bin/ ``` !!! tip @@ -131,7 +131,7 @@ COPY --from=ghcr.io/astral-sh/uv:0.7.18 /uv /uvx /bin/ Or, with the installer: ```dockerfile -ADD https://astral.sh/uv/0.7.18/install.sh /uv-installer.sh +ADD https://astral.sh/uv/0.7.19/install.sh /uv-installer.sh ``` ### Installing a project @@ -557,5 +557,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.7.18`, or (even better) the specific image digest, + version tag, e.g., `ghcr.io/astral-sh/uv:0.7.19`, 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 60a18b8b4..de8853d05 100644 --- a/docs/guides/integration/github.md +++ b/docs/guides/integration/github.md @@ -47,7 +47,7 @@ jobs: uses: astral-sh/setup-uv@v5 with: # Install a specific version of uv. - version: "0.7.18" + version: "0.7.19" ``` ## Setting up Python diff --git a/docs/guides/integration/pre-commit.md b/docs/guides/integration/pre-commit.md index dbbc88462..b2f637a42 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.7.18 + rev: 0.7.19 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.7.18 + rev: 0.7.19 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.7.18 + rev: 0.7.19 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.7.18 + rev: 0.7.19 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.7.18 + rev: 0.7.19 hooks: # Compile requirements - id: pip-compile diff --git a/pyproject.toml b/pyproject.toml index 7e73fa84c..5d4261360 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "uv" -version = "0.7.18" +version = "0.7.19" 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" From c3f13d2505f719dd8b145a42215dd62f72e67b8e Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 2 Jul 2025 20:02:17 -0500 Subject: [PATCH 142/349] Finish incomplete sentence in pip migration guide (#14432) Fixes https://github.com/astral-sh/uv/pull/12382#discussion_r2181237729 --- docs/guides/migration/pip-to-project.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/migration/pip-to-project.md b/docs/guides/migration/pip-to-project.md index a2be9ccd3..1356cb5d7 100644 --- a/docs/guides/migration/pip-to-project.md +++ b/docs/guides/migration/pip-to-project.md @@ -324,7 +324,7 @@ so multiple files are not needed to lock development dependencies. The uv lockfile is always [universal](../../concepts/resolution.md#universal-resolution), so multiple files are not needed to [lock dependencies for each platform](#platform-specific-dependencies). This ensures that all -developers +developers are using consistent, locked versions of dependencies regardless of their machine. The uv lockfile also supports concepts like [pinning packages to specific indexes](../../concepts/indexes.md#pinning-a-package-to-an-index), From 85c0fc963b1d4f6c8130c4b4446d15b8f6ac8ac4 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 3 Jul 2025 07:29:59 -0500 Subject: [PATCH 143/349] Fix forced resolution with all extras in `uv version` (#14434) Closes https://github.com/astral-sh/uv/issues/14433 Same as https://github.com/astral-sh/uv/pull/13380 --- crates/uv/src/commands/project/version.rs | 2 +- crates/uv/tests/it/version.rs | 54 +++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/crates/uv/src/commands/project/version.rs b/crates/uv/src/commands/project/version.rs index 102d91af6..bc79f8eb9 100644 --- a/crates/uv/src/commands/project/version.rs +++ b/crates/uv/src/commands/project/version.rs @@ -385,7 +385,7 @@ async fn lock_and_sync( let default_groups = default_dependency_groups(project.pyproject_toml())?; let default_extras = DefaultExtras::default(); let groups = DependencyGroups::default().with_defaults(default_groups); - let extras = ExtrasSpecification::from_all_extras().with_defaults(default_extras); + let extras = ExtrasSpecification::default().with_defaults(default_extras); let install_options = InstallOptions::default(); // Convert to an `AddTarget` by attaching the appropriate interpreter or environment. diff --git a/crates/uv/tests/it/version.rs b/crates/uv/tests/it/version.rs index 152cedaec..97d30f4f4 100644 --- a/crates/uv/tests/it/version.rs +++ b/crates/uv/tests/it/version.rs @@ -1958,3 +1958,57 @@ fn version_set_evil_constraints() -> Result<()> { Ok(()) } + +/// Bump the version with conflicting extras, to ensure we're activating the correct subset of +/// extras during the resolve. +#[test] +fn version_extras() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "1.10.31" +requires-python = ">=3.12" + +[project.optional-dependencies] +foo = ["requests"] +bar = ["httpx"] +baz = ["flask"] + +[tool.uv] +conflicts = [[{"extra" = "foo"}, {"extra" = "bar"}]] +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("patch"), @r" + success: true + exit_code: 0 + ----- stdout ----- + myproject 1.10.31 => 1.10.32 + + ----- stderr ----- + Resolved 19 packages in [TIME] + Audited in [TIME] + "); + + // Sync an extra, we should not remove it. + context.sync().arg("--extra").arg("foo").assert().success(); + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("patch"), @r" + success: true + exit_code: 0 + ----- stdout ----- + myproject 1.10.32 => 1.10.33 + + ----- stderr ----- + Resolved 19 packages in [TIME] + Audited in [TIME] + "); + + Ok(()) +} From 39cdfe9981655e6069e0072c0eacf2d4c877f622 Mon Sep 17 00:00:00 2001 From: konsti Date: Thu, 3 Jul 2025 15:34:44 +0200 Subject: [PATCH 144/349] Add a test for `--force-pep517` (#14310) There was previously a gap in the test coverage in ensuring that `--force-pep517` was respected. --- crates/uv/tests/it/build.rs | 65 ++++++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/crates/uv/tests/it/build.rs b/crates/uv/tests/it/build.rs index 706c1a681..0d4418d1a 100644 --- a/crates/uv/tests/it/build.rs +++ b/crates/uv/tests/it/build.rs @@ -133,7 +133,7 @@ fn build_sdist() -> Result<()> { let filters = context .filters() .into_iter() - .chain([(r"exit code: 1", "exit status: 1"), (r"\\\.", "")]) + .chain([(r"exit code: ", "exit status: "), (r"\\\.", "")]) .collect::>(); let project = context.temp_dir.child("project"); @@ -1981,3 +1981,66 @@ fn build_with_nonnormalized_name() -> Result<()> { Ok(()) } + +/// Check that `--force-pep517` is respected. +/// +/// The error messages for a broken project are different for direct builds vs. PEP 517. +#[test] +fn force_pep517() -> Result<()> { + // We need to use a real `uv_build` package. + let context = TestContext::new("3.12").with_exclude_newer("2025-05-27T00:00:00Z"); + + context + .init() + .arg("--build-backend") + .arg("uv") + .assert() + .success(); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str(indoc! {r#" + [project] + name = "project" + version = "1.0.0" + + [tool.uv.build-backend] + module-name = "does_not_exist" + + [build-system] + requires = ["uv_build>=0.5.15,<10000"] + build-backend = "uv_build" + "#})?; + + uv_snapshot!(context.filters(), context.build().env("RUST_BACKTRACE", "0"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Building source distribution (uv build backend)... + × Failed to build `[TEMP_DIR]/` + ╰─▶ Expected a Python module at: `src/does_not_exist/__init__.py` + "); + + let filters = context + .filters() + .into_iter() + .chain([(r"exit code: 1", "exit status: 1")]) + .collect::>(); + + uv_snapshot!(filters, context.build().arg("--force-pep517").env("RUST_BACKTRACE", "0"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Building source distribution... + Error: Missing module directory for `does_not_exist` in `src`. Found: `temp` + × Failed to build `[TEMP_DIR]/` + ├─▶ The build backend returned an error + ╰─▶ Call to `uv_build.build_sdist` failed (exit status: 1) + hint: This usually indicates a problem with the package or the build environment. + "); + + Ok(()) +} From a1cda6213c6d4626cac3d6b45bfedc5ba3d2aeda Mon Sep 17 00:00:00 2001 From: konsti Date: Thu, 3 Jul 2025 15:50:40 +0200 Subject: [PATCH 145/349] Make "exit code" -> "exit status" a default filter (#14441) Remove some test boilerplate. Revival of https://github.com/astral-sh/uv/pull/14439 with main as base. --- crates/uv/tests/it/build.rs | 31 +++++++++++------------------- crates/uv/tests/it/common/mod.rs | 2 ++ crates/uv/tests/it/edit.rs | 10 ++-------- crates/uv/tests/it/lock.rs | 20 ++++--------------- crates/uv/tests/it/pip_compile.rs | 5 +---- crates/uv/tests/it/pip_install.rs | 30 ++++++----------------------- crates/uv/tests/it/sync.rs | 27 ++++++-------------------- crates/uv/tests/it/tool_install.rs | 1 - 8 files changed, 32 insertions(+), 94 deletions(-) diff --git a/crates/uv/tests/it/build.rs b/crates/uv/tests/it/build.rs index 0d4418d1a..3d08a90d4 100644 --- a/crates/uv/tests/it/build.rs +++ b/crates/uv/tests/it/build.rs @@ -15,7 +15,7 @@ fn build_basic() -> Result<()> { let filters = context .filters() .into_iter() - .chain([(r"exit code: 1", "exit status: 1"), (r"\\\.", "")]) + .chain([(r"\\\.", "")]) .collect::>(); let project = context.temp_dir.child("project"); @@ -133,7 +133,7 @@ fn build_sdist() -> Result<()> { let filters = context .filters() .into_iter() - .chain([(r"exit code: ", "exit status: "), (r"\\\.", "")]) + .chain([(r"\\\.", "")]) .collect::>(); let project = context.temp_dir.child("project"); @@ -189,7 +189,7 @@ fn build_wheel() -> Result<()> { let filters = context .filters() .into_iter() - .chain([(r"exit code: 1", "exit status: 1"), (r"\\\.", "")]) + .chain([(r"\\\.", "")]) .collect::>(); let project = context.temp_dir.child("project"); @@ -245,7 +245,7 @@ fn build_sdist_wheel() -> Result<()> { let filters = context .filters() .into_iter() - .chain([(r"exit code: 1", "exit status: 1"), (r"\\\.", "")]) + .chain([(r"\\\.", "")]) .collect::>(); let project = context.temp_dir.child("project"); @@ -303,7 +303,7 @@ fn build_wheel_from_sdist() -> Result<()> { let filters = context .filters() .into_iter() - .chain([(r"exit code: 1", "exit status: 1"), (r"\\\.", "")]) + .chain([(r"\\\.", "")]) .collect::>(); let project = context.temp_dir.child("project"); @@ -412,7 +412,7 @@ fn build_fail() -> Result<()> { let filters = context .filters() .into_iter() - .chain([(r"exit code: 1", "exit status: 1"), (r"\\\.", "")]) + .chain([(r"\\\.", "")]) .collect::>(); let project = context.temp_dir.child("project"); @@ -488,7 +488,6 @@ fn build_workspace() -> Result<()> { .filters() .into_iter() .chain([ - (r"exit code: 1", "exit status: 1"), (r"\\\.", ""), (r"\[project\]", "[PKG]"), (r"\[member\]", "[PKG]"), @@ -694,7 +693,6 @@ fn build_all_with_failure() -> Result<()> { .filters() .into_iter() .chain([ - (r"exit code: 1", "exit status: 1"), (r"\\\.", ""), (r"\[project\]", "[PKG]"), (r"\[member-\w+\]", "[PKG]"), @@ -840,7 +838,7 @@ fn build_constraints() -> Result<()> { let filters = context .filters() .into_iter() - .chain([(r"exit code: 1", "exit status: 1"), (r"\\\.", "")]) + .chain([(r"\\\.", "")]) .collect::>(); let project = context.temp_dir.child("project"); @@ -901,7 +899,7 @@ fn build_sha() -> Result<()> { let filters = context .filters() .into_iter() - .chain([(r"exit code: 1", "exit status: 1"), (r"\\\.", "")]) + .chain([(r"\\\.", "")]) .collect::>(); let project = context.temp_dir.child("project"); @@ -1187,7 +1185,7 @@ fn build_tool_uv_sources() -> Result<()> { let filters = context .filters() .into_iter() - .chain([(r"exit code: 1", "exit status: 1"), (r"\\\.", "")]) + .chain([(r"\\\.", "")]) .collect::>(); let build = context.temp_dir.child("backend"); @@ -1337,7 +1335,6 @@ fn build_non_package() -> Result<()> { .filters() .into_iter() .chain([ - (r"exit code: 1", "exit status: 1"), (r"\\\.", ""), (r"\[project\]", "[PKG]"), (r"\[member\]", "[PKG]"), @@ -1930,7 +1927,7 @@ fn build_with_nonnormalized_name() -> Result<()> { let filters = context .filters() .into_iter() - .chain([(r"exit code: 1", "exit status: 1"), (r"\\\.", "")]) + .chain([(r"\\\.", "")]) .collect::>(); let project = context.temp_dir.child("project"); @@ -2022,13 +2019,7 @@ fn force_pep517() -> Result<()> { ╰─▶ Expected a Python module at: `src/does_not_exist/__init__.py` "); - let filters = context - .filters() - .into_iter() - .chain([(r"exit code: 1", "exit status: 1")]) - .collect::>(); - - uv_snapshot!(filters, context.build().arg("--force-pep517").env("RUST_BACKTRACE", "0"), @r" + uv_snapshot!(context.filters(), context.build().arg("--force-pep517").env("RUST_BACKTRACE", "0"), @r" success: false exit_code: 2 ----- stdout ----- diff --git a/crates/uv/tests/it/common/mod.rs b/crates/uv/tests/it/common/mod.rs index 4c411899c..7b13c49b5 100644 --- a/crates/uv/tests/it/common/mod.rs +++ b/crates/uv/tests/it/common/mod.rs @@ -517,6 +517,8 @@ impl TestContext { if cfg!(windows) { filters.push((" --link-mode ".to_string(), String::new())); filters.push((r#"link-mode = "copy"\n"#.to_string(), String::new())); + // Unix uses "exit status", Windows uses "exit code" + filters.push((r"exit code: ".to_string(), "exit status: ".to_string())); } filters.extend( diff --git a/crates/uv/tests/it/edit.rs b/crates/uv/tests/it/edit.rs index ee0bf04ee..0ae2a07a6 100644 --- a/crates/uv/tests/it/edit.rs +++ b/crates/uv/tests/it/edit.rs @@ -7246,10 +7246,7 @@ fn fail_to_add_revert_project() -> Result<()> { .child("setup.py") .write_str("1/0")?; - let filters = std::iter::once((r"exit code: 1", "exit status: 1")) - .chain(context.filters()) - .collect::>(); - uv_snapshot!(filters, context.add().arg("./child"), @r#" + uv_snapshot!(context.filters(), context.add().arg("./child"), @r#" success: false exit_code: 1 ----- stdout ----- @@ -7351,10 +7348,7 @@ fn fail_to_edit_revert_project() -> Result<()> { .child("setup.py") .write_str("1/0")?; - let filters = std::iter::once((r"exit code: 1", "exit status: 1")) - .chain(context.filters()) - .collect::>(); - uv_snapshot!(filters, context.add().arg("./child"), @r#" + uv_snapshot!(context.filters(), context.add().arg("./child"), @r#" success: false exit_code: 1 ----- stdout ----- diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index 82754381b..5851022b8 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -23617,10 +23617,7 @@ fn lock_derivation_chain_prod() -> Result<()> { let filters = context .filters() .into_iter() - .chain([ - (r"exit code: 1", "exit status: 1"), - (r"/.*/src", "/[TMP]/src"), - ]) + .chain([(r"/.*/src", "/[TMP]/src")]) .collect::>(); uv_snapshot!(filters, context.lock(), @r###" @@ -23677,10 +23674,7 @@ fn lock_derivation_chain_extra() -> Result<()> { let filters = context .filters() .into_iter() - .chain([ - (r"exit code: 1", "exit status: 1"), - (r"/.*/src", "/[TMP]/src"), - ]) + .chain([(r"/.*/src", "/[TMP]/src")]) .collect::>(); uv_snapshot!(filters, context.lock(), @r###" @@ -23739,10 +23733,7 @@ fn lock_derivation_chain_group() -> Result<()> { let filters = context .filters() .into_iter() - .chain([ - (r"exit code: 1", "exit status: 1"), - (r"/.*/src", "/[TMP]/src"), - ]) + .chain([(r"/.*/src", "/[TMP]/src")]) .collect::>(); uv_snapshot!(filters, context.lock(), @r###" @@ -23812,10 +23803,7 @@ fn lock_derivation_chain_extended() -> Result<()> { let filters = context .filters() .into_iter() - .chain([ - (r"exit code: 1", "exit status: 1"), - (r"/.*/src", "/[TMP]/src"), - ]) + .chain([(r"/.*/src", "/[TMP]/src")]) .collect::>(); uv_snapshot!(filters, context.lock(), @r###" diff --git a/crates/uv/tests/it/pip_compile.rs b/crates/uv/tests/it/pip_compile.rs index ff135d959..b99be1296 100644 --- a/crates/uv/tests/it/pip_compile.rs +++ b/crates/uv/tests/it/pip_compile.rs @@ -14679,10 +14679,7 @@ fn compile_derivation_chain() -> Result<()> { let filters = context .filters() .into_iter() - .chain([ - (r"exit code: 1", "exit status: 1"), - (r"/.*/src", "/[TMP]/src"), - ]) + .chain([(r"/.*/src", "/[TMP]/src")]) .collect::>(); uv_snapshot!(filters, context.pip_compile().arg("pyproject.toml"), @r###" diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index 76b108a81..a33e08d90 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -342,10 +342,7 @@ dependencies = ["flask==1.0.x"] let requirements_txt = context.temp_dir.child("requirements.txt"); requirements_txt.write_str("./path_dep")?; - let filters = std::iter::once((r"exit code: 1", "exit status: 1")) - .chain(context.filters()) - .collect::>(); - uv_snapshot!(filters, context.pip_install() + uv_snapshot!(context.filters(), context.pip_install() .arg("-r") .arg("requirements.txt"), @r###" success: false @@ -4930,10 +4927,7 @@ fn no_build_isolation() -> Result<()> { requirements_in.write_str("anyio @ https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz")?; // We expect the build to fail, because `setuptools` is not installed. - let filters = std::iter::once((r"exit code: 1", "exit status: 1")) - .chain(context.filters()) - .collect::>(); - uv_snapshot!(filters, context.pip_install() + uv_snapshot!(context.filters(), context.pip_install() .arg("-r") .arg("requirements.in") .arg("--no-build-isolation"), @r###" @@ -5001,10 +4995,7 @@ fn respect_no_build_isolation_env_var() -> Result<()> { requirements_in.write_str("anyio @ https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz")?; // We expect the build to fail, because `setuptools` is not installed. - let filters = std::iter::once((r"exit code: 1", "exit status: 1")) - .chain(context.filters()) - .collect::>(); - uv_snapshot!(filters, context.pip_install() + uv_snapshot!(context.filters(), context.pip_install() .arg("-r") .arg("requirements.in") .env(EnvVars::UV_NO_BUILD_ISOLATION, "yes"), @r###" @@ -8601,10 +8592,7 @@ fn install_build_isolation_package() -> Result<()> { )?; // Running `uv pip install` should fail for iniconfig. - let filters = std::iter::once((r"exit code: 1", "exit status: 1")) - .chain(context.filters()) - .collect::>(); - uv_snapshot!(filters, context.pip_install() + uv_snapshot!(context.filters(), context.pip_install() .arg("--no-build-isolation-package") .arg("iniconfig") .arg(package.path()), @r###" @@ -8931,10 +8919,7 @@ fn missing_top_level() { fn sklearn() { let context = TestContext::new("3.12"); - let filters = std::iter::once((r"exit code: 1", "exit status: 1")) - .chain(context.filters()) - .collect::>(); - uv_snapshot!(filters, context.pip_install().arg("sklearn"), @r###" + uv_snapshot!(context.filters(), context.pip_install().arg("sklearn"), @r###" success: false exit_code: 1 ----- stdout ----- @@ -8984,10 +8969,7 @@ fn resolve_derivation_chain() -> Result<()> { let filters = context .filters() .into_iter() - .chain([ - (r"exit code: 1", "exit status: 1"), - (r"/.*/src", "/[TMP]/src"), - ]) + .chain([(r"/.*/src", "/[TMP]/src")]) .collect::>(); uv_snapshot!(filters, context.pip_install() diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index a97775d9f..f9a71fe82 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -1122,10 +1122,7 @@ fn sync_build_isolation_package() -> Result<()> { )?; // Running `uv sync` should fail for iniconfig. - let filters = std::iter::once((r"exit code: 1", "exit status: 1")) - .chain(context.filters()) - .collect::>(); - uv_snapshot!(filters, context.sync().arg("--no-build-isolation-package").arg("source-distribution"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-build-isolation-package").arg("source-distribution"), @r###" success: false exit_code: 1 ----- stdout ----- @@ -1215,10 +1212,7 @@ fn sync_build_isolation_extra() -> Result<()> { )?; // Running `uv sync` should fail for the `compile` extra. - let filters = std::iter::once((r"exit code: 1", "exit status: 1")) - .chain(context.filters()) - .collect::>(); - uv_snapshot!(&filters, context.sync().arg("--extra").arg("compile"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("compile"), @r###" success: false exit_code: 1 ----- stdout ----- @@ -1239,7 +1233,7 @@ fn sync_build_isolation_extra() -> Result<()> { "###); // Running `uv sync` with `--all-extras` should also fail. - uv_snapshot!(&filters, context.sync().arg("--all-extras"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-extras"), @r###" success: false exit_code: 1 ----- stdout ----- @@ -6985,10 +6979,7 @@ fn sync_derivation_chain() -> Result<()> { let filters = context .filters() .into_iter() - .chain([ - (r"exit code: 1", "exit status: 1"), - (r"/.*/src", "/[TMP]/src"), - ]) + .chain([(r"/.*/src", "/[TMP]/src")]) .collect::>(); uv_snapshot!(filters, context.sync(), @r###" @@ -7051,10 +7042,7 @@ fn sync_derivation_chain_extra() -> Result<()> { let filters = context .filters() .into_iter() - .chain([ - (r"exit code: 1", "exit status: 1"), - (r"/.*/src", "/[TMP]/src"), - ]) + .chain([(r"/.*/src", "/[TMP]/src")]) .collect::>(); uv_snapshot!(filters, context.sync().arg("--extra").arg("wsgi"), @r###" @@ -7119,10 +7107,7 @@ fn sync_derivation_chain_group() -> Result<()> { let filters = context .filters() .into_iter() - .chain([ - (r"exit code: 1", "exit status: 1"), - (r"/.*/src", "/[TMP]/src"), - ]) + .chain([(r"/.*/src", "/[TMP]/src")]) .collect::>(); uv_snapshot!(filters, context.sync().arg("--group").arg("wsgi"), @r###" diff --git a/crates/uv/tests/it/tool_install.rs b/crates/uv/tests/it/tool_install.rs index deb935f9b..6a2d38db8 100644 --- a/crates/uv/tests/it/tool_install.rs +++ b/crates/uv/tests/it/tool_install.rs @@ -1682,7 +1682,6 @@ fn tool_install_uninstallable() { .filters() .into_iter() .chain([ - (r"exit code: 1", "exit status: 1"), (r"bdist\.[^/\\\s]+(-[^/\\\s]+)?", "bdist.linux-x86_64"), (r"\\\.", ""), (r"#+", "#"), From 8afbd86f03b21cd8a77663da166b66aa0be62acf Mon Sep 17 00:00:00 2001 From: Simon Sure Date: Thu, 3 Jul 2025 17:43:59 +0100 Subject: [PATCH 146/349] make `ErrorTree` for `NoSolutionError` externally accessible (#14444) Hey, are you okay with exposing the `ErrorTree` for library consumers? We have a use case that needs more information on conflicts. We need the tree-structure of the conflict and be able to traverse it in particular. Signed-off-by: Simon Sure --- crates/uv-resolver/src/error.rs | 7 ++++++- crates/uv-resolver/src/lib.rs | 4 ++-- crates/uv-resolver/src/pubgrub/mod.rs | 2 +- crates/uv-resolver/src/pubgrub/package.rs | 6 +++--- crates/uv-resolver/src/resolver/availability.rs | 6 +++--- 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/crates/uv-resolver/src/error.rs b/crates/uv-resolver/src/error.rs index e327e8562..72f9217e3 100644 --- a/crates/uv-resolver/src/error.rs +++ b/crates/uv-resolver/src/error.rs @@ -156,7 +156,7 @@ impl From> for ResolveError { } } -pub(crate) type ErrorTree = DerivationTree, UnavailableReason>; +pub type ErrorTree = DerivationTree, UnavailableReason>; /// A wrapper around [`pubgrub::error::NoSolutionError`] that displays a resolution failure report. pub struct NoSolutionError { @@ -367,6 +367,11 @@ impl NoSolutionError { NoSolutionHeader::new(self.env.clone()) } + /// Get the conflict derivation tree for external analysis + pub fn derivation_tree(&self) -> &ErrorTree { + &self.error + } + /// Hint at limiting the resolver environment if universal resolution failed for a target /// that is not the current platform or not the current Python version. fn hint_disjoint_targets(&self, f: &mut Formatter) -> std::fmt::Result { diff --git a/crates/uv-resolver/src/lib.rs b/crates/uv-resolver/src/lib.rs index 48904660d..e91df3a7e 100644 --- a/crates/uv-resolver/src/lib.rs +++ b/crates/uv-resolver/src/lib.rs @@ -1,5 +1,5 @@ pub use dependency_mode::DependencyMode; -pub use error::{NoSolutionError, NoSolutionHeader, ResolveError, SentinelRange}; +pub use error::{ErrorTree, NoSolutionError, NoSolutionHeader, ResolveError, SentinelRange}; pub use exclude_newer::ExcludeNewer; pub use exclusions::Exclusions; pub use flat_index::{FlatDistributions, FlatIndex}; @@ -54,7 +54,7 @@ mod options; mod pins; mod preferences; mod prerelease; -mod pubgrub; +pub mod pubgrub; mod python_requirement; mod redirect; mod resolution; diff --git a/crates/uv-resolver/src/pubgrub/mod.rs b/crates/uv-resolver/src/pubgrub/mod.rs index f4802a2ca..bd58fbc72 100644 --- a/crates/uv-resolver/src/pubgrub/mod.rs +++ b/crates/uv-resolver/src/pubgrub/mod.rs @@ -1,6 +1,6 @@ pub(crate) use crate::pubgrub::dependencies::PubGrubDependency; pub(crate) use crate::pubgrub::distribution::PubGrubDistribution; -pub(crate) use crate::pubgrub::package::{PubGrubPackage, PubGrubPackageInner, PubGrubPython}; +pub use crate::pubgrub::package::{PubGrubPackage, PubGrubPackageInner, PubGrubPython}; pub(crate) use crate::pubgrub::priority::{PubGrubPriorities, PubGrubPriority, PubGrubTiebreaker}; pub(crate) use crate::pubgrub::report::PubGrubReportFormatter; diff --git a/crates/uv-resolver/src/pubgrub/package.rs b/crates/uv-resolver/src/pubgrub/package.rs index 8c40f8080..2e67a715a 100644 --- a/crates/uv-resolver/src/pubgrub/package.rs +++ b/crates/uv-resolver/src/pubgrub/package.rs @@ -9,7 +9,7 @@ use crate::python_requirement::PythonRequirement; /// [`Arc`] wrapper around [`PubGrubPackageInner`] to make cloning (inside PubGrub) cheap. #[derive(Debug, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)] -pub(crate) struct PubGrubPackage(Arc); +pub struct PubGrubPackage(Arc); impl Deref for PubGrubPackage { type Target = PubGrubPackageInner; @@ -39,7 +39,7 @@ impl From for PubGrubPackage { /// package (e.g., `black[colorama]`), and mark it as a dependency of the real package (e.g., /// `black`). We then discard the virtual packages at the end of the resolution process. #[derive(Debug, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)] -pub(crate) enum PubGrubPackageInner { +pub enum PubGrubPackageInner { /// The root package, which is used to start the resolution process. Root(Option), /// A Python version. @@ -295,7 +295,7 @@ impl PubGrubPackage { } #[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Hash, Ord)] -pub(crate) enum PubGrubPython { +pub enum PubGrubPython { /// The Python version installed in the current environment. Installed, /// The Python version for which dependencies are being resolved. diff --git a/crates/uv-resolver/src/resolver/availability.rs b/crates/uv-resolver/src/resolver/availability.rs index d2e9296b9..64721b4b6 100644 --- a/crates/uv-resolver/src/resolver/availability.rs +++ b/crates/uv-resolver/src/resolver/availability.rs @@ -7,7 +7,7 @@ use uv_platform_tags::{AbiTag, Tags}; /// The reason why a package or a version cannot be used. #[derive(Debug, Clone, Eq, PartialEq)] -pub(crate) enum UnavailableReason { +pub enum UnavailableReason { /// The entire package cannot be used. Package(UnavailablePackage), /// A single version cannot be used. @@ -29,7 +29,7 @@ impl Display for UnavailableReason { /// Most variant are from [`MetadataResponse`] without the error source, since we don't format /// the source and we want to merge unavailable messages across versions. #[derive(Debug, Clone, Eq, PartialEq)] -pub(crate) enum UnavailableVersion { +pub enum UnavailableVersion { /// Version is incompatible because it has no usable distributions IncompatibleDist(IncompatibleDist), /// The wheel metadata was found, but could not be parsed. @@ -123,7 +123,7 @@ impl From<&MetadataUnavailable> for UnavailableVersion { /// The package is unavailable and cannot be used. #[derive(Debug, Clone, Eq, PartialEq)] -pub(crate) enum UnavailablePackage { +pub enum UnavailablePackage { /// Index lookups were disabled (i.e., `--no-index`) and the package was not found in a flat index (i.e. from `--find-links`). NoIndex, /// Network requests were disabled (i.e., `--offline`), and the package was not found in the cache. From 06af93fce761d3b9634185b6a94efa6c75ca8f8a Mon Sep 17 00:00:00 2001 From: konsti Date: Thu, 3 Jul 2025 22:29:03 +0200 Subject: [PATCH 147/349] Fix optional cfg gates (#14448) Running `cargo clippy` in individual crates could raise warnings due to unused imports as `Cow` is only used with `#[cfg(feature = "schemars")]` --- crates/uv-configuration/src/name_specifiers.rs | 4 +++- crates/uv-configuration/src/required_version.rs | 5 +++-- crates/uv-configuration/src/trusted_host.rs | 4 +++- crates/uv-distribution-types/src/pip_index.rs | 4 +++- crates/uv-distribution-types/src/status_code_strategy.rs | 4 +++- crates/uv-python/src/python_version.rs | 1 + crates/uv-resolver/src/exclude_newer.rs | 4 +++- crates/uv-workspace/src/pyproject.rs | 1 + 8 files changed, 20 insertions(+), 7 deletions(-) diff --git a/crates/uv-configuration/src/name_specifiers.rs b/crates/uv-configuration/src/name_specifiers.rs index 1fd25ea0b..3efeee1f2 100644 --- a/crates/uv-configuration/src/name_specifiers.rs +++ b/crates/uv-configuration/src/name_specifiers.rs @@ -1,4 +1,6 @@ -use std::{borrow::Cow, str::FromStr}; +#[cfg(feature = "schemars")] +use std::borrow::Cow; +use std::str::FromStr; use uv_pep508::PackageName; diff --git a/crates/uv-configuration/src/required_version.rs b/crates/uv-configuration/src/required_version.rs index 7135dfdab..70c69eaf3 100644 --- a/crates/uv-configuration/src/required_version.rs +++ b/crates/uv-configuration/src/required_version.rs @@ -1,5 +1,6 @@ -use std::str::FromStr; -use std::{borrow::Cow, fmt::Formatter}; +#[cfg(feature = "schemars")] +use std::borrow::Cow; +use std::{fmt::Formatter, str::FromStr}; use uv_pep440::{Version, VersionSpecifier, VersionSpecifiers, VersionSpecifiersParseError}; diff --git a/crates/uv-configuration/src/trusted_host.rs b/crates/uv-configuration/src/trusted_host.rs index 9f3efb6fc..07ff2998a 100644 --- a/crates/uv-configuration/src/trusted_host.rs +++ b/crates/uv-configuration/src/trusted_host.rs @@ -1,5 +1,7 @@ use serde::{Deserialize, Deserializer}; -use std::{borrow::Cow, str::FromStr}; +#[cfg(feature = "schemars")] +use std::borrow::Cow; +use std::str::FromStr; use url::Url; /// A host specification (wildcard, or host, with optional scheme and/or port) for which diff --git a/crates/uv-distribution-types/src/pip_index.rs b/crates/uv-distribution-types/src/pip_index.rs index 888c0df0d..18671e42f 100644 --- a/crates/uv-distribution-types/src/pip_index.rs +++ b/crates/uv-distribution-types/src/pip_index.rs @@ -3,7 +3,9 @@ //! flags set. use serde::{Deserialize, Deserializer, Serialize}; -use std::{borrow::Cow, path::Path}; +#[cfg(feature = "schemars")] +use std::borrow::Cow; +use std::path::Path; use crate::{Index, IndexUrl}; diff --git a/crates/uv-distribution-types/src/status_code_strategy.rs b/crates/uv-distribution-types/src/status_code_strategy.rs index 709f68be1..b019d0329 100644 --- a/crates/uv-distribution-types/src/status_code_strategy.rs +++ b/crates/uv-distribution-types/src/status_code_strategy.rs @@ -1,4 +1,6 @@ -use std::{borrow::Cow, ops::Deref}; +#[cfg(feature = "schemars")] +use std::borrow::Cow; +use std::ops::Deref; use http::StatusCode; use rustc_hash::FxHashSet; diff --git a/crates/uv-python/src/python_version.rs b/crates/uv-python/src/python_version.rs index 63f50f226..c5d8f6365 100644 --- a/crates/uv-python/src/python_version.rs +++ b/crates/uv-python/src/python_version.rs @@ -1,3 +1,4 @@ +#[cfg(feature = "schemars")] use std::borrow::Cow; use std::fmt::{Display, Formatter}; use std::ops::Deref; diff --git a/crates/uv-resolver/src/exclude_newer.rs b/crates/uv-resolver/src/exclude_newer.rs index c1ac6adb8..65fa55cfe 100644 --- a/crates/uv-resolver/src/exclude_newer.rs +++ b/crates/uv-resolver/src/exclude_newer.rs @@ -1,4 +1,6 @@ -use std::{borrow::Cow, str::FromStr}; +#[cfg(feature = "schemars")] +use std::borrow::Cow; +use std::str::FromStr; use jiff::{Timestamp, ToSpan, tz::TimeZone}; diff --git a/crates/uv-workspace/src/pyproject.rs b/crates/uv-workspace/src/pyproject.rs index 41e20914f..124a62881 100644 --- a/crates/uv-workspace/src/pyproject.rs +++ b/crates/uv-workspace/src/pyproject.rs @@ -6,6 +6,7 @@ //! //! Then lowers them into a dependency specification. +#[cfg(feature = "schemars")] use std::borrow::Cow; use std::collections::BTreeMap; use std::fmt::Formatter; From e8bc3950ef9bb276b3b03d96e8d38cafb4052af2 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 3 Jul 2025 19:32:07 -0400 Subject: [PATCH 148/349] Remove transparent variants in `uv-extract` to enable retries (#14450) ## Summary We think this is the culprit for the lack of retries in some settings (e.g., Python downloads). See: https://github.com/astral-sh/uv/issues/14425. --- crates/uv-extract/src/error.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/uv-extract/src/error.rs b/crates/uv-extract/src/error.rs index 09191bb0a..ae2fdff1a 100644 --- a/crates/uv-extract/src/error.rs +++ b/crates/uv-extract/src/error.rs @@ -2,11 +2,11 @@ use std::{ffi::OsString, path::PathBuf}; #[derive(Debug, thiserror::Error)] pub enum Error { - #[error(transparent)] + #[error("Failed to read from zip file")] Zip(#[from] zip::result::ZipError), - #[error(transparent)] + #[error("Failed to read from zip file")] AsyncZip(#[from] async_zip::error::ZipError), - #[error(transparent)] + #[error("I/O operation failed during extraction")] Io(#[from] std::io::Error), #[error( "The top-level of the archive must only contain a list directory, but it contains: {0:?}" From eaf517efd85f3fcaafe290ffc36615f4be8e04f9 Mon Sep 17 00:00:00 2001 From: Tim de Jager Date: Fri, 4 Jul 2025 20:08:23 +0200 Subject: [PATCH 149/349] Add method to get packages involved in a `NoSolutionError` (#14457) ## Summary In pixi we overlay the PyPI packages over the conda packages and we sometimes need to figure out what PyPI packages are involved in the no-solution error. We could parse the error message, but this is pretty error-prone, so it would be good to get access to more information. A lot of information in this module is private and should probably stay this way, but package names are easy enough to expose. This would help us a lot! I collect into a HashSet to remove duplication, and did not want to expose a rustc_hash datastructure directly, thats's why I've chosen to expose as an iterator :) Let me know if any changes need to be done, and thanks! --------- Co-authored-by: Zanie Blue --- crates/uv-resolver/src/error.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/uv-resolver/src/error.rs b/crates/uv-resolver/src/error.rs index 72f9217e3..0916f54ac 100644 --- a/crates/uv-resolver/src/error.rs +++ b/crates/uv-resolver/src/error.rs @@ -3,6 +3,7 @@ use std::fmt::Formatter; use std::sync::Arc; use indexmap::IndexSet; +use itertools::Itertools; use owo_colors::OwoColorize; use pubgrub::{ DefaultStringReporter, DerivationTree, Derived, External, Range, Ranges, Reporter, Term, @@ -409,6 +410,15 @@ impl NoSolutionError { } Ok(()) } + + /// Get the packages that are involved in this error. + pub fn packages(&self) -> impl Iterator { + self.error + .packages() + .into_iter() + .filter_map(|p| p.name()) + .unique() + } } impl std::fmt::Debug for NoSolutionError { From f609e1ddaf0b9468cc362fcbf4a091bf8b46c7e3 Mon Sep 17 00:00:00 2001 From: John Mumm Date: Fri, 4 Jul 2025 22:42:56 +0200 Subject: [PATCH 150/349] Document that `VerbatimUrl` does not preserve original string after serialization (#14456) This came up in [discussion](https://github.com/astral-sh/uv/pull/14387#issuecomment-3032223670) on #14387. --- crates/uv-pep508/src/verbatim_url.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/uv-pep508/src/verbatim_url.rs b/crates/uv-pep508/src/verbatim_url.rs index 8b914eb74..c800ba10c 100644 --- a/crates/uv-pep508/src/verbatim_url.rs +++ b/crates/uv-pep508/src/verbatim_url.rs @@ -18,11 +18,16 @@ use uv_redacted::DisplaySafeUrl; use crate::Pep508Url; /// A wrapper around [`Url`] that preserves the original string. +/// +/// The original string is not preserved after serialization/deserialization. #[derive(Debug, Clone, Eq)] pub struct VerbatimUrl { /// The parsed URL. url: DisplaySafeUrl, /// The URL as it was provided by the user. + /// + /// Even if originally set, this will be [`None`] after + /// serialization/deserialization. given: Option, } From 1308c85efe856cf8c4d26125b5c999182adb2fc6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 09:58:05 +0200 Subject: [PATCH 151/349] Update Rust crate async-channel to v2.5.0 (#14478) 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 | |---|---|---|---| | [async-channel](https://redirect.github.com/smol-rs/async-channel) | workspace.dependencies | minor | `2.3.1` -> `2.5.0` | --- > [!WARNING] > Some dependencies could not be looked up. Check the Dependency Dashboard for more information. --- ### Release Notes
    smol-rs/async-channel (async-channel) ### [`v2.5.0`](https://redirect.github.com/smol-rs/async-channel/blob/HEAD/CHANGELOG.md#Version-250) [Compare Source](https://redirect.github.com/smol-rs/async-channel/compare/v2.4.0...v2.5.0) - Add `Sender::closed()` ([#​102](https://redirect.github.com/smol-rs/async-channel/issues/102)) ### [`v2.4.0`](https://redirect.github.com/smol-rs/async-channel/blob/HEAD/CHANGELOG.md#Version-240) [Compare Source](https://redirect.github.com/smol-rs/async-channel/compare/v2.3.1...v2.4.0) - Add `Sender::same_channel()` and `Receiver::same_channel()`. ([#​98](https://redirect.github.com/smol-rs/async-channel/issues/98)) - Add `portable-atomic` feature to support platforms without atomics. ([#​106](https://redirect.github.com/smol-rs/async-channel/issues/106))
    --- ### Configuration 📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC), 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 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f9e51c47a..1e77b0505 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -189,9 +189,9 @@ dependencies = [ [[package]] name = "async-channel" -version = "2.3.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" dependencies = [ "concurrent-queue", "event-listener-strategy", @@ -1165,9 +1165,9 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ "event-listener", "pin-project-lite", From fc758bb75538244ec26066e059da99e618c88ceb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 10:00:52 +0200 Subject: [PATCH 152/349] Update Rust crate schemars to v1.0.4 (#14476) 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 | |---|---|---|---| | [schemars](https://graham.cool/schemars/) ([source](https://redirect.github.com/GREsau/schemars)) | workspace.dependencies | patch | `1.0.3` -> `1.0.4` | --- > [!WARNING] > Some dependencies could not be looked up. Check the Dependency Dashboard for more information. --- ### Release Notes
    GREsau/schemars (schemars) ### [`v1.0.4`](https://redirect.github.com/GREsau/schemars/blob/HEAD/CHANGELOG.md#104---2025-07-06) [Compare Source](https://redirect.github.com/GREsau/schemars/compare/v1.0.3...v1.0.4) ##### Fixed - Fix `JsonSchema` impl on [atomic](https://doc.rust-lang.org/std/sync/atomic/) types being ignored on non-nightly compilers due to a buggy `cfg` check ([https://github.com/GREsau/schemars/issues/453](https://redirect.github.com/GREsau/schemars/issues/453)) - Fix compatibility with minimal dependency versions, e.g. old(-ish) versions of `syn` ([https://github.com/GREsau/schemars/issues/450](https://redirect.github.com/GREsau/schemars/issues/450)) - Fix derive for empty tuple variants ([https://github.com/GREsau/schemars/issues/455](https://redirect.github.com/GREsau/schemars/issues/455))
    --- ### Configuration 📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC), 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 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1e77b0505..12d28a238 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3428,9 +3428,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1375ba8ef45a6f15d83fa8748f1079428295d403d6ea991d09ab100155fbc06d" +checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" dependencies = [ "dyn-clone", "ref-cast", @@ -3442,9 +3442,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b13ed22d6d49fe23712e068770b5c4df4a693a2b02eeff8e7ca3135627a24f6" +checksum = "33d020396d1d138dc19f1165df7545479dcd58d93810dc5d646a16e55abefa80" dependencies = [ "proc-macro2", "quote", From bb738aeb440d59f98a40284630002b15b3f3d797 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 10:04:50 +0200 Subject: [PATCH 153/349] Update Rust crate test-log to v0.2.18 (#14477) 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 | |---|---|---|---| | [test-log](https://redirect.github.com/d-e-s-o/test-log) | dev-dependencies | patch | `0.2.17` -> `0.2.18` | --- > [!WARNING] > Some dependencies could not be looked up. Check the Dependency Dashboard for more information. --- ### Release Notes
    d-e-s-o/test-log (test-log) ### [`v0.2.18`](https://redirect.github.com/d-e-s-o/test-log/blob/HEAD/CHANGELOG.md#0218) [Compare Source](https://redirect.github.com/d-e-s-o/test-log/compare/v0.2.17...v0.2.18) - Improved cooperation with other similar procedural macros to enable attribute stacking
    --- ### Configuration 📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC), 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 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 12d28a238..ffadd8df2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3968,9 +3968,9 @@ dependencies = [ [[package]] name = "test-log" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7f46083d221181166e5b6f6b1e5f1d499f3a76888826e6cb1d057554157cd0f" +checksum = "1e33b98a582ea0be1168eba097538ee8dd4bbe0f2b01b22ac92ea30054e5be7b" dependencies = [ "test-log-macros", "tracing-subscriber", @@ -3978,9 +3978,9 @@ dependencies = [ [[package]] name = "test-log-macros" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "888d0c3c6db53c0fdab160d2ed5e12ba745383d3e85813f2ea0f2b1475ab553f" +checksum = "451b374529930d7601b1eef8d32bc79ae870b6079b069401709c2a8bf9e75f36" dependencies = [ "proc-macro2", "quote", From 1d027bd92ae35a5eded15c8bc1109023ad4b482a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 10:36:26 +0200 Subject: [PATCH 154/349] Update pre-commit dependencies (#14474) 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 | |---|---|---|---| | [astral-sh/ruff-pre-commit](https://redirect.github.com/astral-sh/ruff-pre-commit) | repository | patch | `v0.12.1` -> `v0.12.2` | | [crate-ci/typos](https://redirect.github.com/crate-ci/typos) | repository | minor | `v1.33.1` -> `v1.34.0` | --- > [!WARNING] > Some dependencies could not be looked up. Check the Dependency Dashboard for more information. Note: The `pre-commit` manager in Renovate is not supported by the `pre-commit` maintainers or community. Please do not report any problems there, instead [create a Discussion in the Renovate repository](https://redirect.github.com/renovatebot/renovate/discussions/new) if you have any questions. --- ### Release Notes
    astral-sh/ruff-pre-commit (astral-sh/ruff-pre-commit) ### [`v0.12.2`](https://redirect.github.com/astral-sh/ruff-pre-commit/releases/tag/v0.12.2) [Compare Source](https://redirect.github.com/astral-sh/ruff-pre-commit/compare/v0.12.1...v0.12.2) See: https://github.com/astral-sh/ruff/releases/tag/0.12.2
    crate-ci/typos (crate-ci/typos) ### [`v1.34.0`](https://redirect.github.com/crate-ci/typos/releases/tag/v1.34.0) [Compare Source](https://redirect.github.com/crate-ci/typos/compare/v1.33.1...v1.34.0) #### \[1.34.0] - 2025-06-30 ##### Features - Updated the dictionary with the [June 2025](https://redirect.github.com/crate-ci/typos/issues/1309) changes
    --- ### Configuration 📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC), 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. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] 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> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2280f337b..1c8965c0f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,7 +12,7 @@ repos: - id: validate-pyproject - repo: https://github.com/crate-ci/typos - rev: v1.33.1 + rev: v1.34.0 hooks: - id: typos @@ -42,7 +42,7 @@ repos: types_or: [yaml, json5] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.1 + rev: v0.12.2 hooks: - id: ruff-format - id: ruff From 3a77b9cdd997777a6e464bcc60a0995ecf31b23b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 11:31:31 +0200 Subject: [PATCH 155/349] Update aws-actions/configure-aws-credentials digest to f503a18 (#14473) 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 | action | digest | `3d8cba3` -> `f503a18` | --- > [!WARNING] > Some dependencies could not be looked up. Check the Dependency Dashboard for more information. --- ### Configuration 📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC), 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 d102f87cf..bc77abd93 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1585,7 +1585,7 @@ jobs: run: chmod +x ./uv - name: "Configure AWS credentials" - uses: aws-actions/configure-aws-credentials@3d8cba388a057b13744d61818a337e40a119b1a7 + uses: aws-actions/configure-aws-credentials@f503a1870408dcf2c35d5c2b8a68e69211042c7d with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} From d31e6ad7c7fca31a5640f6cdc3f0870eef2c390c Mon Sep 17 00:00:00 2001 From: John Mumm Date: Mon, 7 Jul 2025 12:51:21 +0200 Subject: [PATCH 156/349] Move fragment preservation test to directly test our redirect handling logic (#14480) When [updating](https://github.com/astral-sh/uv/pull/14475) to the latest `reqwest` version, our fragment propagation test broke. That test was partially testing the `reqwest` behavior, so this PR moves the fragment test to directly test our logic for constructing redirect requests. --- crates/uv-client/src/base_client.rs | 39 +++++++++++++++++++++++++ crates/uv-client/src/registry_client.rs | 38 ------------------------ 2 files changed, 39 insertions(+), 38 deletions(-) diff --git a/crates/uv-client/src/base_client.rs b/crates/uv-client/src/base_client.rs index d62621863..e11845adb 100644 --- a/crates/uv-client/src/base_client.rs +++ b/crates/uv-client/src/base_client.rs @@ -982,6 +982,45 @@ mod tests { Ok(()) } + #[tokio::test] + async fn test_redirect_preserves_fragment() -> Result<()> { + for status in &[301, 302, 303, 307, 308] { + let server = MockServer::start().await; + Mock::given(method("GET")) + .respond_with( + ResponseTemplate::new(*status) + .insert_header("location", format!("{}/redirect", server.uri())), + ) + .mount(&server) + .await; + + let request = Client::new() + .get(format!("{}#fragment", server.uri())) + .build() + .unwrap(); + + let response = Client::builder() + .redirect(reqwest::redirect::Policy::none()) + .build() + .unwrap() + .execute(request.try_clone().unwrap()) + .await + .unwrap(); + + let redirect_request = + request_into_redirect(request, &response, CrossOriginCredentialsPolicy::Secure)? + .unwrap(); + assert!( + redirect_request + .url() + .fragment() + .is_some_and(|fragment| fragment == "fragment") + ); + } + + Ok(()) + } + #[tokio::test] async fn test_redirect_removes_authorization_header_on_cross_origin() -> Result<()> { for status in &[301, 302, 303, 307, 308] { diff --git a/crates/uv-client/src/registry_client.rs b/crates/uv-client/src/registry_client.rs index b53e1ed9a..5788ea56c 100644 --- a/crates/uv-client/src/registry_client.rs +++ b/crates/uv-client/src/registry_client.rs @@ -1416,44 +1416,6 @@ mod tests { Ok(()) } - #[tokio::test] - async fn test_redirect_preserve_fragment() -> Result<(), Error> { - let redirect_server = MockServer::start().await; - - // Configure the redirect server to respond with a 307 with a relative URL. - Mock::given(method("GET")) - .respond_with(ResponseTemplate::new(307).insert_header("Location", "/foo".to_string())) - .mount(&redirect_server) - .await; - - Mock::given(method("GET")) - .and(path_regex("/foo")) - .respond_with(ResponseTemplate::new(200)) - .mount(&redirect_server) - .await; - - let cache = Cache::temp()?; - let registry_client = RegistryClientBuilder::new(cache).build(); - let client = registry_client.cached_client().uncached(); - - let mut url = DisplaySafeUrl::parse(&redirect_server.uri())?; - url.set_fragment(Some("fragment")); - - assert_eq!( - client - .for_host(&url) - .get(Url::from(url.clone())) - .send() - .await? - .url() - .to_string(), - format!("{}/foo#fragment", redirect_server.uri()), - "Requests should preserve fragment" - ); - - Ok(()) - } - #[test] fn ignore_failing_files() { // 1.7.7 has an invalid requires-python field (double comma), 1.7.8 is valid From ddb1577a931bef1db26202095d84f0045688fb5c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 13:39:57 +0200 Subject: [PATCH 157/349] Update Rust crate reqwest to v0.12.22 (#14475) 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 | |---|---|---|---| | [reqwest](https://redirect.github.com/seanmonstar/reqwest) | workspace.dependencies | patch | `=0.12.15` -> `=0.12.22` | --- > [!WARNING] > Some dependencies could not be looked up. Check the Dependency Dashboard for more information. --- ### Release Notes
    seanmonstar/reqwest (reqwest) ### [`v0.12.22`](https://redirect.github.com/seanmonstar/reqwest/blob/HEAD/CHANGELOG.md#v01222) [Compare Source](https://redirect.github.com/seanmonstar/reqwest/compare/v0.12.21...v0.12.22) - Fix socks proxies when resolving IPv6 destinations. ### [`v0.12.21`](https://redirect.github.com/seanmonstar/reqwest/blob/HEAD/CHANGELOG.md#v01221) [Compare Source](https://redirect.github.com/seanmonstar/reqwest/compare/v0.12.20...v0.12.21) - Fix socks proxy to use `socks4a://` instead of `socks4h://`. - Fix `Error::is_timeout()` to check for hyper and IO timeouts too. - Fix request `Error` to again include URLs when possible. - Fix socks connect error to include more context. - (wasm) implement `Default` for `Body`. ### [`v0.12.20`](https://redirect.github.com/seanmonstar/reqwest/blob/HEAD/CHANGELOG.md#v01220) [Compare Source](https://redirect.github.com/seanmonstar/reqwest/compare/v0.12.19...v0.12.20) - Add `ClientBuilder::tcp_user_timeout(Duration)` option to set `TCP_USER_TIMEOUT`. - Fix proxy headers only using the first matched proxy. - (wasm) Fix re-adding `Error::is_status()`. ### [`v0.12.19`](https://redirect.github.com/seanmonstar/reqwest/blob/HEAD/CHANGELOG.md#v01219) [Compare Source](https://redirect.github.com/seanmonstar/reqwest/compare/v0.12.18...v0.12.19) - Fix redirect that changes the method to GET should remove payload headers. - Fix redirect to only check the next scheme if the policy action is to follow. - (wasm) Fix compilation error if `cookies` feature is enabled (by the way, it's a noop feature in wasm). ### [`v0.12.18`](https://redirect.github.com/seanmonstar/reqwest/blob/HEAD/CHANGELOG.md#v01218) [Compare Source](https://redirect.github.com/seanmonstar/reqwest/compare/v0.12.17...v0.12.18) - Fix compilation when `socks` enabled without TLS. ### [`v0.12.17`](https://redirect.github.com/seanmonstar/reqwest/blob/HEAD/CHANGELOG.md#v01217) [Compare Source](https://redirect.github.com/seanmonstar/reqwest/compare/v0.12.16...v0.12.17) - Fix compilation on macOS. ### [`v0.12.16`](https://redirect.github.com/seanmonstar/reqwest/blob/HEAD/CHANGELOG.md#v01216) [Compare Source](https://redirect.github.com/seanmonstar/reqwest/compare/v0.12.15...v0.12.16) - Add `ClientBuilder::http3_congestion_bbr()` to enable BBR congestion control. - Add `ClientBuilder::http3_send_grease()` to configure whether to send use QUIC grease. - Add `ClientBuilder::http3_max_field_section_size()` to configure the maximum response headers. - Add `ClientBuilder::tcp_keepalive_interval()` to configure TCP probe interval. - Add `ClientBuilder::tcp_keepalive_retries()` to configure TCP probe count. - Add `Proxy::headers()` to add extra headers that should be sent to a proxy. - Fix `redirect::Policy::limit()` which had an off-by-1 error, allowing 1 more redirect than specified. - Fix HTTP/3 to support streaming request bodies. - (wasm) Fix null bodies when calling `Response::bytes_stream()`.
    --- ### Configuration 📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC), 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). --- Closes #14243 --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: konstin --- Cargo.lock | 91 ++++++++++++++++++++++++++++-------------------------- Cargo.toml | 2 +- 2 files changed, 48 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ffadd8df2..ef7511af5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1698,7 +1698,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots", + "webpki-roots 0.26.8", ] [[package]] @@ -1707,6 +1707,7 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" dependencies = [ + "base64 0.22.1", "bytes", "futures-channel", "futures-core", @@ -1714,7 +1715,9 @@ dependencies = [ "http", "http-body", "hyper", + "ipnet", "libc", + "percent-encoding", "pin-project-lite", "socket2", "tokio", @@ -1945,6 +1948,16 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is-terminal" version = "0.4.15" @@ -3062,9 +3075,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.15" +version = "0.12.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" +checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" dependencies = [ "async-compression", "base64 0.22.1", @@ -3079,18 +3092,14 @@ dependencies = [ "hyper", "hyper-rustls", "hyper-util", - "ipnet", "js-sys", "log", - "mime", "mime_guess", - "once_cell", "percent-encoding", "pin-project-lite", "quinn", "rustls", "rustls-native-certs", - "rustls-pemfile", "rustls-pki-types", "serde", "serde_json", @@ -3098,17 +3107,16 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-rustls", - "tokio-socks", "tokio-util", "tower", + "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots", - "windows-registry 0.4.0", + "webpki-roots 1.0.1", ] [[package]] @@ -3351,15 +3359,6 @@ dependencies = [ "security-framework", ] -[[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "rustls-pki-types" version = "1.11.0" @@ -4172,18 +4171,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-socks" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f" -dependencies = [ - "either", - "futures-util", - "thiserror 1.0.69", - "tokio", -] - [[package]] name = "tokio-stream" version = "0.1.17" @@ -4266,6 +4253,24 @@ dependencies = [ "tower-service", ] +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags 2.9.1", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.3" @@ -5635,7 +5640,7 @@ dependencies = [ "uv-trampoline-builder", "uv-warnings", "which", - "windows-registry 0.5.3", + "windows-registry", "windows-result 0.3.4", "windows-sys 0.59.0", ] @@ -5839,7 +5844,7 @@ dependencies = [ "tracing", "uv-fs", "uv-static", - "windows-registry 0.5.3", + "windows-registry", "windows-result 0.3.4", "windows-sys 0.59.0", ] @@ -6221,6 +6226,15 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "webpki-roots" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8782dd5a41a24eed3a4f40b606249b3e236ca61adf1f25ea4d45c73de122b502" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "weezl" version = "0.1.8" @@ -6448,17 +6462,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-registry" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" -dependencies = [ - "windows-result 0.3.4", - "windows-strings 0.3.1", - "windows-targets 0.53.0", -] - [[package]] name = "windows-registry" version = "0.5.3" diff --git a/Cargo.toml b/Cargo.toml index 817c5c62b..fc19dcc9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -142,7 +142,7 @@ ref-cast = { version = "1.0.24" } reflink-copy = { version = "0.1.19" } regex = { version = "1.10.6" } regex-automata = { version = "0.4.8", default-features = false, features = ["dfa-build", "dfa-search", "perf", "std", "syntax"] } -reqwest = { version = "=0.12.15", default-features = false, features = ["json", "gzip", "deflate", "zstd", "stream", "rustls-tls", "rustls-tls-native-roots", "socks", "multipart", "http2", "blocking"] } +reqwest = { version = "0.12.22", default-features = false, features = ["json", "gzip", "deflate", "zstd", "stream", "rustls-tls", "rustls-tls-native-roots", "socks", "multipart", "http2", "blocking"] } reqwest-middleware = { git = "https://github.com/astral-sh/reqwest-middleware", rev = "ad8b9d332d1773fde8b4cd008486de5973e0a3f8", features = ["multipart"] } reqwest-retry = { git = "https://github.com/astral-sh/reqwest-middleware", rev = "ad8b9d332d1773fde8b4cd008486de5973e0a3f8" } rkyv = { version = "0.8.8", features = ["bytecheck"] } From 1d20530f2dfbfa8e20dad236558d4ae82ebebdf3 Mon Sep 17 00:00:00 2001 From: Nils Koch Date: Mon, 7 Jul 2025 18:16:50 +0200 Subject: [PATCH 158/349] trim content of `INSTALLER` file (#14488) ## Summary We are using UV as a library and `installer()` returned `"pip\n"`. The packages got installed by the pip package manager and not by UV. pip seems to add a new line to the `INSTALLER` file and UV does not. ## Test Plan --- crates/uv-distribution-types/src/installed.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/uv-distribution-types/src/installed.rs b/crates/uv-distribution-types/src/installed.rs index d5813f4c7..a285fc1db 100644 --- a/crates/uv-distribution-types/src/installed.rs +++ b/crates/uv-distribution-types/src/installed.rs @@ -365,7 +365,7 @@ impl InstalledDist { pub fn installer(&self) -> Result, InstalledDistError> { let path = self.install_path().join("INSTALLER"); match fs::read_to_string(path) { - Ok(installer) => Ok(Some(installer)), + Ok(installer) => Ok(Some(installer.trim().to_owned())), Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None), Err(err) => Err(err.into()), } From 5c6d76ca8bfec186f87bcea78a474ab22e122d2e Mon Sep 17 00:00:00 2001 From: theirix Date: Mon, 7 Jul 2025 20:04:45 +0100 Subject: [PATCH 159/349] Update documentation for GHA to use v6 (#14490) ## Summary `astral-sh/setup-uv@v6` is the latest version of GitHub actions. ## Test Plan Documentation update --- docs/guides/integration/github.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/guides/integration/github.md b/docs/guides/integration/github.md index de8853d05..5a0ae6ad0 100644 --- a/docs/guides/integration/github.md +++ b/docs/guides/integration/github.md @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@v4 - name: Install uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@v6 ``` It is considered best practice to pin to a specific uv version, e.g., with: @@ -44,7 +44,7 @@ jobs: - uses: actions/checkout@v4 - name: Install uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@v6 with: # Install a specific version of uv. version: "0.7.19" @@ -66,7 +66,7 @@ jobs: - uses: actions/checkout@v4 - name: Install uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@v6 - name: Set up Python run: uv python install @@ -93,10 +93,10 @@ jobs: - uses: actions/checkout@v4 - name: Install uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@v6 - name: "Set up Python" - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version-file: ".python-version" ``` @@ -116,10 +116,10 @@ jobs: - uses: actions/checkout@v4 - name: Install uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@v6 - name: "Set up Python" - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version-file: "pyproject.toml" ``` @@ -146,7 +146,7 @@ jobs: - uses: actions/checkout@v4 - name: Install uv and set the python version - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@v6 with: python-version: ${{ matrix.python-version }} ``` @@ -187,7 +187,7 @@ jobs: - uses: actions/checkout@v4 - name: Install uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@v6 - 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@v5 + uses: astral-sh/setup-uv@v6 with: enable-cache: true ``` @@ -221,7 +221,7 @@ You can configure the action to use a custom cache directory on the runner: ```yaml title="example.yml" - name: Define a custom uv cache path - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@v6 with: enable-cache: true cache-local-path: "/path/to/cache" @@ -231,7 +231,7 @@ Or invalidate it when the lockfile changes: ```yaml title="example.yml" - name: Define a cache dependency glob - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@v6 with: enable-cache: true cache-dependency-glob: "uv.lock" @@ -241,7 +241,7 @@ Or when any requirements file changes: ```yaml title="example.yml" - name: Define a cache dependency glob - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@v6 with: enable-cache: true cache-dependency-glob: "requirements**.txt" From dedced32657e9db1e5561798da47b35a0ac42b64 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Mon, 7 Jul 2025 15:06:23 -0500 Subject: [PATCH 160/349] Remove `cache-dependency-glob` examples for `setup-uv` (#14493) See https://github.com/astral-sh/uv/pull/13163#discussion_r2063244551 --- docs/guides/integration/github.md | 33 ------------------------------- 1 file changed, 33 deletions(-) diff --git a/docs/guides/integration/github.md b/docs/guides/integration/github.md index 5a0ae6ad0..b41b99e2d 100644 --- a/docs/guides/integration/github.md +++ b/docs/guides/integration/github.md @@ -217,39 +217,6 @@ persisting the cache: enable-cache: true ``` -You can configure the action to use a custom cache directory on the runner: - -```yaml title="example.yml" -- name: Define a custom uv cache path - uses: astral-sh/setup-uv@v6 - with: - enable-cache: true - cache-local-path: "/path/to/cache" -``` - -Or invalidate it when the lockfile changes: - -```yaml title="example.yml" -- name: Define a cache dependency glob - uses: astral-sh/setup-uv@v6 - with: - enable-cache: true - cache-dependency-glob: "uv.lock" -``` - -Or when any requirements file changes: - -```yaml title="example.yml" -- name: Define a cache dependency glob - uses: astral-sh/setup-uv@v6 - with: - enable-cache: true - cache-dependency-glob: "requirements**.txt" -``` - -Note that `astral-sh/setup-uv` will automatically use a separate cache key for each host -architecture and platform. - Alternatively, you can manage the cache manually with the `actions/cache` action: ```yaml title="example.yml" From e31f5562057bf88a7df387bc50e83cbbfc554e1e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 8 Jul 2025 00:53:38 +0000 Subject: [PATCH 161/349] Sync latest Python releases (#14452) Automated update for Python releases. Co-authored-by: zanieb <2586601+zanieb@users.noreply.github.com> --- crates/uv-python/download-metadata.json | 96 +++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/crates/uv-python/download-metadata.json b/crates/uv-python/download-metadata.json index b697da9c8..39d31ca3e 100644 --- a/crates/uv-python/download-metadata.json +++ b/crates/uv-python/download-metadata.json @@ -33135,6 +33135,102 @@ "sha256": null, "variant": "debug" }, + "pypy-3.11.13-darwin-aarch64-none": { + "name": "pypy", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 11, + "patch": 13, + "prerelease": "", + "url": "https://downloads.python.org/pypy/pypy3.11-v7.3.20-macos_arm64.tar.bz2", + "sha256": "84a48e09c97f57df62cc9f01b7a6d8c3e306b6270671d871aa8ab8c06945940d", + "variant": null + }, + "pypy-3.11.13-darwin-x86_64-none": { + "name": "pypy", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 11, + "patch": 13, + "prerelease": "", + "url": "https://downloads.python.org/pypy/pypy3.11-v7.3.20-macos_x86_64.tar.bz2", + "sha256": "bb3ae80cf5fca5044af2e42933e7692c7c5e76a828ce0eb6404a5d5da83b313c", + "variant": null + }, + "pypy-3.11.13-linux-aarch64-gnu": { + "name": "pypy", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 13, + "prerelease": "", + "url": "https://downloads.python.org/pypy/pypy3.11-v7.3.20-aarch64.tar.bz2", + "sha256": "9347fe691a07fd9df17a1b186554fb9d9e6210178ffef19520a579ce1f9eb741", + "variant": null + }, + "pypy-3.11.13-linux-i686-gnu": { + "name": "pypy", + "arch": { + "family": "i686", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 13, + "prerelease": "", + "url": "https://downloads.python.org/pypy/pypy3.11-v7.3.20-linux32.tar.bz2", + "sha256": "d08ce15dd61e9ace5e010b047104f0137110a258184e448ea8239472f10cf99b", + "variant": null + }, + "pypy-3.11.13-linux-x86_64-gnu": { + "name": "pypy", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 13, + "prerelease": "", + "url": "https://downloads.python.org/pypy/pypy3.11-v7.3.20-linux64.tar.bz2", + "sha256": "1410db3a7ae47603e2b7cbfd7ff6390b891b2e041c9eb4f1599f333677bccb3e", + "variant": null + }, + "pypy-3.11.13-windows-x86_64-none": { + "name": "pypy", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "windows", + "libc": "none", + "major": 3, + "minor": 11, + "patch": 13, + "prerelease": "", + "url": "https://downloads.python.org/pypy/pypy3.11-v7.3.20-win64.zip", + "sha256": "a8d36f6ceb1d9be6cf24a73b0ba103e7567e396b2f7a33426b05e4a06330755b", + "variant": null + }, "pypy-3.11.11-darwin-aarch64-none": { "name": "pypy", "arch": { From 7e48292fac968b015c4521e193b09e27af1d5c7b Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Mon, 7 Jul 2025 20:10:35 -0500 Subject: [PATCH 162/349] Fix handling of pre-releases in preferences (#14498) Closes https://github.com/astral-sh/uv/issues/14485 I tested this using the reproduction in the issue. It'd be nice to add test coverage though. --- crates/uv-resolver/src/candidate_selector.rs | 22 ++++++++------- crates/uv-resolver/src/preferences.rs | 28 ++++++++++++++++++++ crates/uv-resolver/src/resolver/mod.rs | 3 ++- crates/uv/src/commands/project/lock.rs | 2 +- crates/uv/src/commands/project/mod.rs | 12 ++++----- crates/uv/src/commands/project/run.rs | 6 ++--- 6 files changed, 53 insertions(+), 20 deletions(-) diff --git a/crates/uv-resolver/src/candidate_selector.rs b/crates/uv-resolver/src/candidate_selector.rs index b0fe74409..e03302966 100644 --- a/crates/uv-resolver/src/candidate_selector.rs +++ b/crates/uv-resolver/src/candidate_selector.rs @@ -13,10 +13,9 @@ use uv_normalize::PackageName; use uv_pep440::Version; use uv_types::InstalledPackagesProvider; -use crate::preferences::{Entry, Preferences}; +use crate::preferences::{Entry, PreferenceSource, Preferences}; use crate::prerelease::{AllowPrerelease, PrereleaseStrategy}; use crate::resolution_mode::ResolutionStrategy; -use crate::universal_marker::UniversalMarker; use crate::version_map::{VersionMap, VersionMapDistHandle}; use crate::{Exclusions, Manifest, Options, ResolverEnvironment}; @@ -188,7 +187,7 @@ impl CandidateSelector { if index.is_some_and(|index| !entry.index().matches(index)) { return None; } - Either::Left(std::iter::once((entry.marker(), entry.pin().version()))) + Either::Left(std::iter::once((entry.pin().version(), entry.source()))) } [..] => { type Entries<'a> = SmallVec<[&'a Entry; 3]>; @@ -219,7 +218,7 @@ impl CandidateSelector { Either::Right( preferences .into_iter() - .map(|entry| (entry.marker(), entry.pin().version())), + .map(|entry| (entry.pin().version(), entry.source())), ) } }; @@ -238,7 +237,7 @@ impl CandidateSelector { /// Return the first preference that satisfies the current range and is allowed. fn get_preferred_from_iter<'a, InstalledPackages: InstalledPackagesProvider>( &'a self, - preferences: impl Iterator, + preferences: impl Iterator, package_name: &'a PackageName, range: &Range, version_maps: &'a [VersionMap], @@ -246,7 +245,7 @@ impl CandidateSelector { reinstall: bool, env: &ResolverEnvironment, ) -> Option> { - for (marker, version) in preferences { + for (version, source) in preferences { // Respect the version range for this requirement. if !range.contains(version) { continue; @@ -290,9 +289,14 @@ impl CandidateSelector { let allow = match self.prerelease_strategy.allows(package_name, env) { AllowPrerelease::Yes => true, AllowPrerelease::No => false, - // If the pre-release is "global" (i.e., provided via a lockfile, rather than - // a fork), accept it unless pre-releases are completely banned. - AllowPrerelease::IfNecessary => marker.is_true(), + // If the pre-release was provided via an existing file, rather than from the + // current solve, accept it unless pre-releases are completely banned. + AllowPrerelease::IfNecessary => match source { + PreferenceSource::Resolver => false, + PreferenceSource::Lock + | PreferenceSource::Environment + | PreferenceSource::RequirementsTxt => true, + }, }; if !allow { continue; diff --git a/crates/uv-resolver/src/preferences.rs b/crates/uv-resolver/src/preferences.rs index 116d94d87..51e325d68 100644 --- a/crates/uv-resolver/src/preferences.rs +++ b/crates/uv-resolver/src/preferences.rs @@ -34,6 +34,8 @@ pub struct Preference { /// is part of, otherwise `None`. fork_markers: Vec, hashes: HashDigests, + /// The source of the preference. + source: PreferenceSource, } impl Preference { @@ -73,6 +75,7 @@ impl Preference { .map(String::as_str) .map(HashDigest::from_str) .collect::>()?, + source: PreferenceSource::RequirementsTxt, })) } @@ -91,6 +94,7 @@ impl Preference { index: PreferenceIndex::from(package.index(install_path)?), fork_markers: package.fork_markers().to_vec(), hashes: HashDigests::empty(), + source: PreferenceSource::Lock, })) } @@ -112,6 +116,7 @@ impl Preference { // `pylock.toml` doesn't have fork annotations. fork_markers: vec![], hashes: HashDigests::empty(), + source: PreferenceSource::Lock, })) } @@ -127,6 +132,7 @@ impl Preference { index: PreferenceIndex::Any, fork_markers: vec![], hashes: HashDigests::empty(), + source: PreferenceSource::Environment, }) } @@ -171,11 +177,24 @@ impl From> for PreferenceIndex { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum PreferenceSource { + /// The preference is from an installed package in the environment. + Environment, + /// The preference is from a `uv.ock` file. + Lock, + /// The preference is from a `requirements.txt` file. + RequirementsTxt, + /// The preference is from the current solve. + Resolver, +} + #[derive(Debug, Clone)] pub(crate) struct Entry { marker: UniversalMarker, index: PreferenceIndex, pin: Pin, + source: PreferenceSource, } impl Entry { @@ -193,6 +212,11 @@ impl Entry { pub(crate) fn pin(&self) -> &Pin { &self.pin } + + /// Return the source of the entry. + pub(crate) fn source(&self) -> PreferenceSource { + self.source + } } /// A set of pinned packages that should be preserved during resolution, if possible. @@ -245,6 +269,7 @@ impl Preferences { version: preference.version, hashes: preference.hashes, }, + source: preference.source, }); } else { for fork_marker in preference.fork_markers { @@ -255,6 +280,7 @@ impl Preferences { version: preference.version.clone(), hashes: preference.hashes.clone(), }, + source: preference.source, }); } } @@ -270,11 +296,13 @@ impl Preferences { index: Option, markers: UniversalMarker, pin: impl Into, + source: PreferenceSource, ) { self.0.entry(package_name).or_default().push(Entry { marker: markers, index: PreferenceIndex::from(index), pin: pin.into(), + source, }); } diff --git a/crates/uv-resolver/src/resolver/mod.rs b/crates/uv-resolver/src/resolver/mod.rs index ed1cd48af..32d684f04 100644 --- a/crates/uv-resolver/src/resolver/mod.rs +++ b/crates/uv-resolver/src/resolver/mod.rs @@ -47,7 +47,7 @@ use crate::fork_strategy::ForkStrategy; use crate::fork_urls::ForkUrls; use crate::manifest::Manifest; use crate::pins::FilePins; -use crate::preferences::Preferences; +use crate::preferences::{PreferenceSource, Preferences}; use crate::pubgrub::{ PubGrubDependency, PubGrubDistribution, PubGrubPackage, PubGrubPackageInner, PubGrubPriorities, PubGrubPython, @@ -447,6 +447,7 @@ impl ResolverState { +pub(crate) enum PreferenceLocation<'lock> { /// The preferences should be extracted from a lockfile. Lock { lock: &'lock Lock, @@ -1745,7 +1745,7 @@ pub(crate) struct EnvironmentSpecification<'lock> { /// The requirements to include in the environment. requirements: RequirementsSpecification, /// The preferences to respect when resolving. - preferences: Option>, + preferences: Option>, } impl From for EnvironmentSpecification<'_> { @@ -1758,9 +1758,9 @@ impl From for EnvironmentSpecification<'_> { } impl<'lock> EnvironmentSpecification<'lock> { - /// Set the [`PreferenceSource`] for the specification. + /// Set the [`PreferenceLocation`] for the specification. #[must_use] - pub(crate) fn with_preferences(self, preferences: PreferenceSource<'lock>) -> Self { + pub(crate) fn with_preferences(self, preferences: PreferenceLocation<'lock>) -> Self { Self { preferences: Some(preferences), ..self @@ -1869,7 +1869,7 @@ pub(crate) async fn resolve_environment( // If an existing lockfile exists, build up a set of preferences. let preferences = match spec.preferences { - Some(PreferenceSource::Lock { lock, install_path }) => { + Some(PreferenceLocation::Lock { lock, install_path }) => { let LockedRequirements { preferences, git } = read_lock_requirements(lock, install_path, &upgrade)?; @@ -1881,7 +1881,7 @@ pub(crate) async fn resolve_environment( preferences } - Some(PreferenceSource::Entries(entries)) => entries, + Some(PreferenceLocation::Entries(entries)) => entries, None => vec![], }; diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index a6ea4c0e0..a4fd4ae7d 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -49,7 +49,7 @@ use crate::commands::project::install_target::InstallTarget; use crate::commands::project::lock::LockMode; use crate::commands::project::lock_target::LockTarget; use crate::commands::project::{ - EnvironmentSpecification, PreferenceSource, ProjectEnvironment, ProjectError, + EnvironmentSpecification, PreferenceLocation, ProjectEnvironment, ProjectError, ScriptEnvironment, ScriptInterpreter, UniversalState, WorkspacePython, default_dependency_groups, script_specification, update_environment, validate_project_requires_python, @@ -958,10 +958,10 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl let spec = EnvironmentSpecification::from(spec).with_preferences( if let Some((lock, install_path)) = base_lock.as_ref() { // If we have a lockfile, use the locked versions as preferences. - PreferenceSource::Lock { lock, install_path } + PreferenceLocation::Lock { lock, install_path } } else { // Otherwise, extract preferences from the base environment. - PreferenceSource::Entries( + PreferenceLocation::Entries( base_site_packages .iter() .filter_map(Preference::from_installed) From 5e2dc5a9aa18aaa942f26513ca3b8ae4704eb018 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 8 Jul 2025 16:13:02 -0400 Subject: [PATCH 163/349] Remove `uv pip sync` suggestion with `pyproject.toml` (#14510) ## Summary I think doing this would almost always be a mistake, since it won't install any transitive dependencies. Instead, I took the opportunity to mention the `pylock.toml` format. Closes https://github.com/astral-sh/uv/issues/14507#issuecomment-3050083116. --- docs/pip/compile.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/pip/compile.md b/docs/pip/compile.md index c25414270..07780ff45 100644 --- a/docs/pip/compile.md +++ b/docs/pip/compile.md @@ -127,10 +127,10 @@ To sync an environment with a `requirements.txt` file: $ uv pip sync requirements.txt ``` -To sync an environment with a `pyproject.toml` file: +To sync an environment with a [PEP 751](https://peps.python.org/pep-0751/) `pylock.toml` file: ```console -$ uv pip sync pyproject.toml +$ uv pip sync pylock.toml ``` ## Adding constraints From afcbcc7498532f434d00c6d9a6d95ca3d3865e66 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 8 Jul 2025 21:12:22 -0500 Subject: [PATCH 164/349] Sync latest Python releases (#14514) Automated update for Python releases. Co-authored-by: zanieb <2586601+zanieb@users.noreply.github.com> --- crates/uv-python/download-metadata.json | 1560 +++++++++++++++++------ crates/uv/tests/it/python_install.rs | 10 +- 2 files changed, 1201 insertions(+), 369 deletions(-) diff --git a/crates/uv-python/download-metadata.json b/crates/uv-python/download-metadata.json index 39d31ca3e..4e2d98846 100644 --- a/crates/uv-python/download-metadata.json +++ b/crates/uv-python/download-metadata.json @@ -1,4 +1,836 @@ { + "cpython-3.14.0b4-darwin-aarch64-none": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "7a69c986243f4e7ed70c1a97d4a524253d3fb4f042ae68eb688f9fafe5dbb714", + "variant": null + }, + "cpython-3.14.0b4-darwin-x86_64-none": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "8c100fe3bfef08b046051c4183c9ca4542317729c466982783fabea996fcb97f", + "variant": null + }, + "cpython-3.14.0b4-linux-aarch64-gnu": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "930e8ecf6c89de145cf49171d98e089af7007752e8e7652c1ea73460fec0d07c", + "variant": null + }, + "cpython-3.14.0b4-linux-armv7-gnueabi": { + "name": "cpython", + "arch": { + "family": "armv7", + "variant": null + }, + "os": "linux", + "libc": "gnueabi", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "5b489148c56a0a9772568706cf6c716e14b1d93e52f54d76f71f14783f659d13", + "variant": null + }, + "cpython-3.14.0b4-linux-armv7-gnueabihf": { + "name": "cpython", + "arch": { + "family": "armv7", + "variant": null + }, + "os": "linux", + "libc": "gnueabihf", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "2b4474ebc495b64374339acf58d22793f8f55ce1a40e31d61a988af7cf2c8085", + "variant": null + }, + "cpython-3.14.0b4-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": { + "family": "powerpc64le", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "abc24237c270f248b5b2990091209a60c23d5bef8476796cf5b0c16c34a24e54", + "variant": null + }, + "cpython-3.14.0b4-linux-riscv64-gnu": { + "name": "cpython", + "arch": { + "family": "riscv64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "fd25c2de82d3ea004831c543591195f3790c93d5df7f5f1a39b0e5f9e1716039", + "variant": null + }, + "cpython-3.14.0b4-linux-s390x-gnu": { + "name": "cpython", + "arch": { + "family": "s390x", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "35f93fd3336dcfd2612fb2945937221f81af9a65369efb81afa1d89784029e61", + "variant": null + }, + "cpython-3.14.0b4-linux-x86_64-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "a76999ca5b8c6e219750b016870fc85cc395dd992de1d702576d1c831585aa95", + "variant": null + }, + "cpython-3.14.0b4-linux-x86_64-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "a8f12323bd6c10f1ecadbe424e64c2429434e59e69314966a422c9a7eb5f13a0", + "variant": null + }, + "cpython-3.14.0b4-linux-x86_64_v2-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v2" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "64649a18cee348ba72b42ec46aa548dca3d79ed37a2abeea17f5b5fea4ad67b4", + "variant": null + }, + "cpython-3.14.0b4-linux-x86_64_v2-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v2" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "352b97d9c5634787cdfe11b00a4ac83e0a254f70dc2887780fa93b52a8cdbec8", + "variant": null + }, + "cpython-3.14.0b4-linux-x86_64_v3-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v3" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "d780f46da4c2ae2400cb08c6e5900d976d46572c1fb2dc6a9494a4c309f913f2", + "variant": null + }, + "cpython-3.14.0b4-linux-x86_64_v3-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v3" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "4ef7c85e6a6788f1838a80a23463ee36fdfd50c909c784bc6ed7011725220288", + "variant": null + }, + "cpython-3.14.0b4-linux-x86_64_v4-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v4" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "cd91301114d7ebfcfccbb3377a09c8d8537dc460de629ec6e64d3880aeb7ab0c", + "variant": null + }, + "cpython-3.14.0b4-linux-x86_64_v4-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v4" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "ff8cba3869c879717c6aae2931398b1c30ab761008483a49cc5d93899a2eeb8c", + "variant": null + }, + "cpython-3.14.0b4-windows-aarch64-none": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "windows", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "c21eb7a109ec8b980735aee5ca5c3b7522479919d12078f046a05114de428ff0", + "variant": null + }, + "cpython-3.14.0b4-windows-i686-none": { + "name": "cpython", + "arch": { + "family": "i686", + "variant": null + }, + "os": "windows", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "29ebdc7899a947e29aba6376477d059871698b712cf0dfb75b8e96af2e8b23cb", + "variant": null + }, + "cpython-3.14.0b4-windows-x86_64-none": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "windows", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "072b97a1850f11bc350c1abfa5c08024ce4fe008022d634e23d4647e47cc005f", + "variant": null + }, + "cpython-3.14.0b4+freethreaded-darwin-aarch64-none": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "f4a28e1d77003d6cd955f2a436a244ec03bb64f142a9afc79246634d3dec5da3", + "variant": "freethreaded" + }, + "cpython-3.14.0b4+freethreaded-darwin-x86_64-none": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "f1ea70b041fa5862124980b7fe34362987243a7ecc34fde881357503e47f32ab", + "variant": "freethreaded" + }, + "cpython-3.14.0b4+freethreaded-linux-aarch64-gnu": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-aarch64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "2a92a108a3fbd5c439408fe9f3b62bf569ef06dbc2b5b657de301f14a537231a", + "variant": "freethreaded" + }, + "cpython-3.14.0b4+freethreaded-linux-armv7-gnueabi": { + "name": "cpython", + "arch": { + "family": "armv7", + "variant": null + }, + "os": "linux", + "libc": "gnueabi", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-armv7-unknown-linux-gnueabi-freethreaded%2Blto-full.tar.zst", + "sha256": "f1d52c12f6908f6dc0658bf9d5cf1068272b4f9026aa33b59ded9f17e1d51f9f", + "variant": "freethreaded" + }, + "cpython-3.14.0b4+freethreaded-linux-armv7-gnueabihf": { + "name": "cpython", + "arch": { + "family": "armv7", + "variant": null + }, + "os": "linux", + "libc": "gnueabihf", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-armv7-unknown-linux-gnueabihf-freethreaded%2Blto-full.tar.zst", + "sha256": "418741c7de3c53323d9ae8a42a450f0f612fa5fbea1bedeea57dee0647c82a8d", + "variant": "freethreaded" + }, + "cpython-3.14.0b4+freethreaded-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": { + "family": "powerpc64le", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-ppc64le-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "5823a07c957162d6d675488d5306ac3f35a3f458e946cd74da6d1ac69bc97ce3", + "variant": "freethreaded" + }, + "cpython-3.14.0b4+freethreaded-linux-riscv64-gnu": { + "name": "cpython", + "arch": { + "family": "riscv64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-riscv64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "f48843e0f1c13ddeaaf9180bc105475873d924638969bc9256a2ac170faeb933", + "variant": "freethreaded" + }, + "cpython-3.14.0b4+freethreaded-linux-s390x-gnu": { + "name": "cpython", + "arch": { + "family": "s390x", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-s390x-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "a1e6f843d533c88e290d1e757d4c7953c4f4ccfb5380fef5405aceab938c6f57", + "variant": "freethreaded" + }, + "cpython-3.14.0b4+freethreaded-linux-x86_64-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "7f5ab66a563f48f169bdb1d216eed8c4126698583d21fa191ab4d995ca8b5506", + "variant": "freethreaded" + }, + "cpython-3.14.0b4+freethreaded-linux-x86_64-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "180249191d6e84b5dd61f6f7ba7215582b1296ef4d8bd048439cd981363cd2b2", + "variant": "freethreaded" + }, + "cpython-3.14.0b4+freethreaded-linux-x86_64_v2-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v2" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v2-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "bc9c0f25680f1f3c3104aef3144f1cd8c72d31e4cbf45a7c6f89ddb5c1b0e952", + "variant": "freethreaded" + }, + "cpython-3.14.0b4+freethreaded-linux-x86_64_v2-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v2" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v2-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "b30a2004c89d79256926bb4d87bec6100b669d967d336cb9df1aa5ae9a9106cf", + "variant": "freethreaded" + }, + "cpython-3.14.0b4+freethreaded-linux-x86_64_v3-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v3" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v3-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "6941b1d02adb12cd875c2320e0d30380b7837c705333336b8d295440d93d3668", + "variant": "freethreaded" + }, + "cpython-3.14.0b4+freethreaded-linux-x86_64_v3-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v3" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v3-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "b64f69cb58ac51e962080d6fa848d90dc24739bc94089a7975b3459b23ad5df3", + "variant": "freethreaded" + }, + "cpython-3.14.0b4+freethreaded-linux-x86_64_v4-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v4" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v4-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "b294b586bdcbc0b038e77999d4371c6fe3d90228b2b9aa632262ad3f5210487b", + "variant": "freethreaded" + }, + "cpython-3.14.0b4+freethreaded-linux-x86_64_v4-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v4" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v4-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "61ed61ed5052a7ca9d919194526486d7f973fd69bb97e70e95c917a984f723c7", + "variant": "freethreaded" + }, + "cpython-3.14.0b4+freethreaded-windows-aarch64-none": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "windows", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-aarch64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "d7396bafafc82b7e817f0d16208d0f37a88a97c0a71d91e477cbadc5b9d55f6d", + "variant": "freethreaded" + }, + "cpython-3.14.0b4+freethreaded-windows-i686-none": { + "name": "cpython", + "arch": { + "family": "i686", + "variant": null + }, + "os": "windows", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-i686-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "7066fc54db97331fb25f52783f188d65f8868ad578f9e25cb9b1ae1f2c6dacc5", + "variant": "freethreaded" + }, + "cpython-3.14.0b4+freethreaded-windows-x86_64-none": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "windows", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "5de7968ba0e344562fcff0f9f7c9454966279f1e274b6e701edee253b4a6b565", + "variant": "freethreaded" + }, + "cpython-3.14.0b4+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "9ac97f7531f9d74ccd1f7de8b558029094831a0be965fe9569ecc7547aeec445", + "variant": "debug" + }, + "cpython-3.14.0b4+debug-linux-armv7-gnueabi": { + "name": "cpython", + "arch": { + "family": "armv7", + "variant": null + }, + "os": "linux", + "libc": "gnueabi", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "fcb0d09a7774b69ca7df3a954fedc32bd1935838c91918f1d08b9a19914f30ec", + "variant": "debug" + }, + "cpython-3.14.0b4+debug-linux-armv7-gnueabihf": { + "name": "cpython", + "arch": { + "family": "armv7", + "variant": null + }, + "os": "linux", + "libc": "gnueabihf", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "664a70a1f73eb0ca1299bf8b26ec0b696ea1a09a26b5a1956688c3e4004b0ce2", + "variant": "debug" + }, + "cpython-3.14.0b4+debug-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": { + "family": "powerpc64le", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "71ac17708fd382292c5dbc77b11646b9ee52230381c2f7067bc5f22a2e2fd9cf", + "variant": "debug" + }, + "cpython-3.14.0b4+debug-linux-riscv64-gnu": { + "name": "cpython", + "arch": { + "family": "riscv64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "2916572ff670885b38860861fceb395711831ac2a36e0830fe0ee029a91cec56", + "variant": "debug" + }, + "cpython-3.14.0b4+debug-linux-s390x-gnu": { + "name": "cpython", + "arch": { + "family": "s390x", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "4086605066914c6fb1944932e59585c328c3a688379d2c061df8e963e65e04dd", + "variant": "debug" + }, + "cpython-3.14.0b4+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "c91fa37d96f46a4f58ac6d3b2d9e0178288e2fb21a05131c874abfbfae404f71", + "variant": "debug" + }, + "cpython-3.14.0b4+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "ab08748b50a7df1e6231fab1bf59a7e0b26cfb44ff2c811a9f249fe141332d21", + "variant": "debug" + }, + "cpython-3.14.0b4+debug-linux-x86_64_v2-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v2" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "64dd678f10b3bb86bd047cf585651d323c80e34da840ca8ed49507f3959acc90", + "variant": "debug" + }, + "cpython-3.14.0b4+debug-linux-x86_64_v2-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v2" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "3e057342e72555a4934e05037423f2b68f42d62a6f10b36d48150ca5110d603e", + "variant": "debug" + }, + "cpython-3.14.0b4+debug-linux-x86_64_v3-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v3" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "265b07a17fedc8ca32a8ebd6763946c21bb472346ac65efb89d1e045e4772abd", + "variant": "debug" + }, + "cpython-3.14.0b4+debug-linux-x86_64_v3-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v3" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "5860fc768bf7c7d2051ee80109f0fd5a4d89f045ca26562f88e5f93978979abe", + "variant": "debug" + }, + "cpython-3.14.0b4+debug-linux-x86_64_v4-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v4" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "ae0cf5352a594ce1dfd287fb49684490128a7f89b3dfbcd43f1b8d84083c8ead", + "variant": "debug" + }, + "cpython-3.14.0b4+debug-linux-x86_64_v4-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v4" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "5e2b1a537aa9cc6e1c77e6050f31aacd866c50b16b603b54c485b8f8cfeebb4a", + "variant": "debug" + }, "cpython-3.14.0b3-darwin-aarch64-none": { "name": "cpython", "arch": { @@ -5563,8 +6395,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "5a7888b6e0bbc2abf7327a786d50f46f36b941f43268ce05d6ef6f1f733734ca", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "71c9af8648001c4a09943305a890339a4cfff0bd260aa5a9d8c8e82e7ef32583", "variant": null }, "cpython-3.13.5-darwin-x86_64-none": { @@ -5579,8 +6411,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "691282c117b01de296d70bd3f2ec2d7316620233626440b35fa2271ddbcc07dc", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "65b171888e34d0a904ee0a6adef1a5366bdedcd9fca990ec06717a68eef2c4ff", "variant": null }, "cpython-3.13.5-linux-aarch64-gnu": { @@ -5595,8 +6427,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "d7ab196fefb0cacb44869774dd6afcaed2edc90219b67601ec1875002378219f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "e0d2322a92b9bb8e39442cbcfa6ee9590fd035de2a6199d4e6903dcbc0b6542a", "variant": null }, "cpython-3.13.5-linux-armv7-gnueabi": { @@ -5611,8 +6443,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "b90aac358d49e0c278843b83b5c8935036effe10f004ecec903313fea199badf", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "59442502a4eebff23a49503a9cbe92a6b813a756bf36a299ced55fb705d5fe73", "variant": null }, "cpython-3.13.5-linux-armv7-gnueabihf": { @@ -5627,8 +6459,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "69fe828cd149b21a6fda0937f73ef654dd8237d567916508acb328f24f9368c7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "c3de5a89b71ef3dc8ee53777a9fda3f2d7f381abc0b4a6f6f890de55d3620293", "variant": null }, "cpython-3.13.5-linux-powerpc64le-gnu": { @@ -5643,8 +6475,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "1a69f799fc5c0eb61708202ec5ba1514d6e5f3a547c07c53d40415d93084b903", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "c17e73fe07de36a506ffc400173739d2802f30bdc5f5b6443891bbcee926edac", "variant": null }, "cpython-3.13.5-linux-riscv64-gnu": { @@ -5659,8 +6491,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "2ce3570c6f3f8204e4b5e8e35896c87c71ddc038ca9a60f1111e2ea468b78f08", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "1b5da1585dca39a15452c891ff16f468ce984f76500c262f08c4aeae75e79c3c", "variant": null }, "cpython-3.13.5-linux-s390x-gnu": { @@ -5675,8 +6507,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "c974786ad18943fc3d5fbe4eca7bd43ceb07e725d2d513ac4dc0e3b6dd11a89e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "d47e645034432fce6d107835c07d5fe38fd53232a66e0a9d63ead48b42da3539", "variant": null }, "cpython-3.13.5-linux-x86_64-gnu": { @@ -5691,8 +6523,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "4444b5e95217c2c686bf3a689ab9655d47ea3b11c1f74714eceab021d50b7d74", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "2f57c58edc385fe9958d2c6e41ecd389cfed3f882515a1813f1d2ba4c964f399", "variant": null }, "cpython-3.13.5-linux-x86_64-musl": { @@ -5707,8 +6539,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "5125ef4d58b3dddbb0946c29f443160de95d6e8ea79bbe9562f9dd2873651d12", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "13cf16ef2008adf36a812add953317a4359945468dbcaece38b2b71466d05502", "variant": null }, "cpython-3.13.5-linux-x86_64_v2-gnu": { @@ -5723,8 +6555,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "91cf219324d090e7b2814f33c2b7fbf4930aa01c6a0fd8960eab8874f3b8babd", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "416d3a7bd64c3ee047b37d91ce1a58ec308733292c0268bfd860984c21eb7377", "variant": null }, "cpython-3.13.5-linux-x86_64_v2-musl": { @@ -5739,8 +6571,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "392dd5fd090f9aa5759318795733e37026cf13d554bcf5f90315d0f448a07228", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "c32aee456cb150a8c105c213dc4afa8a409fba1aced890a4f58001ae70074922", "variant": null }, "cpython-3.13.5-linux-x86_64_v3-gnu": { @@ -5755,8 +6587,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "c1bd09a8f83a78dd92a6d0e2a8dbf30b99d6ca31269fd1c80e14f0769b67df3f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "f2d3a4aa566ce5a505a82357c766ccfc60f6bb4e255fab8725da2fbc28a199d3", "variant": null }, "cpython-3.13.5-linux-x86_64_v3-musl": { @@ -5771,8 +6603,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "77350988d48699f3172a25aad33b229604b6faab9f1df35589ad7aca10ec10a8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "f8d1c8f82a6cd694ca453e1c5e96e7415232be288a832b17bd5a4e9b7a5c09fe", "variant": null }, "cpython-3.13.5-linux-x86_64_v4-gnu": { @@ -5787,8 +6619,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "f84aafa52b22484de42edb7c9965cafc52718fc03ac5f8d5ad6a92eb46ff3008", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "a46b315e40f93ce673fb5ff9193c1f9dee550fe6f494fe1bba41885ef19ee094", "variant": null }, "cpython-3.13.5-linux-x86_64_v4-musl": { @@ -5803,8 +6635,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "9510ffc50a28a1a06d5c1ed2bfd18fa0f469d5e93982d7a9289ab0ac4c8a2eee", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "4efeb9cd7c96f3b157478bb3037597b56334f14aad519eddc64da29849cc8031", "variant": null }, "cpython-3.13.5-windows-aarch64-none": { @@ -5819,8 +6651,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "2459713eff80372e0bfcce42b23b9892eb7e3b21ea6ae5cb5e504b8c0f12e6dd", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "22b73edc3afc256b58bb41b5a660aa835500781ef5b187de0c941748b1f38e3a", "variant": null }, "cpython-3.13.5-windows-i686-none": { @@ -5835,8 +6667,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "76287476f3a2b8658d29da67e05d550fbf2db33b9e9730c6d071bd239211ffe8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "fffdf2a1a16b9a24ef8489008a4a08927b202d7b79401913bbe1363e4180ad3a", "variant": null }, "cpython-3.13.5-windows-x86_64-none": { @@ -5851,8 +6683,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "f5838b42985c644d0597a1a6a54fb185647bb57d4f06cbc7d3ac8dfb53326521", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "0871127fcf73c79479f36b2f34177565f6e97b87b4dd9cdafe4d6c37b54c153a", "variant": null }, "cpython-3.13.5+freethreaded-darwin-aarch64-none": { @@ -5867,8 +6699,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "52e582cc89d654c565297b4ff9c3bd4bed5c3e81cad46f41c62485e700faf8bd", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "b7764ec1b41a7018c67c83ce3c98f47b0eeac9c4039f3cd50b5bcde4e86bde96", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-darwin-x86_64-none": { @@ -5883,8 +6715,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "5aed6d5950514004149d514f81a1cd426ac549696a563b8e47d32f7eba3b4be3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "f15f0700b64fb3475c4dcc2a41540b47857da0c777544c10eb510f71f552e8ec", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-aarch64-gnu": { @@ -5899,8 +6731,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-aarch64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "461832e4fb5ec1d719dc40f6490f9a639414dfa6769158187fa85d4b424b57cd", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-aarch64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "ced03b7ba62d2864df87ae86ecc50512fbfed66897602ae6f7aacbfb8d7eab38", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-armv7-gnueabi": { @@ -5915,8 +6747,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-armv7-unknown-linux-gnueabi-freethreaded%2Blto-full.tar.zst", - "sha256": "469fc30158fbcb5c3dc7e65e0d7d9e9e0f4de7dffdc97492083781f5f6216356", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-armv7-unknown-linux-gnueabi-freethreaded%2Blto-full.tar.zst", + "sha256": "0eafdd313352b0cda5cbfa872610cae8f47cfcba72da5a4267c7a1ef4dab8ccd", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-armv7-gnueabihf": { @@ -5931,8 +6763,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-armv7-unknown-linux-gnueabihf-freethreaded%2Blto-full.tar.zst", - "sha256": "9db3d9dbb529068d24b46c0616307f3c278e59c0087d7a1637105afde3bc5685", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-armv7-unknown-linux-gnueabihf-freethreaded%2Blto-full.tar.zst", + "sha256": "1a7c93ed247a564836416cbb008837059fb4e66468d1770a9b2ba2d12a415450", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-powerpc64le-gnu": { @@ -5947,8 +6779,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-ppc64le-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "c65c75edb450de830f724afdc774a215c2d3255097e0d670f709d2271fd6fd52", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-ppc64le-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "9c943e130a9893c9f6f375c02b34c0b7e62d186d283fc7950d0ee20d7e2f6821", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-riscv64-gnu": { @@ -5963,8 +6795,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-riscv64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "716e6e3fad24fb9931b93005000152dd9da4c3343b88ca54b5c01a7ab879d734", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-riscv64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "8075ed7b5f8c8a7c7c65563d2a1d5c20622a46416fb2e5b8d746592527472ea7", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-s390x-gnu": { @@ -5979,8 +6811,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-s390x-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "27276aee426a51f4165fac49391aedc5a9e301ae217366c77b65826122bb30fc", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-s390x-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "a8dbcbe79f7603d82a3640dfd05f9dbff07264f14a6a9a616d277f19d113222c", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64-gnu": { @@ -5995,8 +6827,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "f5eb29604c0b7afa2097fca094a06eb7a1f3ca4e194264c34f342739cae78202", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "e21a8d49749ffd40a439349f62fc59cb9e6424a22b40da0242bb8af6e964ba04", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64-musl": { @@ -6011,8 +6843,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "61f5960849c547313ff7142990ec8a8c1e299ccf3fcba00600bc8ee50fbb0db9", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "625ae3e251cf7f310078f3f77bfdae8bbe3f1fe2c64f0d8c2c60939cb71b99d4", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v2-gnu": { @@ -6027,8 +6859,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v2-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "6348a6ca86e8cfe30557fecfc15d6facefeeecb55aba33c338d6aa5830495e5b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v2-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "7b9bc02fc1eb08ba78145946644fe81bc6353e2e28e74890ff93378daffa9547", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v2-musl": { @@ -6043,8 +6875,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v2-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "4200aa24f5ca3b1621185fe0aee642f84e91ec353e0da2ca47a62c369855d07a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v2-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "4e163edf7e6a6a104f19213f3ad1b767f4d33a950ca8ea51f7b9ce04ba5a4c16", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v3-gnu": { @@ -6059,8 +6891,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v3-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "17ada48150f49c1d9aefc10839708d6f31f2665fa625103d45ccf88af46c9674", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v3-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "f1390326557df5562639bccaaaad4edcebf4e710696a2948b2aa00db2abdde5a", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v3-musl": { @@ -6075,8 +6907,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v3-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "b8a951e4eb04042af91864f3934e8e8b7527e390720ba68507a4b9fe4143032b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v3-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "d5751f3b8af6d06e06a0ce5ea18307c1b6c38508b3879442c504eca3047d4ae2", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v4-gnu": { @@ -6091,8 +6923,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v4-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "7bb14c995b2bc7b0a330a8e7b33d103d9f99ecb9c30ff8ca621d9d066bb63d9f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v4-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "88d8e7dfed818877158ede9b22342d9ce0fd3f49116954ca0eae7540e675d235", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v4-musl": { @@ -6107,8 +6939,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v4-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "a78845959c6f816f9a0fa602403b339d67d7125515f5b0fbe5c0ef393e4ce4e9", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v4-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "2a6de48306f788910b33c54e1640d3b9fe29ccb3c44dcdc0b0ba6d6a89213d9e", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-windows-aarch64-none": { @@ -6123,8 +6955,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-aarch64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", - "sha256": "97041594d903d6a1de1e55e9a3e5c613384aa7b900a93096f372732d9953f52a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-aarch64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "accb608c75ba9d6487fa3c611e1b8038873675cb058423a23fa7e30fc849cf69", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-windows-i686-none": { @@ -6139,8 +6971,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-i686-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", - "sha256": "02d20b1521420ef84160c3a781928cdc49cd2e39b3850fb26f01e4e643b8379e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-i686-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "5cba33c38d25519b4c55a5b0015865771e604a2d331c7d335f52753b09d5b667", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-windows-x86_64-none": { @@ -6155,8 +6987,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", - "sha256": "39e19dcb823a2ed47d9510753a642ba468802f1c5e15771c6c22814f4acada94", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "75acd65c9a44afae432abfd83db648256ac89122f31e21a59310b0c373b147f1", "variant": "freethreaded" }, "cpython-3.13.5+debug-linux-aarch64-gnu": { @@ -6171,8 +7003,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "fc7e5a8765892887b887b31eaa03b952405c98ad0b79bf412489810ab4872a18", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "bd73726128747a991d39bbc2c1a1792d97c6d2f4c7b6ed4b2db9254dd16d4ea6", "variant": "debug" }, "cpython-3.13.5+debug-linux-armv7-gnueabi": { @@ -6187,8 +7019,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "61e5a7e1c6bd86db10302899fe1053f54815a4d3e846ad3e8d4ebc5a858aa1ae", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "bbc1e704e4a2466cd52785e52f075e1b10ef5628879620b9461c6af2072e7036", "variant": "debug" }, "cpython-3.13.5+debug-linux-armv7-gnueabihf": { @@ -6203,8 +7035,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "e400e3e2ae88108034e62076edbc5518974eb76a46d236b5322fa7b2aa2110f4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "60389c2db232050357f24d7858ff019bb9cb37295465196275ec999e1d85f7db", "variant": "debug" }, "cpython-3.13.5+debug-linux-powerpc64le-gnu": { @@ -6219,8 +7051,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "1216b39a34397f25065f80bb6f3ffa56f364da9dae09a519df9d384c3a1c7505", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "e93c5832c3c6e39a2131d69de2e700bddab3a4f8bce74039e69276cec645f3a8", "variant": "debug" }, "cpython-3.13.5+debug-linux-riscv64-gnu": { @@ -6235,8 +7067,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "f2212f189076e6631e4447cc0c37872d03fc39eb92bb717a922899852e17765b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "6fb1da6dd6ccc40eea19062cb494f7cf0207c1e99a0a8cf9cae8fdc9cc30a4b6", "variant": "debug" }, "cpython-3.13.5+debug-linux-s390x-gnu": { @@ -6251,8 +7083,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "f672e057e98d5909b6ef94558018036254d4d4e40660cfb1654ce2c3b87bcd82", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "a62a131ed07e9ef322ded45fb5257aa58502b10cb6e2a18298145838a041637b", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64-gnu": { @@ -6267,8 +7099,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "3e798c809c4a9fc7308626ff72540036d5f01f9ac85ce6176acbdd581e973302", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "a054dca4b204562ae34cd38f7b31ff53f035acd012310f9f7c8817eac9852db2", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64-musl": { @@ -6283,8 +7115,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "1d5a3c193673b52eab5c5a362a18e6e184e4298a36a809fe5e21be6f01f9b76f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "5da37b4623286ed7283277ec6288d0be88fcd3d208e98c075a140385734f0056", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v2-gnu": { @@ -6299,8 +7131,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "8886ff6fd5483254a234e4ce52b4707147bc493f6556fa9869da4d1264af9440", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "83faa4f0a92287a55887ef402bb138ca7aa46848afb7c9a30ebc337f8cb4b86c", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v2-musl": { @@ -6315,8 +7147,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "8894486f97353fd0279fd7e4d107625aa57c68010c6fc8fcba6a549e3c4aa499", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "8caaba837f778d2da1b041f15f0f46a3c117a531a55d6e79f5aaca836ecfb84f", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v3-gnu": { @@ -6331,8 +7163,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "914baf954e3decbe182413b38a8a5c347246e889a4d34a91db3f4466945dba0a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "d4d2e746af77d16428d8168d11f8bf5b90424667949af7895413cdc18ebcaee8", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v3-musl": { @@ -6347,8 +7179,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "9c5de2ef49b4b871157e81cd7a0f4e881971d0c16873e6ad6376ace2914c07c5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "f76628dc2447a1fc55f463623c81f9a19002b5f968afe77b57136fdc41833993", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v4-gnu": { @@ -6363,8 +7195,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "48f0eaeeac55dbe85593e659962e6ea44cc638f74cc0aab1d943da805a4eca39", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "4620c454e6ae9ad0093785b54790ddb68c2d3f2d868aa79a5aa678b98e1138a3", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v4-musl": { @@ -6379,8 +7211,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.13.5%2B20250702-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "d0b7928f0e56c3509d541ecb5538d93d0dd673ba6460159f0d05c6a453c575c4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "e1f4f398dadd9cd83e351ea08a068bc3ea24f870ccddbeb3b65ce65a3bc5c106", "variant": "debug" }, "cpython-3.13.4-darwin-aarch64-none": { @@ -10603,8 +11435,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "0f3a56aeca07b511050210932e035a3b325abb032fca1e6b5d571f19cc93bc5b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "3c948bee581f42c4a3b072a5e1ff261e0eb1636c00d5474c28a13fa627c95578", "variant": null }, "cpython-3.12.11-darwin-x86_64-none": { @@ -10619,8 +11451,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "1543bcace1e0aadc5cdcc4a750202a7faa9be21fb50782aee67824f26f2668ad", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "c81794121d513b7eab710a210202e78393400460251a6878c85b927977098b38", "variant": null }, "cpython-3.12.11-linux-aarch64-gnu": { @@ -10635,8 +11467,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "1b7260c6aa4d3c7d27c5fc5750e6ece2312cf24e56c60239d55b5ad7a96b17cb", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "7ac6956ce9119a44531e9cbe3fe4d0beadcf244e02be81a863b95aa69041314f", "variant": null }, "cpython-3.12.11-linux-armv7-gnueabi": { @@ -10651,8 +11483,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "b977bb031eeffcf07b7373c509601dd26963df1a42964196fccf193129be6e3b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "4cc102db1b315425d2feda63407ee0e737902d94eaecf52e3ec8ea6f6d7cee4d", "variant": null }, "cpython-3.12.11-linux-armv7-gnueabihf": { @@ -10667,8 +11499,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "bf67827338f2b17443e6df04b19827ed2e8e4072850b18d4feca70ba26ba2d56", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "c62d2c512b4e35dfb40d29246ed02cf0049e645bf333eca0a9e703da51f64597", "variant": null }, "cpython-3.12.11-linux-powerpc64le-gnu": { @@ -10683,8 +11515,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "22f894d8e6d6848c4bc9ead18961febeaaecfea65bcf97ccc3ca1bd4fdcd4f70", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "1025a919ad5170f76c58fb73f4b2b3a5e2ed910d1f802390f032b4da91152f23", "variant": null }, "cpython-3.12.11-linux-riscv64-gnu": { @@ -10699,8 +11531,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "96c5c30c57d5fd1bdb705bfe73170462201a519f8a27cc0a394cd4ed875ae535", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "0178724fd0ce4712092c2afb66094e12d1f7e07744cf9d0c462aad516a82b984", "variant": null }, "cpython-3.12.11-linux-s390x-gnu": { @@ -10715,8 +11547,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "1967e03dd3d1f8137d29556eec8f7a51890816fd71c8b37060bd061bce74715a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "cb480b2fd0fefcdf71e07ab6a321e878bbc6d2c855356575db29fcbb48d5eae1", "variant": null }, "cpython-3.12.11-linux-x86_64-gnu": { @@ -10731,8 +11563,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "2eed351d3f6e99b4b6d1fe1f4202407fe041d799585dffdf6d93c49d1f899e37", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "50f2684ecd4dfdff732d091f0e3d383261a9d524a850784cd01a1c0839ece3e7", "variant": null }, "cpython-3.12.11-linux-x86_64-musl": { @@ -10747,8 +11579,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "8b201f157e437f4f3777e469b50f8e23dfa02f1c6757dfb2a19bde9f1bae9e0a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "f7ef6763b79a50da594fd1e03a6ee39017db6002c552539dbe0edffefc453804", "variant": null }, "cpython-3.12.11-linux-x86_64_v2-gnu": { @@ -10763,8 +11595,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "978249b5f885216be70d6723f51f5d6ad17628bacc2b1b98078e1273326ef847", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "32320209ab9b187b142a81bc4063c8aab9aa05ddb9833ca921c17eefdd2f1509", "variant": null }, "cpython-3.12.11-linux-x86_64_v2-musl": { @@ -10779,8 +11611,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "b81b771718ed550c4476df3c07d38383a2c9341e2e912fd58c224820cb18195c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "14abfef4e25db478db20dd15627576f47ff012a0eb3f7de3f9d1101ea409d02c", "variant": null }, "cpython-3.12.11-linux-x86_64_v3-gnu": { @@ -10795,8 +11627,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "651f0da21ac842276f9c3f955a3f3f87d0ad6ec1bba7c3bb8079c3f4752355b3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "4f71291857a656cf4b780d7c5bd2667ecde14f9ec093e026cf28d2c8727d69ad", "variant": null }, "cpython-3.12.11-linux-x86_64_v3-musl": { @@ -10811,8 +11643,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "4d7cbbf546b75623d0f7510befd2cf0a942b8bc0a38d82876f0844383aa27ba2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "b3530c771104b7765241b87b2ac749f6fce1886b4d2b677a1fc46aaca9378019", "variant": null }, "cpython-3.12.11-linux-x86_64_v4-gnu": { @@ -10827,8 +11659,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "b368b2dd0939f9e847acb5b0851081dcf2151e553bea4ac6f266a6ca0daeca01", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "0de444c0e4ac45f2f4863889e57f2dbbe79f01593afcc21f63b4ddb5832edd61", "variant": null }, "cpython-3.12.11-linux-x86_64_v4-musl": { @@ -10843,8 +11675,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "da76b72b295b230022a839d42edfde36f79ebfd70c9b381f6ed551066c3942bd", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "3d761bb79ef0946ee76b659c9bcf034dc8a67e1d414bef51ecb498c595a2b262", "variant": null }, "cpython-3.12.11-windows-aarch64-none": { @@ -10859,8 +11691,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "b5e56ebce5ea3cc0add5e460a254da1e095fdcf962552dceea1be314c45115bf", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "c2b541cd75cd12d7b1d52ebee724cc1b1f4d7367901d06b2f3f4a2e3ded4145e", "variant": null }, "cpython-3.12.11-windows-i686-none": { @@ -10875,8 +11707,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "0db0a69bab9aa6159f62d99074918b67e2a81c84b445570befeb583053663b58", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "cbf9c2bd5f182f6fc6da969729d0d4a5683d5f392f3a9bed3d7240cbe7385c11", "variant": null }, "cpython-3.12.11-windows-x86_64-none": { @@ -10891,8 +11723,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "195033883da90a35a57aecce522eb068b9b0a36908e6e07b91929f0acf646c8f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "79e5d97e543975309fe3a22e27f2d83d7b08cff462d699bfa721854971773ec6", "variant": null }, "cpython-3.12.11+debug-linux-aarch64-gnu": { @@ -10907,8 +11739,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "b0f04667686f489a410bb3e641b6abefa75dad033cd6d2725ab49a40413e15b7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "a8d1e10b91253cf528c9233c314e6958de7d9380c5e949a2ce1b1b4dc8538ebd", "variant": "debug" }, "cpython-3.12.11+debug-linux-armv7-gnueabi": { @@ -10923,8 +11755,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "d882d16f304b858173b40cca5c681e8f9c9f13774c26390303bd7e7657a1d73c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "46a11e0955ea444a0fe3fabbe9b1f36be4a72c804b8265d90f84f26a3de3199e", "variant": "debug" }, "cpython-3.12.11+debug-linux-armv7-gnueabihf": { @@ -10939,8 +11771,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "5934f6214d60a958fa3cb3147dad1941d912e0a9f37280de911cbf51a2a231be", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "ccc5fbb01a83f1a264e90d8f92324c64d3dc2b2bdc4568340bb58dc62b061cce", "variant": "debug" }, "cpython-3.12.11+debug-linux-powerpc64le-gnu": { @@ -10955,8 +11787,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "a2c821f4a83c3a80d8ec25cf3ca5380aa749488d87db5c99f1c3100069309f5f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "0f334bbaa774e7b98f264e04456dfb6130519294ac0c25593cebb41c92571e34", "variant": "debug" }, "cpython-3.12.11+debug-linux-riscv64-gnu": { @@ -10971,8 +11803,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "d1ac376b8756a057ba0d885171caa72bc7cd7ab7436ebc93bd7c0c47cff01d05", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "f048e364a7895b535c9e68f987cf17e3ee5f3bd3b7189b95cc7db30cd8a7b9b5", "variant": "debug" }, "cpython-3.12.11+debug-linux-s390x-gnu": { @@ -10987,8 +11819,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "8a57e27c920d1c93d45a3703c9f3fe047bac6305660a6d5ce2df51b0f7cfef26", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "c48068f9f02f16314265567acb56e411e9936abc9b18c9d67811f5faade66031", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64-gnu": { @@ -11003,8 +11835,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "6b1e42f30f027dc793f6edaf35c2ff857c63b0f72278c886917e99b6edd064b1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "ef2fe47be6b147bc376ce8f2949cc3d193c9c1d2e362fa9dcbabf0e7c60f8a19", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64-musl": { @@ -11019,8 +11851,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "62b9039f765e56538de58cb37be6baaf2d9da35bb6d95c5e734b432ccec474f8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "a88306d6b3a09b85f93514d43b2c8bd35dff417cf861bd2a1ead4d87c5666f8a", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v2-gnu": { @@ -11035,8 +11867,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "e1fb28c54a76f4e91f4d53fd5fd1840c7f0045049f7fca29f59c4d7bdfa8134d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "b8637c81f61f41d49bf95699cc4c295579d671912f81b5446c3ba2496dac2627", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v2-musl": { @@ -11051,8 +11883,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "c8d4fc92c668c0455a3dce10b2c107651a0d0676e449d30f2d4b6bb3cf2dac1d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "5dab0c1eb4ce013826a462247629263eae7726b635d868408152444cbf83a778", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v3-gnu": { @@ -11067,8 +11899,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "6cd111fa008f0a30345d0475e53f99148dc1aab3a39af73b7029ef4fc17c2717", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "eef2733d40a9511a2af9d83808ad640993c5d8b6fb436bc240cd9bac6be4ffc5", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v3-musl": { @@ -11083,8 +11915,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "3f4595219aaa4b55f3169f71895bac0f63563a2e27c3653ba5008249d7eb4ed0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "9f7fbd3712e13f91414e7a498a58160d8745fa02b9d2898db8f6f3c589920b6d", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v4-gnu": { @@ -11099,8 +11931,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "8976e1ef981ac4ceb186cb9bf367c279361060f468237a191f2ca2e52fd7a08b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "acf0037e25e80cbc3e8a1ff1e3b83da10ed2b00d8ff7df0ff1d207d896e2225f", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v4-musl": { @@ -11115,8 +11947,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.12.11%2B20250702-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "9008ed69a57d76a2e23b6119603217782a8ea3d30efebb550292348223ca87a5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "fefe36ed014e3a6baf0eb122161b42262c1a00ae403de18fb03353cf80d46c1f", "variant": "debug" }, "cpython-3.12.10-darwin-aarch64-none": { @@ -15163,8 +15995,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "7b32a1368af181ef16b2739f65849bb74d4ef1f324613ad9022d6f6fe3bb25f0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "f35b94b5aaefaff34b59f4aab09a5eec02c93e3b61a46c6694f4e93fb2aea86c", "variant": null }, "cpython-3.11.13-darwin-x86_64-none": { @@ -15179,8 +16011,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "7e9a250b61d7c5795dfe564f12869bef52898612220dfda462da88cdcf20031c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "c2a6b3053af4354d74b70d25ccf744bea7c545ee00da38a93e8b392ec9f062f1", "variant": null }, "cpython-3.11.13-linux-aarch64-gnu": { @@ -15195,8 +16027,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "e39b0b3d68487020cbd90e8ab66af334769059b9d4200901da6a6d0af71a0033", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "a05521f2fa75e60920cb1172722920262c73d7ead3045a2a5b4844d287a1dfdd", "variant": null }, "cpython-3.11.13-linux-armv7-gnueabi": { @@ -15211,8 +16043,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "48c8d43677ffbdff82c286c8a3afb472eba533070f2e064c7d9df9cbb2f6decf", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "a4bb388a080d1dc4a7d381d2bc7f74d00311d5fc6ef66d457178b5c62d7e0ac1", "variant": null }, "cpython-3.11.13-linux-armv7-gnueabihf": { @@ -15227,8 +16059,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "51e2914bb9846c2d88da69043371250f1fb7c1cafbc511d34794dbec5052cf98", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "80444ffb9f33d39a9462e2efa04ba7edbef6af2e957457a71a0710344972f0ba", "variant": null }, "cpython-3.11.13-linux-powerpc64le-gnu": { @@ -15243,8 +16075,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "be21c281b42b4fc250337111f8867d4cc7ced4f409303cc8dd5a56c6c6a820c7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "40e5fcea272e4a8253cf2bc392fbad36ca4260de75a12ef3c95711eb86f57a0c", "variant": null }, "cpython-3.11.13-linux-riscv64-gnu": { @@ -15259,8 +16091,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "ab9d02b521ca79f82f25391e84f35f0a984b949da088091f65744fcf9a83add9", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "eae2bbaf28b1f5886408e6cae4c5d393f3065dbd3293231b93bd0122f5f0543d", "variant": null }, "cpython-3.11.13-linux-s390x-gnu": { @@ -15275,8 +16107,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "2ab4713ea357a1da9da835712ea73b40aa93fe7f55528366e10ea61d8edb4bd0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "702fd03db386a6711afbf14778a5b2aca6d4c3e47ff26e85a4d85991023ee0db", "variant": null }, "cpython-3.11.13-linux-x86_64-gnu": { @@ -15291,8 +16123,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "096a6301b7029d11db44b1fd4364a3d474a3f5c7f2cd9317521bc58abf40b990", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "f730f5d09fc41e2573b0092ef143dd8976a8f6593ad31b833ea1d0adbc5562dd", "variant": null }, "cpython-3.11.13-linux-x86_64-musl": { @@ -15307,8 +16139,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "7aba64af498dfc9056405e21d5857ebf7e2dc88550de2f9b97efc5d67f100d18", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "337e164de474fefe5a2bf63c5d836093eae3532be80ed54b8d1abfd6dcb1b742", "variant": null }, "cpython-3.11.13-linux-x86_64_v2-gnu": { @@ -15323,8 +16155,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "806db935974b0d1c442c297fcb9e9d87b692e8f81bd4d887927449bb7eef70bf", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "3903459242e57e9979ca6e581c06f3e4c573cf1d3e2d3eb62ce2cba8e3d83fd9", "variant": null }, "cpython-3.11.13-linux-x86_64_v2-musl": { @@ -15339,8 +16171,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "e1dfc3b7064af3cbc68b95bdefcb88178fa9b3493f2a276b5b8e8610440ad9f3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "624494b5583fcec1f75464797686ffeb4727cf0ccdc54cf9c73f0b45888d5274", "variant": null }, "cpython-3.11.13-linux-x86_64_v3-gnu": { @@ -15355,8 +16187,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "c429bb26841da0b104644c1ab11dc8a76863e107436ad06e806f6bb54f7ec126", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "d5898a58943ed9f770a94125e7af85fbfd50b87e19135628708e8dbc6c8bd0b4", "variant": null }, "cpython-3.11.13-linux-x86_64_v3-musl": { @@ -15371,8 +16203,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "c618c57d50dd9bdd0f989d71dec9b76a742b051c1ae94111ca15515e183f87ee", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "8bc18b17a9f8d36271dca160d402c18a42552b0e50708bf3732d0e2b1985235d", "variant": null }, "cpython-3.11.13-linux-x86_64_v4-gnu": { @@ -15387,8 +16219,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "c1cf678915eb527b824464841c2c56508836bf8595778f39a9bbb7975d59806d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "257e29dc405d10062184da4078e1d46a787e19a04cba2a1c1831c21e52d0a557", "variant": null }, "cpython-3.11.13-linux-x86_64_v4-musl": { @@ -15403,8 +16235,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "d527c8614340ac46905969ac80e2c62327b7e987fbd448cfd74d66578ab42c67", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "4b7dec009dbdfb4821aebdb5ca082ac7765ecdb67980dc86adebd57febaf1aec", "variant": null }, "cpython-3.11.13-windows-aarch64-none": { @@ -15419,8 +16251,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "3af266f887d83e4705e2ceb2eb7d770f9c74454d676e739e768097d3ff9dc148", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "d45d2a6009dc50a76e4630c39ea36ba85e51555b7a17e1683d1bcf01c3bf7e1a", "variant": null }, "cpython-3.11.13-windows-i686-none": { @@ -15435,8 +16267,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "b38912438477ed7a7cb69aa92a5a834ffbb88d8fa5026eb626f1530adb3e00c7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "892f215501ae1cfe36e210224f4de106e5825f34f41ad8d458ef73f3012be61f", "variant": null }, "cpython-3.11.13-windows-x86_64-none": { @@ -15451,8 +16283,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "edb3eb9c646997de50b27932fdf10d8853614bdbd7d651c686459fc227776c1a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "d19baf214caf1ad3d1b34c6931dcd6d915abedd419ba4aecb0cacb7e1ec7884a", "variant": null }, "cpython-3.11.13+debug-linux-aarch64-gnu": { @@ -15467,8 +16299,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "5a58a85c773dcfd33b88b345fc899ab983e689fe5bf5ca6682fe62d1f3b65694", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "bf9e9c0295634d5ead7d3756651898d6af8d1bfdd8cc410769f9354d3e0871e4", "variant": "debug" }, "cpython-3.11.13+debug-linux-armv7-gnueabi": { @@ -15483,8 +16315,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "6b24708d696e86792db8214cb20d7c1bd9a0d03f926542cde7a5251a466977d8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "c0a5f208bbb1d51dfc3e98919f7856ae3a5643d2e6a6b5edfcbfa7ea41bb822e", "variant": "debug" }, "cpython-3.11.13+debug-linux-armv7-gnueabihf": { @@ -15499,8 +16331,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "ddd27f58a436b31bf1a3f39d53c670ab0ed481f677b1602d5fb0a5a89f471069", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "3d091a03c7d5fb47ac6050bffff371ce3904978ca3dc3c49f2bfacdc6b434a1d", "variant": "debug" }, "cpython-3.11.13+debug-linux-powerpc64le-gnu": { @@ -15515,8 +16347,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "5306fced1a898247f9e3cc897a28f05b647d8b70ed3ece80ea9f7fa525459d94", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "5c2be36a8aa027761b6c5da5bc4bb7ef92c6a8fa70a166f45fcc6f1c8b78330c", "variant": "debug" }, "cpython-3.11.13+debug-linux-riscv64-gnu": { @@ -15531,8 +16363,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "fee8c8cb156c0aa84f41759b277bc27c6ce004c1bbfd03a202c8b0347ea29e50", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "609cd34b0f86f576eec2e55a917d07e4d322e2c58309d6ae2243470207ed369b", "variant": "debug" }, "cpython-3.11.13+debug-linux-s390x-gnu": { @@ -15547,8 +16379,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "eff457ef514ffaf954fa2bfd63fde5fc128a908e5a0d72fe8dab0e4146867f54", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "dd849e7e5308066f03d1f2be307cdfd95d5c815aec9dc743bf53c98731005cd5", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64-gnu": { @@ -15563,8 +16395,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "23c7d6c58a3e9eb0055b847a72053082e1250b04c39ee0026738d0a2298d6dbb", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "6ed2ab536fce32ba93ddf3ea572c92aee3a5c12575f9096defbab858011a9810", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64-musl": { @@ -15579,8 +16411,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "c51dbba70ae11f65a0399d5690a4c1fbb52d9772fc8b1467ed836247225db3af", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "a4df9df180fa29800467eef491b3d22019aec3eca8160f9babd27b24cf6ebf39", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v2-gnu": { @@ -15595,8 +16427,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "479bc0f7b9bae4dde42ec848e508ecd8095f28ee4e89ef1f18e95ec2e29aa19d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "b27f28286c97e589521c496fe327e940c5ab99a406d652fe470008c2a525a159", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v2-musl": { @@ -15611,8 +16443,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "a25ddc1e2588842ada52fdf4211939d5e598defd3d45702ec0d9dfa30797060a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "9ffcf6f5b69805c47fb39c43810030cf1ff0fefab4b858734da75130f2184f7e", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v3-gnu": { @@ -15627,8 +16459,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "d9f819fe8cbd7895c9b9d591e55ca67b500875c945cc0a1278149267d8cdd803", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "c546e8dc6d21eb9e3fc8a849b67fe5564ebd69456c800e1e9ba685a6450e1db3", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v3-musl": { @@ -15643,8 +16475,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "dd22dd11e9bc4bbc716c1af20885c01a3d032eb1ce7bb74f9f939f6a08545ddc", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "190734e9714c4041a160d50240a1e5489fd416091bb2f4f0ae1e17e46a67f641", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v4-gnu": { @@ -15659,8 +16491,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "ab7171c7e0dcfdf7135aaed53169e71222cddc8c4133b7d51f898842bb924f0e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "629c39a382faed464041836b9299a2f3159e3cc5d07844f5cb5be8d579898166", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v4-musl": { @@ -15675,8 +16507,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.11.13%2B20250702-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "c02f0ef29ce93442ac3a61bbf3560c24d74d34b8edb46b166724ff139cde8f26", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "7909d1992f8bc7346b081f46a0d4c37e7ccabd041a947d89c17caa1cc497007b", "variant": "debug" }, "cpython-3.11.12-darwin-aarch64-none": { @@ -19467,8 +20299,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "4ad4b0c3b60c14750fb8d0ad758829cd1a54df318dc6d83c239c279443bb854c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "5076f23af532e6225b85106393a092c1e43c67605f5038a2687efe2608e999b0", "variant": null }, "cpython-3.10.18-darwin-x86_64-none": { @@ -19483,8 +20315,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "9c9833f4f13eed050a1440204b0477d652ae76c8e749bc26021928d5c6fcba2b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "8e9436c3aec957de1e79fd670b7c7801ad59f174a178a7e92964e4642ade8eda", "variant": null }, "cpython-3.10.18-linux-aarch64-gnu": { @@ -19499,8 +20331,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "6c315a5ed0b77457c494048269e71e36e0fae2a9354da0bbfc65f3d583a306fa", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "9e7581dc4e6e75135650551040d1ad9529bb1b7b2b6c2dbf9b80483507284a50", "variant": null }, "cpython-3.10.18-linux-armv7-gnueabi": { @@ -19515,8 +20347,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "c0aa7dfaef03330a1009fae6ed3696062a9c6b6a879de57643222911801f6b14", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "973db52fb00257045a4d3ea13c59c50588bc6f708b0a0230a2adb2154f710009", "variant": null }, "cpython-3.10.18-linux-armv7-gnueabihf": { @@ -19531,8 +20363,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "6a772781facf097fb9bb00fc16b9c77680fc583dbb04ef4f935f1139f5a3a818", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "87368650aa19e173da8b365231f75f1584f2d9e8b95d763b9c47f7fc053a644a", "variant": null }, "cpython-3.10.18-linux-powerpc64le-gnu": { @@ -19547,8 +20379,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "8bbc7cd369d3c3788ca46a66b0c9f0d227054f99b7db3966a547faa7e0ede99c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "cc3079246949bcef9be0118f58e6713fc8af2ba49927db015bc6f4d8fca6ab26", "variant": null }, "cpython-3.10.18-linux-riscv64-gnu": { @@ -19563,8 +20395,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "954603b1e72f7b697812bb821b9820f2d1ab21b9fb166201c068df28577f3967", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "73c6d8cf8eb865595ef232f5bb7d7a55cb0c861e2ee72a6b23e61409010bf6ee", "variant": null }, "cpython-3.10.18-linux-s390x-gnu": { @@ -19579,8 +20411,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "8d02a604f4ef13541a678b8f32b2955003f53471489460f867de3bbbd0b7b0a2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "668f8d911eec50bdd36996f3c0c098255fd90360e83d73efc383c136a93cbd30", "variant": null }, "cpython-3.10.18-linux-x86_64-gnu": { @@ -19595,8 +20427,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "780e5199279301cec595590e1a12549e615f5863e794db171897b996deb5db2b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "c6e79f2c78b893339c4fbb4f337647f5e14d491ca2c05ecec8f78187bfd9480c", "variant": null }, "cpython-3.10.18-linux-x86_64-musl": { @@ -19611,8 +20443,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "fac56a0d55b28dfada5b1b1ad12c38bca7fda14621f84d4dba599dfb697d0f6a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "cb6f4ea6cb5eef904d5a8fb4bcfee77bc34bca4946f8a12bab70c103f503f676", "variant": null }, "cpython-3.10.18-linux-x86_64_v2-gnu": { @@ -19627,8 +20459,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "b47af0eb09bd0ae5d5b33e0bfd3a121dd8bf042ffe61d03d54be27629db55a78", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "dbc05eadb1cdf504718688bb29367ab16fc0868c3b873031ea49b85e919a3bee", "variant": null }, "cpython-3.10.18-linux-x86_64_v2-musl": { @@ -19643,8 +20475,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "c59eac8b665419cc94c24807fd2654cc424f7f926a6b107a7e22a9599ba416ea", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "5c7ac0653d42d1ab391fec12c1f1f1d940c7ebe20013979d91d4651c3fcb62b9", "variant": null }, "cpython-3.10.18-linux-x86_64_v3-gnu": { @@ -19659,8 +20491,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "f2042831ec67633ad96f27407fee67b671bb5a589c8c8491dbb9420f58246db8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "1199924aba81e7475479b9e709e91f5cbb5cf3dc269cc0c30c27cf25cbfe8f01", "variant": null }, "cpython-3.10.18-linux-x86_64_v3-musl": { @@ -19675,8 +20507,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "2205ef12cd51afe189ac152b9413788eccc5e0d8c86b78f6c2209ab8d5ead0b8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "66a78c15f1f2cd0cfd0196edf323bdffe77481e6904751e125d4db23db78bad0", "variant": null }, "cpython-3.10.18-linux-x86_64_v4-gnu": { @@ -19691,8 +20523,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "f80c94a23c67b2cd7695fb58d3dd3bb4509cbe94bf3da9200dcc7f5c06939067", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "7c0aaa49f3a5b15689ae43d6cd4f418732ee95070aaa96dabf968bb3ac45b29e", "variant": null }, "cpython-3.10.18-linux-x86_64_v4-musl": { @@ -19707,8 +20539,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "2356bc9f121cb555921a10155126b53ca92e471e35e93644feae37ef6adbe91d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "b14649f4bdb22cf8b2c3656034687b9854f0ad0489018a65a1d44e886a000e96", "variant": null }, "cpython-3.10.18-windows-i686-none": { @@ -19723,8 +20555,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "16379aad0f72dffdcedc32240bceacf8c341c8ac9c49f1634a94bef3eb34ff91", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "e633c5093644502c477ba2391bde9bf23fb5d695aaa7de0e727b363592d81edf", "variant": null }, "cpython-3.10.18-windows-x86_64-none": { @@ -19739,8 +20571,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "1d9028a8b16a2dddfd0334a12195eb37653e4ba3dd4691059a58dc18c9c2bad5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "9b168333744e676d221d0e47b73328e38a78a080bbeff009db72d0eae201a3a7", "variant": null }, "cpython-3.10.18+debug-linux-aarch64-gnu": { @@ -19755,8 +20587,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "ba86c13891bba5395db087bad08e2175d4fe4f7c2592f4228c8302e89b1876ae", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "2997824229577882eb7f0000118c93d0fb12f97bee10bd7c41ed46b7123c6d5d", "variant": "debug" }, "cpython-3.10.18+debug-linux-armv7-gnueabi": { @@ -19771,8 +20603,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "da75e3b55503f9cc33e1476e4933457b42c5ac0a765321a8056278056f2c6032", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "5650962a60d540d9a71b6af917f78386ae69f4368f9b3537828b8368400aee8f", "variant": "debug" }, "cpython-3.10.18+debug-linux-armv7-gnueabihf": { @@ -19787,8 +20619,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "65c4d23b2507715b60f8157fda6651ad0490d38d3a354aa5e85c5401f7b791b5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "891540ab2a6e2534115787c95e06111176c2630dc261bad2169251924ec41fc6", "variant": "debug" }, "cpython-3.10.18+debug-linux-powerpc64le-gnu": { @@ -19803,8 +20635,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "1ea4b070dc8795316798e5dde4a45f9bcbd3b8907ece534e73164e9e82902817", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "7266278b47151f48b7b57790cda43aeb12bb1a776711fbb552a60ace2d9e68fc", "variant": "debug" }, "cpython-3.10.18+debug-linux-riscv64-gnu": { @@ -19819,8 +20651,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "2b9381ee30e69b0a7722a1b0917a4be8abc9b22d3542c918c8810d3bf10144f8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "ba07bece860b8f98da3740860f4e91de18d0e05a30f1970203f0d5f98489210c", "variant": "debug" }, "cpython-3.10.18+debug-linux-s390x-gnu": { @@ -19835,8 +20667,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "3ea8a041ed81fbc11e2781cc6b57ef0abf2ecd874374603153213d316da19e5e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "217a35c1c9ef9bfef37970587245ce06c3e63f92322b083e0baa7da2a82587cf", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64-gnu": { @@ -19851,8 +20683,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "afd58d81e22c5f96c7021c27aedb89bc3be3c40d58625035a5b7158bb464a89f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "1d485c1882d0ecefe858ef8db3864fb6b91a938941f3d7350c06f3b6a03734db", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64-musl": { @@ -19867,8 +20699,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "c82f5cb37140257016a05c92a83813c8ad85f108898c6076750b4bfc8e49052d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "cdbead37d85fff493e6eb3e6adf3d6935a721315b4711666db56d157e796396b", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v2-gnu": { @@ -19883,8 +20715,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "fdd72ff3418b1dd74fdc5514d392e309fe615739aafeeeed80276bfa28646e93", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "5ae93dac6ae65c7f13c355ce1fe28b78a0a9b272c428bb27f5dbf2a357275bc2", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v2-musl": { @@ -19899,8 +20731,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "d13113baa9f5749b5f70a2e4b396393363df1bba14c4fca6d13455ab92786f16", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "a588754cd0e959123c5beedd1d50cc849f8c3bed4908174a6f55730951a10241", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v3-gnu": { @@ -19915,8 +20747,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "7ac330ff09a193ef7e4a93751dd1bc243a8a2d35debdb9f1f4c967ee98be7c9b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "65976255591b39e428ae750050e398521a32bcdefb96053dd2cf9007165411da", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v3-musl": { @@ -19931,8 +20763,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "ffa713da073c0ac6b9d46e8c34f682c936c1ee6ecacfdaa369617d621bc5f800", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "fc8ba366396b3e6b5aca7e3ba449ad094350a533f31a0c99c6ed1ac0d41ef7d2", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v4-gnu": { @@ -19947,8 +20779,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "d22dc14204be742df984cd74b086c5bce23ea6071bbccf66e0a4e9373fb7e1fc", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "2bf6024c48b82b667dc3bab77d9ff143ac3983e75be94c32cdc22b9cd7e50d15", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v4-musl": { @@ -19963,8 +20795,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.10.18%2B20250702-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "f8b309b55988356eeb43b1d6daaaed44c3f2c7615abb015145e750cc81c84f13", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "41696205b706ea5b0ef89eefd695bfe87f44dae57f9318711892b1ceb144cff7", "variant": "debug" }, "cpython-3.10.17-darwin-aarch64-none": { @@ -24907,8 +25739,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "901b88f69f92c93991b03315f6e9853fdf6e57796b7f46eae2578f8e6cec7f79", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "aff1156fa5be26caf1ac2d4029936eb9379dc4351bb1d32d2120b10f2ba61747", "variant": null }, "cpython-3.9.23-darwin-x86_64-none": { @@ -24923,8 +25755,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "663403464b734f7fcb6697dc03a7bb4415f1bd7c29df8b0476d145e768b6e220", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "9de5325065b159e3e7daa53c133126df6b3eeed2316176d84e7761b01d16ba7f", "variant": null }, "cpython-3.9.23-linux-aarch64-gnu": { @@ -24939,8 +25771,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "fd037489d2d0006d9f74f20a751fd0369c61adf2c8ead32c9a572759162b3241", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "51fe6b026253b9f9c83205d1907572d7618ea47216e40a351d30eaa55f879c3e", "variant": null }, "cpython-3.9.23-linux-armv7-gnueabi": { @@ -24955,8 +25787,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "594b85658309561642361b1708aac18579817978ffdbb08f1c5f7040f9c30f28", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "1faeec85e15cd17acb90683bc42cc8bccdb5250816501863d3407713deb6215e", "variant": null }, "cpython-3.9.23-linux-armv7-gnueabihf": { @@ -24971,8 +25803,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "40eedb55eda5598dc9335728b70f7dff8b58be111b462e392cf2f8ba249c68ac", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "08261e7a2328c989409a7f0f4574bfca84adfab7e5db6556209642ebba55de5e", "variant": null }, "cpython-3.9.23-linux-powerpc64le-gnu": { @@ -24987,8 +25819,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "4c294d9bd701ffaa60440e0e1871c5570c690051b7c8f1b674f8e7fc2239e8c9", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "2ab4c6c616b23b2220829420028f90d0aa4f767ae60fcdf5d2edff08644bb5af", "variant": null }, "cpython-3.9.23-linux-riscv64-gnu": { @@ -25003,8 +25835,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "41c2dd0ab80b4ddd60a22fc775d87bec1e49c533ee0b0aec757e432df17c06ea", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "bea6c21421b016ca03e786f0fb91a03cc9d3f39aa8069785632efe3666e90df5", "variant": null }, "cpython-3.9.23-linux-s390x-gnu": { @@ -25019,8 +25851,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "530e5bb6e47f5e009768b96d9bed2d0c4fe21f1bc113a35571c6981922dd345f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "6715a5b8af51e76929c1f7a81c9085053243d2b4025bac29f8ec18301766d795", "variant": null }, "cpython-3.9.23-linux-x86_64-gnu": { @@ -25035,8 +25867,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "b11d434321025e814b07e171e17cb183b4fe02bddbec5e036882c85fb7020b18", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "ad39b79d0168f0f7cc5dbe14d99ff8d1068077f15cc2b03456fe3364630157e8", "variant": null }, "cpython-3.9.23-linux-x86_64-musl": { @@ -25051,8 +25883,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "996f66c44d75bf681d6d5c5d2f6315b7f0fff9e9e56b628bdf0f4d865be69a31", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "977af02740232123c385e7f8e70eb8acdcf8ffd4126526f9d3d8cb1bd20fd669", "variant": null }, "cpython-3.9.23-linux-x86_64_v2-gnu": { @@ -25067,8 +25899,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "9355e74e4922c9ffd62fadfd0d8949a1de860c14ad16db8ec80e04552219eeaa", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "ffbb92f9213591ab7b253c89d34218c3adab25327668b89bc6120038cc2b0a37", "variant": null }, "cpython-3.9.23-linux-x86_64_v2-musl": { @@ -25083,8 +25915,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "3548ddd479dc2ca6d108cba69c0e267a37664ff795d7ebc908836a3faacef9b1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "e53121074856e6ef4e8f3a865c2848d4287431a1d0ceef21fd389cc39649f917", "variant": null }, "cpython-3.9.23-linux-x86_64_v3-gnu": { @@ -25099,8 +25931,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "4364cf01c55eee28f5ca918cc9c20f3130cec3d20c45156623576986729e7a9f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "1856f202d42555e8e8709db0291bbfac5a896724734314746ef20c014cca8552", "variant": null }, "cpython-3.9.23-linux-x86_64_v3-musl": { @@ -25115,8 +25947,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "ac0f0cca348f51d29c3e9201e8cb35251b0eceb0e6d29ce2b652fc2bd912bf7c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "94f94fa20477b5088a147936c565c2b0a5a18e353d954ad6bbd5048e933d9a67", "variant": null }, "cpython-3.9.23-linux-x86_64_v4-gnu": { @@ -25131,8 +25963,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "4622c9b7aad91c6aa9d3b251a5721b52725866defb6132e9d8b0c7b05ebdd317", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "55209fe80fac7837837c5b4d310e71e1de822ca413465bf7589fabae5dd9ba7a", "variant": null }, "cpython-3.9.23-linux-x86_64_v4-musl": { @@ -25147,8 +25979,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "227544768f4214350a1051282a49e598a742bead5447ac7adfb1da488cf6b165", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "41e1237774abf02a8c3b33c365d959ba8529f6a845d93789e3fe7ba4203fb8c2", "variant": null }, "cpython-3.9.23-windows-i686-none": { @@ -25163,8 +25995,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "0029b916ac37b330d40c6fa13f507249660f0ceaaa34415bc691e705925b6d1b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "f8d558d6d260cc970f02e04f5b6555acd5148b1b2bef25d2c945ab2b8dfd3ce2", "variant": null }, "cpython-3.9.23-windows-x86_64-none": { @@ -25179,8 +26011,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "fd864f5f2aff6727250bd9104409a458146552f88d6ae7b78427aed719506b9c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "3a150e1126b1b7645a95ba06992d886cd03dab524d7c2660bd94bcf51f499fa1", "variant": null }, "cpython-3.9.23+debug-linux-aarch64-gnu": { @@ -25195,8 +26027,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "080edc8aca719b776e62e1803948390cc75392db8a416f3ebc3fa1b6ec219c8e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "51cfb2db5abdd1e10d2998289fbf3235352a61b4b6a3ef8ac4fbf4252ae09c78", "variant": "debug" }, "cpython-3.9.23+debug-linux-armv7-gnueabi": { @@ -25211,8 +26043,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "b187d469dd3c61efdac4ac4a9f9a17e01db860bef5e836251ad38e751bd2f2e9", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "369a0f68be191dbb45a3ca173c9589d77f973be3552f08225d03f5e013795d25", "variant": "debug" }, "cpython-3.9.23+debug-linux-armv7-gnueabihf": { @@ -25227,8 +26059,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "d4f4ae11a45a4f7821caca519880fe79a052bb8191cbc7678965304d5efea5a3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "0821af742c0187823ae3194c53b7590e7bf0524a14b94580300391e0b13bdd8a", "variant": "debug" }, "cpython-3.9.23+debug-linux-powerpc64le-gnu": { @@ -25243,8 +26075,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "2c389fc71513a2f75ef3a1a299a160d1a7d19f701f2a9635ece77454b2fddfb1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "45525a2d123981cb56f5fe4cd87e9bbe18c3fffe6b778313e8ef76f864315513", "variant": "debug" }, "cpython-3.9.23+debug-linux-riscv64-gnu": { @@ -25259,8 +26091,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "4c11855610bfe76f7dd003bcf3be0df9f656a41667c835df9da870b8ee91c465", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "9280d5f805d1f1ff992657af852a343f90cdaf7ef40287b55f48a73e409a4fe3", "variant": "debug" }, "cpython-3.9.23+debug-linux-s390x-gnu": { @@ -25275,8 +26107,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "90d4077a0907f4e491662b92184368b6b16f4b3623e618fdbd37ae6ceecb6813", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "340c153709d2d428d0604802983bd017079ea95f48ccbb8877e08c87b8c93f4f", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64-gnu": { @@ -25291,8 +26123,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "5a062251e9ee9f765373cb5eae61943bc214f8363392e3cffd235ca1a751ef98", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "e63909ea5cf383db126d5af9c3ba09fc68868104cf8db265723ad1220a5fafae", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64-musl": { @@ -25307,8 +26139,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "e5e5ef74bd58d9f0994e583830811ec3be9149276a1434753b07bd19d77e9417", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "1f58c434a2772e136506e517e412cc450359807a32742064d9ef3ec18ae1ef3e", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v2-gnu": { @@ -25323,8 +26155,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "3fc2ad7307cd0fb5e360baea3b598ed9218313f51f83063b4d085fcf6c85c7e0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "6702268ce25da3f547ed1f48ee20144d0cdc1db967a467f25d097f43cb52a25e", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v2-musl": { @@ -25339,8 +26171,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "ec069be5c7b2705b885993ed8f15f3e0456f445beeee1f372b65fdd89afc7cd1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "606eeb49821a06fb874527494f6493606e5f837cf56dba8235e75149ec53297b", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v3-gnu": { @@ -25355,8 +26187,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "071b71c4a41da3cde092d877e36ce55f4906246c9d0755a3a349717ad4b1d7a5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "11dcf8d92a18e609f32750ceb758a65855505a79907302142c8b70785c5c9a03", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v3-musl": { @@ -25371,8 +26203,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "cd3c0e2060fe94dcd346add4ee9f9053bcc35367cd2b69b46c126f4ac0681aed", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "d246a1a69cee5ec4bf467fb1ea42f6218925d3047afd3817b34fc3f8ad199200", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v4-gnu": { @@ -25387,8 +26219,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "3934b72131d7a00c5aeaec79c714315e6773bd4170596fb27265efb643444520", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "05b81fde271d35e97d5e411a2d9e232baa424a55c8ea6e09a15e1606c08833f4", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v4-musl": { @@ -25403,8 +26235,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250702/cpython-3.9.23%2B20250702-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "dd0957b5c94d98f94a267e3d4e8e6acc3561f9b7970532d69d533b3eb59c72e6", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "33e7411e88033865e8a4e9c995112cb3867f284102624b3ce1dbcdb4f4c03ea3", "variant": "debug" }, "cpython-3.9.22-darwin-aarch64-none": { diff --git a/crates/uv/tests/it/python_install.rs b/crates/uv/tests/it/python_install.rs index 7fd596cd8..0fc89df21 100644 --- a/crates/uv/tests/it/python_install.rs +++ b/crates/uv/tests/it/python_install.rs @@ -1444,8 +1444,8 @@ fn python_install_314() { ----- stdout ----- ----- stderr ----- - Installed Python 3.14.0b3 in [TIME] - + cpython-3.14.0b3-[PLATFORM] + Installed Python 3.14.0b4 in [TIME] + + cpython-3.14.0b4-[PLATFORM] "); // Install a specific pre-release @@ -1465,7 +1465,7 @@ fn python_install_314() { success: true exit_code: 0 ----- stdout ----- - [TEMP_DIR]/managed/cpython-3.14.0b3-[PLATFORM]/[INSTALL-BIN]/python + [TEMP_DIR]/managed/cpython-3.14.0b4-[PLATFORM]/[INSTALL-BIN]/python ----- stderr ----- "); @@ -1475,7 +1475,7 @@ fn python_install_314() { success: true exit_code: 0 ----- stdout ----- - [TEMP_DIR]/managed/cpython-3.14.0b3-[PLATFORM]/[INSTALL-BIN]/python + [TEMP_DIR]/managed/cpython-3.14.0b4-[PLATFORM]/[INSTALL-BIN]/python ----- stderr ----- "); @@ -1484,7 +1484,7 @@ fn python_install_314() { success: true exit_code: 0 ----- stdout ----- - [TEMP_DIR]/managed/cpython-3.14.0b3-[PLATFORM]/[INSTALL-BIN]/python + [TEMP_DIR]/managed/cpython-3.14.0b4-[PLATFORM]/[INSTALL-BIN]/python ----- stderr ----- "); From 2709c441a803dc0a335a77e569a16b47eb794d74 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 9 Jul 2025 06:50:31 -0500 Subject: [PATCH 165/349] Revert normalization of trailing slashes on index URLs (#14511) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reverts: - #14349 - #14346 - #14245 Retains the test cases. Includes a `find-links` test case. Supersedes - https://github.com/astral-sh/uv/pull/14387 - https://github.com/astral-sh/uv/pull/14503 We originally got a report at https://github.com/astral-sh/uv/issues/13707 that inclusion of a trailing slash on an index URL was causing lockfile churn despite having no semantic meaning and resolved the issue by adding normalization that stripped trailing slashes at parse time. We then discovered that, while there are not semantic differences for trailing slashes on Simple API index URLs, there are differences for some flat (or find links) indexes. As reported in https://github.com/astral-sh/uv/issues/14367, the change in https://github.com/astral-sh/uv/pull/14245 caused a regression for at least one user. We attempted to fix the regression via a few approaches. https://github.com/astral-sh/uv/pull/14387 attempted to differentiate between Simple API and flat index URL parsing, but failed to account for the `Deserialize` implementation, which always assumed Simple API-style index URLs and incorrectly trimmed trailing slashes in various cases where we deserialized the `IndexUrl` type from a file. I attempted to resolve this by performing a larger refactor, but it ended up being quite painful. In particular, the `Index` type was a blocker — we don't know the `IndexUrl` variant until we've parsed the `IndexFormat` and having a multi-stage deserializer is not appealing but adding a new intermediate type (i.e., `RawIndex`) is painful due to the pervasiveness of `Index`. Given that we've regressed behavior here and there's not a straight-forward fix, we're reverting the normalization entirely. https://github.com/astral-sh/uv/pull/14503 attempted to perform normalization at compare-time, but that means we'd fail to invalidate the lockfile when the a trailing slash was added or removed and given that a trailing slash has semantic meaning for a find-links URL... we'd have another correctness problem. After this revert, we'll retain all index URLs verbatim. The downside to this approach is that we'll be adding a bunch of trailing slashes back to lockfiles that we previously normalized out, and we'll be reverting our fix for users with inconsistent trailing slashes on their index URLs. Users affected by the original motivating issue should use consistent trailing slashes on their URLs, as they do frequently have semantic meaning. We may want to revisit normalization and type aware index URL parsing as part of a larger change. Closes https://github.com/astral-sh/uv/issues/14367 --- crates/uv-distribution-types/src/file.rs | 54 ---- crates/uv-distribution-types/src/index_url.rs | 17 +- crates/uv-pep508/src/verbatim_url.rs | 5 - crates/uv-resolver/src/lock/mod.rs | 12 +- crates/uv/tests/it/edit.rs | 24 +- crates/uv/tests/it/lock.rs | 190 +++++++++-- crates/uv/tests/it/lock_scenarios.rs | 306 +++++++++--------- crates/uv/tests/it/sync.rs | 2 +- 8 files changed, 344 insertions(+), 266 deletions(-) diff --git a/crates/uv-distribution-types/src/file.rs b/crates/uv-distribution-types/src/file.rs index a75af3977..89da7b87e 100644 --- a/crates/uv-distribution-types/src/file.rs +++ b/crates/uv-distribution-types/src/file.rs @@ -169,26 +169,6 @@ impl UrlString { .map(|(path, _)| Cow::Owned(UrlString(SmallString::from(path)))) .unwrap_or(Cow::Borrowed(self)) } - - /// Return the [`UrlString`] (as a [`Cow`]) with trailing slash removed. - /// - /// This matches the semantics of [`Url::pop_if_empty`], which will not trim a trailing slash if - /// it's the only path segment, e.g., `https://example.com/` would be unchanged. - #[must_use] - pub fn without_trailing_slash(&self) -> Cow<'_, Self> { - self.as_ref() - .strip_suffix('/') - .filter(|path| { - // Only strip the trailing slash if there's _another_ trailing slash that isn't a - // part of the scheme. - path.split_once("://") - .map(|(_scheme, rest)| rest) - .unwrap_or(path) - .contains('/') - }) - .map(|path| Cow::Owned(UrlString(SmallString::from(path)))) - .unwrap_or(Cow::Borrowed(self)) - } } impl AsRef for UrlString { @@ -283,38 +263,4 @@ mod tests { ); assert!(matches!(url.without_fragment(), Cow::Owned(_))); } - - #[test] - fn without_trailing_slash() { - // Borrows a URL without a slash - let url = UrlString("https://example.com/path".into()); - assert_eq!(&*url.without_trailing_slash(), &url); - assert!(matches!(url.without_trailing_slash(), Cow::Borrowed(_))); - - // Removes the trailing slash if present on the URL - let url = UrlString("https://example.com/path/".into()); - assert_eq!( - &*url.without_trailing_slash(), - &UrlString("https://example.com/path".into()) - ); - assert!(matches!(url.without_trailing_slash(), Cow::Owned(_))); - - // Does not remove a trailing slash if it's the only path segment - let url = UrlString("https://example.com/".into()); - assert_eq!(&*url.without_trailing_slash(), &url); - assert!(matches!(url.without_trailing_slash(), Cow::Borrowed(_))); - - // Does not remove a trailing slash if it's the only path segment with a missing scheme - let url = UrlString("example.com/".into()); - assert_eq!(&*url.without_trailing_slash(), &url); - assert!(matches!(url.without_trailing_slash(), Cow::Borrowed(_))); - - // Removes the trailing slash when the scheme is missing - let url = UrlString("example.com/path/".into()); - assert_eq!( - &*url.without_trailing_slash(), - &UrlString("example.com/path".into()) - ); - assert!(matches!(url.without_trailing_slash(), Cow::Owned(_))); - } } diff --git a/crates/uv-distribution-types/src/index_url.rs b/crates/uv-distribution-types/src/index_url.rs index 0290018f1..1c8cd0a76 100644 --- a/crates/uv-distribution-types/src/index_url.rs +++ b/crates/uv-distribution-types/src/index_url.rs @@ -38,8 +38,6 @@ impl IndexUrl { /// /// If no root directory is provided, relative paths are resolved against the current working /// directory. - /// - /// Normalizes non-file URLs by removing trailing slashes for consistency. pub fn parse(path: &str, root_dir: Option<&Path>) -> Result { let url = match split_scheme(path) { Some((scheme, ..)) => { @@ -258,20 +256,13 @@ impl<'de> serde::de::Deserialize<'de> for IndexUrl { } impl From for IndexUrl { - fn from(mut url: VerbatimUrl) -> Self { + fn from(url: VerbatimUrl) -> Self { if url.scheme() == "file" { Self::Path(Arc::new(url)) + } else if *url.raw() == *PYPI_URL { + Self::Pypi(Arc::new(url)) } else { - // Remove trailing slashes for consistency. They'll be re-added if necessary when - // querying the Simple API. - if let Ok(mut path_segments) = url.raw_mut().path_segments_mut() { - path_segments.pop_if_empty(); - } - if *url.raw() == *PYPI_URL { - Self::Pypi(Arc::new(url)) - } else { - Self::Url(Arc::new(url)) - } + Self::Url(Arc::new(url)) } } } diff --git a/crates/uv-pep508/src/verbatim_url.rs b/crates/uv-pep508/src/verbatim_url.rs index c800ba10c..37d07b40b 100644 --- a/crates/uv-pep508/src/verbatim_url.rs +++ b/crates/uv-pep508/src/verbatim_url.rs @@ -171,11 +171,6 @@ impl VerbatimUrl { &self.url } - /// Return a mutable reference to the underlying [`DisplaySafeUrl`]. - pub fn raw_mut(&mut self) -> &mut DisplaySafeUrl { - &mut self.url - } - /// Convert a [`VerbatimUrl`] into a [`DisplaySafeUrl`]. pub fn to_url(&self) -> DisplaySafeUrl { self.url.clone() diff --git a/crates/uv-resolver/src/lock/mod.rs b/crates/uv-resolver/src/lock/mod.rs index beeadc912..7ca100fd8 100644 --- a/crates/uv-resolver/src/lock/mod.rs +++ b/crates/uv-resolver/src/lock/mod.rs @@ -1478,11 +1478,9 @@ impl Lock { if let Source::Registry(index) = &package.id.source { match index { RegistrySource::Url(url) => { - // Normalize URL before validating. - let url = url.without_trailing_slash(); if remotes .as_ref() - .is_some_and(|remotes| !remotes.contains(&url)) + .is_some_and(|remotes| !remotes.contains(url)) { let name = &package.id.name; let version = &package @@ -1490,11 +1488,7 @@ impl Lock { .version .as_ref() .expect("version for registry source"); - return Ok(SatisfiesResult::MissingRemoteIndex( - name, - version, - url.into_owned(), - )); + return Ok(SatisfiesResult::MissingRemoteIndex(name, version, url)); } } RegistrySource::Path(path) => { @@ -1799,7 +1793,7 @@ pub enum SatisfiesResult<'lock> { /// The lockfile is missing a workspace member. MissingRoot(PackageName), /// The lockfile referenced a remote index that was not provided - MissingRemoteIndex(&'lock PackageName, &'lock Version, UrlString), + MissingRemoteIndex(&'lock PackageName, &'lock Version, &'lock UrlString), /// The lockfile referenced a local index that was not provided MissingLocalIndex(&'lock PackageName, &'lock Version, &'lock Path), /// A package in the lockfile contains different `requires-dist` metadata than expected. diff --git a/crates/uv/tests/it/edit.rs b/crates/uv/tests/it/edit.rs index 0ae2a07a6..2aa5b651b 100644 --- a/crates/uv/tests/it/edit.rs +++ b/crates/uv/tests/it/edit.rs @@ -4374,7 +4374,7 @@ fn add_lower_bound_local() -> Result<()> { filters => context.filters(), }, { assert_snapshot!( - pyproject_toml, @r#" + pyproject_toml, @r###" [project] name = "project" version = "0.1.0" @@ -4384,8 +4384,8 @@ fn add_lower_bound_local() -> Result<()> { ] [[tool.uv.index]] - url = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" - "# + url = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" + "### ); }); @@ -4403,7 +4403,7 @@ fn add_lower_bound_local() -> Result<()> { [[package]] name = "local-simple-a" version = "1.2.3+foo" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/local_simple_a-1.2.3+foo.tar.gz", hash = "sha256:ebd55c4a79d0a5759126657cb289ff97558902abcfb142e036b993781497edac" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/local_simple_a-1.2.3+foo-py3-none-any.whl", hash = "sha256:6f30e2e709b3e171cd734bb58705229a582587c29e0a7041227435583c7224cc" }, @@ -9259,7 +9259,7 @@ fn add_index_with_trailing_slash() -> Result<()> { filters => context.filters(), }, { assert_snapshot!( - pyproject_toml, @r#" + pyproject_toml, @r###" [project] name = "project" version = "0.1.0" @@ -9272,8 +9272,8 @@ fn add_index_with_trailing_slash() -> Result<()> { constraint-dependencies = ["markupsafe<3"] [[tool.uv.index]] - url = "https://pypi.org/simple" - "# + url = "https://pypi.org/simple/" + "### ); }); @@ -9297,7 +9297,7 @@ fn add_index_with_trailing_slash() -> Result<()> { [[package]] name = "iniconfig" version = "2.0.0" - source = { registry = "https://pypi.org/simple" } + 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" }, @@ -11194,7 +11194,7 @@ fn repeated_index_cli_reversed() -> Result<()> { filters => context.filters(), }, { assert_snapshot!( - pyproject_toml, @r#" + pyproject_toml, @r###" [project] name = "project" version = "0.1.0" @@ -11204,8 +11204,8 @@ fn repeated_index_cli_reversed() -> Result<()> { ] [[tool.uv.index]] - url = "https://test.pypi.org/simple" - "# + url = "https://test.pypi.org/simple/" + "### ); }); @@ -11226,7 +11226,7 @@ fn repeated_index_cli_reversed() -> Result<()> { [[package]] name = "iniconfig" version = "2.0.0" - source = { registry = "https://test.pypi.org/simple" } + source = { registry = "https://test.pypi.org/simple/" } sdist = { url = "https://test-files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646, upload-time = "2023-01-07T11:08:16.826Z" } wheels = [ { url = "https://test-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:14.843Z" }, diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index 5851022b8..cb9c72c03 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -15500,7 +15500,7 @@ fn lock_add_empty_dependency_group() -> Result<()> { /// Use a trailing slash on the declared index. #[test] -fn lock_trailing_slash() -> Result<()> { +fn lock_trailing_slash_index_url() -> Result<()> { let context = TestContext::new("3.12"); let pyproject_toml = context.temp_dir.child("pyproject.toml"); @@ -15543,7 +15543,7 @@ fn lock_trailing_slash() -> Result<()> { [[package]] name = "anyio" version = "3.7.0" - source = { registry = "https://pypi.org/simple" } + source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "idna" }, { name = "sniffio" }, @@ -15556,7 +15556,7 @@ fn lock_trailing_slash() -> Result<()> { [[package]] name = "idna" version = "3.6" - source = { registry = "https://pypi.org/simple" } + 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" }, @@ -15576,7 +15576,7 @@ fn lock_trailing_slash() -> Result<()> { [[package]] name = "sniffio" version = "1.3.1" - source = { registry = "https://pypi.org/simple" } + source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, @@ -28310,10 +28310,10 @@ fn lock_conflict_for_disjoint_platform() -> Result<()> { Ok(()) } -/// Add a package with an `--index` URL with no trailing slash. Run `uv lock --locked` -/// with a `pyproject.toml` with that same URL but with a trailing slash. +/// Add a package with an `--index` URL with no trailing slash while an index with the same URL +/// exists with a trailing slash in the `pyproject.toml`. #[test] -fn lock_with_inconsistent_trailing_slash() -> Result<()> { +fn lock_trailing_slash_index_url_in_pyproject_not_index_argument() -> Result<()> { let context = TestContext::new("3.12"); let pyproject_toml = context.temp_dir.child("pyproject.toml"); @@ -28408,20 +28408,22 @@ fn lock_with_inconsistent_trailing_slash() -> Result<()> { // Re-run with `--locked`. uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" - success: true - exit_code: 0 + success: false + exit_code: 2 ----- stdout ----- ----- stderr ----- Resolved 4 packages in [TIME] + error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. "); Ok(()) } -/// Run `uv lock --locked` with a lockfile with trailing slashes on index URLs. +/// Run `uv lock --locked` with a lockfile with trailing slashes on the index URL but a +/// `pyproject.toml` without a trailing slash on the index URL. #[test] -fn lock_with_index_trailing_slashes_in_lockfile() -> Result<()> { +fn lock_trailing_slash_index_url_in_lockfile_not_pyproject() -> Result<()> { let context = TestContext::new("3.12"); let pyproject_toml = context.temp_dir.child("pyproject.toml"); @@ -28497,20 +28499,22 @@ fn lock_with_index_trailing_slashes_in_lockfile() -> Result<()> { // Run `uv lock --locked`. uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" - success: true - exit_code: 0 + success: false + exit_code: 2 ----- stdout ----- ----- stderr ----- Resolved 4 packages in [TIME] + error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. "); Ok(()) } -/// Run `uv lock --locked` with a lockfile with trailing slashes on index URLs. +/// Run `uv lock --locked` with `pyproject.toml` with trailing slashes on the index URL but a +/// lockfile without trailing slashes on the index URL. #[test] -fn lock_with_index_trailing_slashes_in_pyproject_toml() -> Result<()> { +fn lock_trailing_slash_index_url_in_pyproject_and_not_lockfile() -> Result<()> { let context = TestContext::new("3.12"); let pyproject_toml = context.temp_dir.child("pyproject.toml"); @@ -28586,20 +28590,22 @@ fn lock_with_index_trailing_slashes_in_pyproject_toml() -> Result<()> { // Run `uv lock --locked`. uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" - success: true - exit_code: 0 + success: false + exit_code: 2 ----- stdout ----- ----- stderr ----- Resolved 4 packages in [TIME] + error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. "); Ok(()) } -/// Run `uv lock --locked` with a lockfile with trailing slashes on index URLs. +/// Run `uv lock --locked` with a lockfile and `pyproject.toml` with trailing slashes on the index +/// URL. #[test] -fn lock_with_index_trailing_slashes_in_lockfile_and_pyproject_toml() -> Result<()> { +fn lock_trailing_slash_index_url_in_lockfile_and_pyproject_toml() -> Result<()> { let context = TestContext::new("3.12"); let pyproject_toml = context.temp_dir.child("pyproject.toml"); @@ -28686,6 +28692,152 @@ fn lock_with_index_trailing_slashes_in_lockfile_and_pyproject_toml() -> Result<( Ok(()) } +#[test] +fn lock_trailing_slash_find_links() -> Result<()> { + let context = TestContext::new("3.12"); + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["packaging==23.2"] + [tool.uv] + no-index = true + find-links = ["https://pypi.org/simple/packaging"] + "#, + )?; + + uv_snapshot!(context.filters(), context.lock(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + "); + + let lock = context.read("uv.lock"); + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + lock, @r#" + version = 1 + revision = 2 + requires-python = ">=3.12" + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [[package]] + name = "packaging" + version = "23.2" + source = { registry = "https://pypi.org/simple/packaging" } + sdist = { url = "https://files.pythonhosted.org/packages/fb/2b/9b9c33ffed44ee921d0967086d653047286054117d584f1b1a7c22ceaf7b/packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5" } + wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/1a/610693ac4ee14fcdf2d9bf3c493370e4f2ef7ae2e19217d7a237ff42367d/packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" }, + ] + + [[package]] + name = "project" + version = "0.1.0" + source = { virtual = "." } + dependencies = [ + { name = "packaging" }, + ] + + [package.metadata] + requires-dist = [{ name = "packaging", specifier = "==23.2" }] + "# + ); + }); + + // Re-run with `--locked`. + uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + "); + + // Add a trailing slash, which should invalidate the lockfile + 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 = ["packaging==23.2"] + [tool.uv] + no-index = true + find-links = ["https://pypi.org/simple/packaging/"] + "#, + )?; + + // Re-run with `--locked` + uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + "); + + uv_snapshot!(context.filters(), context.lock(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + "); + + let lock = context.read("uv.lock"); + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + lock, @r#" + version = 1 + revision = 2 + requires-python = ">=3.12" + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [[package]] + name = "packaging" + version = "23.2" + source = { registry = "https://pypi.org/simple/packaging/" } + sdist = { url = "https://files.pythonhosted.org/packages/fb/2b/9b9c33ffed44ee921d0967086d653047286054117d584f1b1a7c22ceaf7b/packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5" } + wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/1a/610693ac4ee14fcdf2d9bf3c493370e4f2ef7ae2e19217d7a237ff42367d/packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" }, + ] + + [[package]] + name = "project" + version = "0.1.0" + source = { virtual = "." } + dependencies = [ + { name = "packaging" }, + ] + + [package.metadata] + requires-dist = [{ name = "packaging", specifier = "==23.2" }] + "# + ); + }); + + Ok(()) +} + #[test] fn lock_prefix_match() -> Result<()> { let context = TestContext::new("3.12"); diff --git a/crates/uv/tests/it/lock_scenarios.rs b/crates/uv/tests/it/lock_scenarios.rs index 801214fa5..3be986ad1 100644 --- a/crates/uv/tests/it/lock_scenarios.rs +++ b/crates/uv/tests/it/lock_scenarios.rs @@ -158,7 +158,7 @@ fn wrong_backtracking_basic() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/wrong_backtracking_basic_a-1.0.0.tar.gz", hash = "sha256:5251a827291d4e5b7ca11c742df3aa26802cc55442e3f5fc307ff3423b8f9295" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/wrong_backtracking_basic_a-1.0.0-py3-none-any.whl", hash = "sha256:d9a7ee79b176cd36c9db03e36bc3325856dd4fb061aefc6159eecad6e8776e88" }, @@ -167,7 +167,7 @@ fn wrong_backtracking_basic() -> Result<()> { [[package]] name = "package-b" version = "2.0.9" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } dependencies = [ { name = "package-a" }, ] @@ -340,7 +340,7 @@ fn wrong_backtracking_indirect() -> Result<()> { [[package]] name = "package-a" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/wrong_backtracking_indirect_a-2.0.0.tar.gz", hash = "sha256:5891b5a45aac67b3afb90f66913d7ced2ada7cad1676fe427136b7324935bb1e" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/wrong_backtracking_indirect_a-2.0.0-py3-none-any.whl", hash = "sha256:68cb37193f4b2277630ad083522f59ac0449cb1c59e943884d04cc0e2a04cba7" }, @@ -349,7 +349,7 @@ fn wrong_backtracking_indirect() -> Result<()> { [[package]] name = "package-b" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } dependencies = [ { name = "package-b-inner" }, ] @@ -361,7 +361,7 @@ fn wrong_backtracking_indirect() -> Result<()> { [[package]] name = "package-b-inner" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } dependencies = [ { name = "package-too-old" }, ] @@ -373,7 +373,7 @@ fn wrong_backtracking_indirect() -> Result<()> { [[package]] name = "package-too-old" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/wrong_backtracking_indirect_too_old-1.0.0.tar.gz", hash = "sha256:1b674a931c34e29d20f22e9b92206b648769fa9e35770ab680466dbaa1335090" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/wrong_backtracking_indirect_too_old-1.0.0-py3-none-any.whl", hash = "sha256:15f8fe39323691c883c3088f8873220944428210a74db080f60a61a74c1fc6b0" }, @@ -477,7 +477,7 @@ fn fork_allows_non_conflicting_non_overlapping_dependencies() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_allows_non_conflicting_non_overlapping_dependencies_a-1.0.0.tar.gz", hash = "sha256:dd40a6bd59fbeefbf9f4936aec3df6fb6017e57d334f85f482ae5dd03ae353b9" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_allows_non_conflicting_non_overlapping_dependencies_a-1.0.0-py3-none-any.whl", hash = "sha256:8111e996c2a4e04c7a7cf91cf6f8338f5195c22ecf2303d899c4ef4e718a8175" }, @@ -592,7 +592,7 @@ fn fork_allows_non_conflicting_repeated_dependencies() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_allows_non_conflicting_repeated_dependencies_a-1.0.0.tar.gz", hash = "sha256:45ca30f1f66eaf6790198fad279b6448719092f2128f23b99f2ede0d6dde613b" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_allows_non_conflicting_repeated_dependencies_a-1.0.0-py3-none-any.whl", hash = "sha256:fc3f6d2fab10d1bb4f52bd9a7de69dc97ed1792506706ca78bdc9e95d6641a6b" }, @@ -699,7 +699,7 @@ fn fork_basic() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "sys_platform == 'darwin'", ] @@ -711,7 +711,7 @@ fn fork_basic() -> Result<()> { [[package]] name = "package-a" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -725,8 +725,8 @@ fn fork_basic() -> Result<()> { version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'darwin'" }, - { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, + { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'darwin'" }, + { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, ] [package.metadata] @@ -1002,7 +1002,7 @@ fn fork_filter_sibling_dependencies() -> Result<()> { [[package]] name = "package-a" version = "4.3.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "sys_platform == 'darwin'", ] @@ -1014,7 +1014,7 @@ fn fork_filter_sibling_dependencies() -> Result<()> { [[package]] name = "package-a" version = "4.4.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -1026,9 +1026,9 @@ fn fork_filter_sibling_dependencies() -> Result<()> { [[package]] name = "package-b" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } dependencies = [ - { name = "package-d", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, + { name = "package-d", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_filter_sibling_dependencies_b-1.0.0.tar.gz", hash = "sha256:af3f861d6df9a2bbad55bae02acf17384ea2efa1abbf19206ac56cb021814613" } wheels = [ @@ -1038,9 +1038,9 @@ fn fork_filter_sibling_dependencies() -> Result<()> { [[package]] name = "package-c" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } dependencies = [ - { name = "package-d", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'darwin'" }, + { name = "package-d", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_filter_sibling_dependencies_c-1.0.0.tar.gz", hash = "sha256:c03742ca6e81c2a5d7d8cb72d1214bf03b2925e63858a19097f17d3e1a750192" } wheels = [ @@ -1050,7 +1050,7 @@ fn fork_filter_sibling_dependencies() -> Result<()> { [[package]] name = "package-d" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -1062,7 +1062,7 @@ fn fork_filter_sibling_dependencies() -> Result<()> { [[package]] name = "package-d" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "sys_platform == 'darwin'", ] @@ -1076,8 +1076,8 @@ fn fork_filter_sibling_dependencies() -> Result<()> { version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "package-a", version = "4.3.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'darwin'" }, - { name = "package-a", version = "4.4.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, + { name = "package-a", version = "4.3.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'darwin'" }, + { name = "package-a", version = "4.4.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, { name = "package-b", marker = "sys_platform == 'linux'" }, { name = "package-c", marker = "sys_platform == 'darwin'" }, ] @@ -1180,7 +1180,7 @@ fn fork_upgrade() -> Result<()> { [[package]] name = "package-bar" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_upgrade_bar-2.0.0.tar.gz", hash = "sha256:2e7b5370d7be19b5af56092a8364a2718a7b8516142a12a95656b82d1b9c8cbc" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_upgrade_bar-2.0.0-py3-none-any.whl", hash = "sha256:d8ce562bf363e849fbf4add170a519b5412ab63e378fb4b7ea290183c77616fc" }, @@ -1189,7 +1189,7 @@ fn fork_upgrade() -> Result<()> { [[package]] name = "package-foo" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } dependencies = [ { name = "package-bar" }, ] @@ -1310,7 +1310,7 @@ fn fork_incomplete_markers() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "python_full_version < '3.10'", ] @@ -1322,7 +1322,7 @@ fn fork_incomplete_markers() -> Result<()> { [[package]] name = "package-a" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "python_full_version >= '3.11'", ] @@ -1334,7 +1334,7 @@ fn fork_incomplete_markers() -> Result<()> { [[package]] name = "package-b" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } dependencies = [ { name = "package-c", marker = "python_full_version == '3.10.*'" }, ] @@ -1346,7 +1346,7 @@ fn fork_incomplete_markers() -> Result<()> { [[package]] name = "package-c" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_incomplete_markers_c-1.0.0.tar.gz", hash = "sha256:ecc02ea1cc8d3b561c8dcb9d2ba1abcdae2dd32de608bf8e8ed2878118426022" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_incomplete_markers_c-1.0.0-py3-none-any.whl", hash = "sha256:03fa287aa4cb78457211cb3df7459b99ba1ee2259aae24bc745eaab45e7eaaee" }, @@ -1357,8 +1357,8 @@ fn fork_incomplete_markers() -> Result<()> { version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "python_full_version < '3.10'" }, - { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "python_full_version >= '3.11'" }, + { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "python_full_version < '3.10'" }, + { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "python_full_version >= '3.11'" }, { name = "package-b" }, ] @@ -1462,7 +1462,7 @@ fn fork_marker_accrue() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } dependencies = [ { name = "package-c", marker = "sys_platform == 'linux'" }, ] @@ -1474,7 +1474,7 @@ fn fork_marker_accrue() -> Result<()> { [[package]] name = "package-b" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } dependencies = [ { name = "package-c", marker = "sys_platform == 'darwin'" }, ] @@ -1486,7 +1486,7 @@ fn fork_marker_accrue() -> Result<()> { [[package]] name = "package-c" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_marker_accrue_c-1.0.0.tar.gz", hash = "sha256:a3e09ac3dc8e787a08ebe8d5d6072e09720c76cbbcb76a6645d6f59652742015" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_marker_accrue_c-1.0.0-py3-none-any.whl", hash = "sha256:b0c8719d38c91b2a8548bd065b1d2153fbe031b37775ed244e76fe5bdfbb502e" }, @@ -1680,15 +1680,15 @@ fn fork_marker_inherit_combined_allowed() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "implementation_name == 'pypy' and sys_platform == 'darwin'", "implementation_name == 'cpython' and sys_platform == 'darwin'", "implementation_name != 'cpython' and implementation_name != 'pypy' and sys_platform == 'darwin'", ] dependencies = [ - { name = "package-b", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "implementation_name == 'pypy' and sys_platform == 'darwin'" }, - { name = "package-b", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "implementation_name == 'cpython' and sys_platform == 'darwin'" }, + { name = "package-b", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "implementation_name == 'pypy' and sys_platform == 'darwin'" }, + { name = "package-b", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "implementation_name == 'cpython' and sys_platform == 'darwin'" }, ] sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_marker_inherit_combined_allowed_a-1.0.0.tar.gz", hash = "sha256:c7232306e8597d46c3fe53a3b1472f99b8ff36b3169f335ba0a5b625e193f7d4" } wheels = [ @@ -1698,7 +1698,7 @@ fn fork_marker_inherit_combined_allowed() -> Result<()> { [[package]] name = "package-a" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -1710,7 +1710,7 @@ fn fork_marker_inherit_combined_allowed() -> Result<()> { [[package]] name = "package-b" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "implementation_name == 'pypy' and sys_platform == 'darwin'", ] @@ -1725,7 +1725,7 @@ fn fork_marker_inherit_combined_allowed() -> Result<()> { [[package]] name = "package-b" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "implementation_name == 'cpython' and sys_platform == 'darwin'", ] @@ -1737,7 +1737,7 @@ fn fork_marker_inherit_combined_allowed() -> Result<()> { [[package]] name = "package-c" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_marker_inherit_combined_allowed_c-1.0.0.tar.gz", hash = "sha256:7ce8efca029cfa952e64f55c2d47fe33975c7f77ec689384bda11cbc3b7ef1db" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_marker_inherit_combined_allowed_c-1.0.0-py3-none-any.whl", hash = "sha256:6a6b776dedabceb6a6c4f54a5d932076fa3fed1380310491999ca2d31e13b41c" }, @@ -1748,8 +1748,8 @@ fn fork_marker_inherit_combined_allowed() -> Result<()> { version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'darwin'" }, - { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, + { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'darwin'" }, + { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, ] [package.metadata] @@ -1866,15 +1866,15 @@ fn fork_marker_inherit_combined_disallowed() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "implementation_name == 'pypy' and sys_platform == 'darwin'", "implementation_name == 'cpython' and sys_platform == 'darwin'", "implementation_name != 'cpython' and implementation_name != 'pypy' and sys_platform == 'darwin'", ] dependencies = [ - { name = "package-b", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "implementation_name == 'pypy' and sys_platform == 'darwin'" }, - { name = "package-b", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "implementation_name == 'cpython' and sys_platform == 'darwin'" }, + { name = "package-b", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "implementation_name == 'pypy' and sys_platform == 'darwin'" }, + { name = "package-b", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "implementation_name == 'cpython' and sys_platform == 'darwin'" }, ] sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_marker_inherit_combined_disallowed_a-1.0.0.tar.gz", hash = "sha256:92081d91570582f3a94ed156f203de53baca5b3fdc350aa1c831c7c42723e798" } wheels = [ @@ -1884,7 +1884,7 @@ fn fork_marker_inherit_combined_disallowed() -> Result<()> { [[package]] name = "package-a" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -1896,7 +1896,7 @@ fn fork_marker_inherit_combined_disallowed() -> Result<()> { [[package]] name = "package-b" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "implementation_name == 'pypy' and sys_platform == 'darwin'", ] @@ -1908,7 +1908,7 @@ fn fork_marker_inherit_combined_disallowed() -> Result<()> { [[package]] name = "package-b" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "implementation_name == 'cpython' and sys_platform == 'darwin'", ] @@ -1922,8 +1922,8 @@ fn fork_marker_inherit_combined_disallowed() -> Result<()> { version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'darwin'" }, - { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, + { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'darwin'" }, + { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, ] [package.metadata] @@ -2041,15 +2041,15 @@ fn fork_marker_inherit_combined() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "implementation_name == 'pypy' and sys_platform == 'darwin'", "implementation_name == 'cpython' and sys_platform == 'darwin'", "implementation_name != 'cpython' and implementation_name != 'pypy' and sys_platform == 'darwin'", ] dependencies = [ - { name = "package-b", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "implementation_name == 'pypy' and sys_platform == 'darwin'" }, - { name = "package-b", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "implementation_name == 'cpython' and sys_platform == 'darwin'" }, + { name = "package-b", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "implementation_name == 'pypy' and sys_platform == 'darwin'" }, + { name = "package-b", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "implementation_name == 'cpython' and sys_platform == 'darwin'" }, ] sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_marker_inherit_combined_a-1.0.0.tar.gz", hash = "sha256:2ec4c9dbb7078227d996c344b9e0c1b365ed0000de9527b2ba5b616233636f07" } wheels = [ @@ -2059,7 +2059,7 @@ fn fork_marker_inherit_combined() -> Result<()> { [[package]] name = "package-a" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -2071,7 +2071,7 @@ fn fork_marker_inherit_combined() -> Result<()> { [[package]] name = "package-b" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "implementation_name == 'pypy' and sys_platform == 'darwin'", ] @@ -2083,7 +2083,7 @@ fn fork_marker_inherit_combined() -> Result<()> { [[package]] name = "package-b" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "implementation_name == 'cpython' and sys_platform == 'darwin'", ] @@ -2097,8 +2097,8 @@ fn fork_marker_inherit_combined() -> Result<()> { version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'darwin'" }, - { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, + { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'darwin'" }, + { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, ] [package.metadata] @@ -2205,7 +2205,7 @@ fn fork_marker_inherit_isolated() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "sys_platform == 'darwin'", ] @@ -2217,7 +2217,7 @@ fn fork_marker_inherit_isolated() -> Result<()> { [[package]] name = "package-a" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -2232,7 +2232,7 @@ fn fork_marker_inherit_isolated() -> Result<()> { [[package]] name = "package-b" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_marker_inherit_isolated_b-1.0.0.tar.gz", hash = "sha256:96f8c3cabc5795e08a064c89ec76a4bfba8afe3c13d647161b4a1568b4584ced" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_marker_inherit_isolated_b-1.0.0-py3-none-any.whl", hash = "sha256:c8affc2f13f9bcd08b3d1601a21a1781ea14d52a8cddc708b29428c9c3d53ea5" }, @@ -2243,8 +2243,8 @@ fn fork_marker_inherit_isolated() -> Result<()> { version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'darwin'" }, - { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, + { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'darwin'" }, + { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, ] [package.metadata] @@ -2359,7 +2359,7 @@ fn fork_marker_inherit_transitive() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "sys_platform == 'darwin'", ] @@ -2374,7 +2374,7 @@ fn fork_marker_inherit_transitive() -> Result<()> { [[package]] name = "package-a" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -2386,7 +2386,7 @@ fn fork_marker_inherit_transitive() -> Result<()> { [[package]] name = "package-b" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } dependencies = [ { name = "package-c", marker = "sys_platform == 'darwin'" }, ] @@ -2398,7 +2398,7 @@ fn fork_marker_inherit_transitive() -> Result<()> { [[package]] name = "package-c" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_marker_inherit_transitive_c-1.0.0.tar.gz", hash = "sha256:58bb788896b2297f2948f51a27fc48cfe44057c687a3c0c4d686b107975f7f32" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_marker_inherit_transitive_c-1.0.0-py3-none-any.whl", hash = "sha256:ad2cbb0582ec6f4dc9549d1726d2aae66cd1fdf0e355acc70cd720cf65ae4d86" }, @@ -2409,8 +2409,8 @@ fn fork_marker_inherit_transitive() -> Result<()> { version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'darwin'" }, - { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, + { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'darwin'" }, + { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, ] [package.metadata] @@ -2519,7 +2519,7 @@ fn fork_marker_inherit() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "sys_platform == 'darwin'", ] @@ -2531,7 +2531,7 @@ fn fork_marker_inherit() -> Result<()> { [[package]] name = "package-a" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -2545,8 +2545,8 @@ fn fork_marker_inherit() -> Result<()> { version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'darwin'" }, - { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, + { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'darwin'" }, + { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, ] [package.metadata] @@ -2662,7 +2662,7 @@ fn fork_marker_limited_inherit() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "sys_platform == 'darwin'", ] @@ -2674,7 +2674,7 @@ fn fork_marker_limited_inherit() -> Result<()> { [[package]] name = "package-a" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -2686,7 +2686,7 @@ fn fork_marker_limited_inherit() -> Result<()> { [[package]] name = "package-b" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } dependencies = [ { name = "package-c", marker = "sys_platform == 'linux'" }, ] @@ -2698,7 +2698,7 @@ fn fork_marker_limited_inherit() -> Result<()> { [[package]] name = "package-c" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_marker_limited_inherit_c-1.0.0.tar.gz", hash = "sha256:8dcb05f5dff09fec52ab507b215ff367fe815848319a17929db997ad3afe88ae" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_marker_limited_inherit_c-1.0.0-py3-none-any.whl", hash = "sha256:877a87a4987ad795ddaded3e7266ed7defdd3cfbe07a29500cb6047637db4065" }, @@ -2709,8 +2709,8 @@ fn fork_marker_limited_inherit() -> Result<()> { version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'darwin'" }, - { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, + { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'darwin'" }, + { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, { name = "package-b" }, ] @@ -2822,7 +2822,7 @@ fn fork_marker_selection() -> Result<()> { [[package]] name = "package-a" version = "0.1.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_marker_selection_a-0.1.0.tar.gz", hash = "sha256:ece83ba864a62d5d747439f79a0bf36aa4c18d15bca96aab855ffc2e94a8eef7" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_marker_selection_a-0.1.0-py3-none-any.whl", hash = "sha256:a3b9d6e46cc226d20994cc60653fd59d81d96527749f971a6f59ef8cbcbc7c01" }, @@ -2831,7 +2831,7 @@ fn fork_marker_selection() -> Result<()> { [[package]] name = "package-b" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "sys_platform == 'darwin'", ] @@ -2843,7 +2843,7 @@ fn fork_marker_selection() -> Result<()> { [[package]] name = "package-b" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -2858,8 +2858,8 @@ fn fork_marker_selection() -> Result<()> { source = { virtual = "." } dependencies = [ { name = "package-a" }, - { name = "package-b", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'darwin'" }, - { name = "package-b", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, + { name = "package-b", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'darwin'" }, + { name = "package-b", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, ] [package.metadata] @@ -2985,7 +2985,7 @@ fn fork_marker_track() -> Result<()> { [[package]] name = "package-a" version = "1.3.1" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } dependencies = [ { name = "package-c", marker = "implementation_name == 'iron'" }, ] @@ -2997,7 +2997,7 @@ fn fork_marker_track() -> Result<()> { [[package]] name = "package-b" version = "2.7" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "sys_platform == 'darwin'", ] @@ -3009,7 +3009,7 @@ fn fork_marker_track() -> Result<()> { [[package]] name = "package-b" version = "2.8" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -3021,7 +3021,7 @@ fn fork_marker_track() -> Result<()> { [[package]] name = "package-c" version = "1.10" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_marker_track_c-1.10.tar.gz", hash = "sha256:c89006d893254790b0fcdd1b33520241c8ff66ab950c6752b745e006bdeff144" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_marker_track_c-1.10-py3-none-any.whl", hash = "sha256:cedcb8fbcdd9fbde4eea76612e57536c8b56507a9d7f7a92e483cb56b18c57a3" }, @@ -3033,8 +3033,8 @@ fn fork_marker_track() -> Result<()> { source = { virtual = "." } dependencies = [ { name = "package-a" }, - { name = "package-b", version = "2.7", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'darwin'" }, - { name = "package-b", version = "2.8", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, + { name = "package-b", version = "2.7", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'darwin'" }, + { name = "package-b", version = "2.8", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, ] [package.metadata] @@ -3137,7 +3137,7 @@ fn fork_non_fork_marker_transitive() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } dependencies = [ { name = "package-c", marker = "sys_platform == 'linux'" }, ] @@ -3149,7 +3149,7 @@ fn fork_non_fork_marker_transitive() -> Result<()> { [[package]] name = "package-b" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } dependencies = [ { name = "package-c", marker = "sys_platform == 'darwin'" }, ] @@ -3161,7 +3161,7 @@ fn fork_non_fork_marker_transitive() -> Result<()> { [[package]] name = "package-c" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_non_fork_marker_transitive_c-2.0.0.tar.gz", hash = "sha256:ffab9124854f64c8b5059ccaed481547f54abac868ba98aa6a454c0163cdb1c7" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_non_fork_marker_transitive_c-2.0.0-py3-none-any.whl", hash = "sha256:2b72d6af81967e1c55f30d920d6a7b913fce6ad0a0658ec79972a3d1a054e85f" }, @@ -3453,7 +3453,7 @@ fn fork_overlapping_markers_basic() -> Result<()> { [[package]] name = "package-a" version = "1.2.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_overlapping_markers_basic_a-1.2.0.tar.gz", hash = "sha256:f8c2058d80430d62b15c87fd66040a6c0dd23d32e7f144a932899c0c74bdff2a" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_overlapping_markers_basic_a-1.2.0-py3-none-any.whl", hash = "sha256:04293ed42eb3620c9ddf56e380a8408a30733d5d38f321a35c024d03e7116083" }, @@ -3636,11 +3636,11 @@ fn preferences_dependent_forking_bistable() -> Result<()> { [[package]] name = "package-cleaver" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } dependencies = [ - { name = "package-fork-if-not-forked", version = "3.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, + { name = "package-fork-if-not-forked", version = "3.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, { name = "package-fork-if-not-forked-proxy", marker = "sys_platform != 'linux'" }, - { name = "package-reject-cleaver1", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, + { name = "package-reject-cleaver1", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, { name = "package-reject-cleaver1-proxy" }, ] sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/preferences_dependent_forking_bistable_cleaver-1.0.0.tar.gz", hash = "sha256:64e5ee0c81d6a51fb71ed517fd04cc26c656908ad05073270e67c2f9b92194c5" } @@ -3651,7 +3651,7 @@ fn preferences_dependent_forking_bistable() -> Result<()> { [[package]] name = "package-fork-if-not-forked" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "sys_platform != 'linux'", ] @@ -3663,7 +3663,7 @@ fn preferences_dependent_forking_bistable() -> Result<()> { [[package]] name = "package-fork-if-not-forked" version = "3.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -3675,9 +3675,9 @@ fn preferences_dependent_forking_bistable() -> Result<()> { [[package]] name = "package-fork-if-not-forked-proxy" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } dependencies = [ - { name = "package-fork-if-not-forked", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform != 'linux'" }, + { name = "package-fork-if-not-forked", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform != 'linux'" }, ] sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/preferences_dependent_forking_bistable_fork_if_not_forked_proxy-1.0.0.tar.gz", hash = "sha256:0ed00a7c8280348225835fadc76db8ecc6b4a9ee11351a6c432c475f8d1579de" } wheels = [ @@ -3687,7 +3687,7 @@ fn preferences_dependent_forking_bistable() -> Result<()> { [[package]] name = "package-reject-cleaver1" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -3699,7 +3699,7 @@ fn preferences_dependent_forking_bistable() -> Result<()> { [[package]] name = "package-reject-cleaver1" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "sys_platform != 'linux'", ] @@ -3711,9 +3711,9 @@ fn preferences_dependent_forking_bistable() -> Result<()> { [[package]] name = "package-reject-cleaver1-proxy" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } dependencies = [ - { name = "package-reject-cleaver1", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform != 'linux'" }, + { name = "package-reject-cleaver1", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform != 'linux'" }, ] sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/preferences_dependent_forking_bistable_reject_cleaver1_proxy-1.0.0.tar.gz", hash = "sha256:6b6eaa229d55de992e36084521d2f62dce35120a866e20354d0e5617e16e00ce" } wheels = [ @@ -4048,7 +4048,7 @@ fn preferences_dependent_forking_tristable() -> Result<()> { [[package]] name = "package-bar" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "sys_platform != 'linux'", ] @@ -4064,7 +4064,7 @@ fn preferences_dependent_forking_tristable() -> Result<()> { [[package]] name = "package-bar" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -4076,7 +4076,7 @@ fn preferences_dependent_forking_tristable() -> Result<()> { [[package]] name = "package-c" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -4088,7 +4088,7 @@ fn preferences_dependent_forking_tristable() -> Result<()> { [[package]] name = "package-c" version = "3.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "sys_platform != 'linux'", ] @@ -4100,9 +4100,9 @@ fn preferences_dependent_forking_tristable() -> Result<()> { [[package]] name = "package-cleaver" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } dependencies = [ - { name = "package-bar", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform != 'linux'" }, + { name = "package-bar", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform != 'linux'" }, { name = "package-foo", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/preferences_dependent_forking_tristable_cleaver-1.0.0.tar.gz", hash = "sha256:49ec5779d0722586652e3ceb4ca2bf053a79dc3fa2d7ccd428a359bcc885a248" } @@ -4113,9 +4113,9 @@ fn preferences_dependent_forking_tristable() -> Result<()> { [[package]] name = "package-d" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } dependencies = [ - { name = "package-c", version = "3.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform != 'linux'" }, + { name = "package-c", version = "3.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform != 'linux'" }, ] sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/preferences_dependent_forking_tristable_d-1.0.0.tar.gz", hash = "sha256:690b69acb46d0ebfb11a81f401d2ea2e2e6a8ae97f199d345715e9bd40a7ceba" } wheels = [ @@ -4125,10 +4125,10 @@ fn preferences_dependent_forking_tristable() -> Result<()> { [[package]] name = "package-foo" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } dependencies = [ - { name = "package-c", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, - { name = "package-c", version = "3.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform != 'linux'" }, + { name = "package-c", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, + { name = "package-c", version = "3.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform != 'linux'" }, { name = "package-reject-cleaver-1" }, ] sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/preferences_dependent_forking_tristable_foo-1.0.0.tar.gz", hash = "sha256:7c1a2ca51dd2156cf36c3400e38595e11b09442052f4bd1d6b3d53eb5b2acf32" } @@ -4139,10 +4139,10 @@ fn preferences_dependent_forking_tristable() -> Result<()> { [[package]] name = "package-reject-cleaver-1" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } dependencies = [ - { name = "package-unrelated-dep2", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, - { name = "package-unrelated-dep2", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform != 'linux'" }, + { name = "package-unrelated-dep2", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, + { name = "package-unrelated-dep2", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform != 'linux'" }, ] sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/preferences_dependent_forking_tristable_reject_cleaver_1-1.0.0.tar.gz", hash = "sha256:6ef93ca22db3a054559cb34f574ffa3789951f2f82b213c5502d0e9ff746f15e" } wheels = [ @@ -4152,7 +4152,7 @@ fn preferences_dependent_forking_tristable() -> Result<()> { [[package]] name = "package-unrelated-dep2" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -4164,7 +4164,7 @@ fn preferences_dependent_forking_tristable() -> Result<()> { [[package]] name = "package-unrelated-dep2" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "sys_platform != 'linux'", ] @@ -4178,8 +4178,8 @@ fn preferences_dependent_forking_tristable() -> Result<()> { version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "package-bar", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform != 'linux'" }, - { name = "package-bar", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, + { name = "package-bar", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform != 'linux'" }, + { name = "package-bar", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, { name = "package-cleaver" }, { name = "package-foo" }, ] @@ -4342,7 +4342,7 @@ fn preferences_dependent_forking() -> Result<()> { [[package]] name = "package-bar" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "sys_platform != 'linux'", ] @@ -4354,7 +4354,7 @@ fn preferences_dependent_forking() -> Result<()> { [[package]] name = "package-bar" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "sys_platform == 'linux'", ] @@ -4366,9 +4366,9 @@ fn preferences_dependent_forking() -> Result<()> { [[package]] name = "package-cleaver" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } dependencies = [ - { name = "package-bar", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform != 'linux'" }, + { name = "package-bar", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform != 'linux'" }, { name = "package-foo", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/preferences_dependent_forking_cleaver-1.0.0.tar.gz", hash = "sha256:0347b927fdf7731758ea53e1594309fc6311ca6983f36553bc11654a264062b2" } @@ -4379,7 +4379,7 @@ fn preferences_dependent_forking() -> Result<()> { [[package]] name = "package-foo" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/preferences_dependent_forking_foo-1.0.0.tar.gz", hash = "sha256:abf1c0ac825ee5961e683067634916f98c6651a6d4473ff87d8b57c17af8fed2" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/preferences_dependent_forking_foo-1.0.0-py3-none-any.whl", hash = "sha256:85348e8df4892b9f297560c16abcf231828f538dc07339ed121197a00a0626a5" }, @@ -4390,8 +4390,8 @@ fn preferences_dependent_forking() -> Result<()> { version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "package-bar", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform != 'linux'" }, - { name = "package-bar", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'linux'" }, + { name = "package-bar", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform != 'linux'" }, + { name = "package-bar", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'linux'" }, { name = "package-cleaver" }, { name = "package-foo" }, ] @@ -4525,15 +4525,15 @@ fn fork_remaining_universe_partitioning() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "os_name == 'darwin' and sys_platform == 'illumos'", "os_name == 'linux' and sys_platform == 'illumos'", "os_name != 'darwin' and os_name != 'linux' and sys_platform == 'illumos'", ] dependencies = [ - { name = "package-b", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "os_name == 'darwin' and sys_platform == 'illumos'" }, - { name = "package-b", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "os_name == 'linux' and sys_platform == 'illumos'" }, + { name = "package-b", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "os_name == 'darwin' and sys_platform == 'illumos'" }, + { name = "package-b", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "os_name == 'linux' and sys_platform == 'illumos'" }, ] sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_remaining_universe_partitioning_a-1.0.0.tar.gz", hash = "sha256:d5be0af9a1958ec08ca2827b47bfd507efc26cab03ecf7ddf204e18e8a3a18ae" } wheels = [ @@ -4543,7 +4543,7 @@ fn fork_remaining_universe_partitioning() -> Result<()> { [[package]] name = "package-a" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "sys_platform == 'windows'", ] @@ -4555,7 +4555,7 @@ fn fork_remaining_universe_partitioning() -> Result<()> { [[package]] name = "package-b" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "os_name == 'darwin' and sys_platform == 'illumos'", ] @@ -4567,7 +4567,7 @@ fn fork_remaining_universe_partitioning() -> Result<()> { [[package]] name = "package-b" version = "2.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } resolution-markers = [ "os_name == 'linux' and sys_platform == 'illumos'", ] @@ -4581,8 +4581,8 @@ fn fork_remaining_universe_partitioning() -> Result<()> { version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'illumos'" }, - { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }, marker = "sys_platform == 'windows'" }, + { name = "package-a", version = "1.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'illumos'" }, + { name = "package-a", version = "2.0.0", source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }, marker = "sys_platform == 'windows'" }, ] [package.metadata] @@ -4845,7 +4845,7 @@ fn fork_requires_python_patch_overlap() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_requires_python_patch_overlap_a-1.0.0.tar.gz", hash = "sha256:ac2820ee4808788674295192d79a709e3259aa4eef5b155e77f719ad4eaa324d" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/fork_requires_python_patch_overlap_a-1.0.0-py3-none-any.whl", hash = "sha256:43a750ba4eaab749d608d70e94d3d51e083cc21f5a52ac99b5967b26486d5ef1" }, @@ -5031,7 +5031,7 @@ fn requires_python_wheels() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/requires_python_wheels_a-1.0.0.tar.gz", hash = "sha256:9a11ff73fdc513c4dab0d3e137f4145a00ef0dfc95154360c8f503eed62a03c9" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/requires_python_wheels_a-1.0.0-cp310-cp310-any.whl", hash = "sha256:b979494a0d7dc825b84d6c516ac407143915f6d2840d229ee2a36b3d06deb61d" }, @@ -5130,7 +5130,7 @@ fn unreachable_package() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/unreachable_package_a-1.0.0.tar.gz", hash = "sha256:308f0b6772e99dcb33acee38003b176e3acffbe01c3c511585db9a7d7ec008f7" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/unreachable_package_a-1.0.0-py3-none-any.whl", hash = "sha256:cc472ded9f3b260e6cda0e633fa407a13607e190422cb455f02beebd32d6751f" }, @@ -5241,7 +5241,7 @@ fn unreachable_wheels() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/unreachable_wheels_a-1.0.0.tar.gz", hash = "sha256:91c6619d1cfa227f3662c0c062b1c0c16efe11e589db2f1836e809e2c6d9961e" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/unreachable_wheels_a-1.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:e9fb30c5eb114114f9031d0ad2238614c2dcce203c5992848305ccda8f38a53e" }, @@ -5250,7 +5250,7 @@ fn unreachable_wheels() -> Result<()> { [[package]] name = "package-b" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/unreachable_wheels_b-1.0.0.tar.gz", hash = "sha256:253ae69b963651cd5ac16601a445e2e179db9eac552e8cfc37aadf73a88931ed" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/unreachable_wheels_b-1.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3de2212ca86f1137324965899ce7f48640ed8db94578f4078d641520b77e13e" }, @@ -5260,7 +5260,7 @@ fn unreachable_wheels() -> Result<()> { [[package]] name = "package-c" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/unreachable_wheels_c-1.0.0.tar.gz", hash = "sha256:5c4783e85f0fa57b720fd02b5c7e0ff8bc98121546fe2cce435710efe4a34b28" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/unreachable_wheels_c-1.0.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:4b846c5b1646b04828a2bef6c9d180ff7cfd725866013dcec8933de7fb5f9e8d" }, @@ -5362,7 +5362,7 @@ fn marker_variants_have_different_extras() -> Result<()> { [[package]] name = "package-psycopg" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } dependencies = [ { name = "package-tzdata", marker = "sys_platform == 'win32'" }, ] @@ -5379,7 +5379,7 @@ fn marker_variants_have_different_extras() -> Result<()> { [[package]] name = "package-psycopg-binary" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/marker_variants_have_different_extras_psycopg_binary-1.0.0.tar.gz", hash = "sha256:9939771dfe78d76e3583492aaec576719780f744b36198b1f18bb16bb5048995" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/marker_variants_have_different_extras_psycopg_binary-1.0.0-py3-none-any.whl", hash = "sha256:4fb0aef60e76bc7e339d60dc919f3b6e27e49184ffdef9fb2c3f6902b23b6bd2" }, @@ -5388,7 +5388,7 @@ fn marker_variants_have_different_extras() -> Result<()> { [[package]] name = "package-tzdata" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/marker_variants_have_different_extras_tzdata-1.0.0.tar.gz", hash = "sha256:5aa31d0aec969afbc13584c3209ca2380107bdab68578f881eb2da543ac2ee8e" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/marker_variants_have_different_extras_tzdata-1.0.0-py3-none-any.whl", hash = "sha256:7466eec7ed202434492e7c09a4a7327517aec6d549aaca0436dcc100f9fcb6a5" }, @@ -5515,7 +5515,7 @@ fn virtual_package_extra_priorities() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } dependencies = [ { name = "package-b" }, ] @@ -5527,7 +5527,7 @@ fn virtual_package_extra_priorities() -> Result<()> { [[package]] name = "package-b" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/virtual_package_extra_priorities_b-1.0.0.tar.gz", hash = "sha256:79a54df14eb28687678447f5270f578f73b325f8234e620d375a87708fd7345c" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/virtual_package_extra_priorities_b-1.0.0-py3-none-any.whl", hash = "sha256:2aab1a3b90f215cb55b9bfde55b3c3617225ca0da726e8c9543c0727734f1df9" }, @@ -5635,7 +5635,7 @@ fn specific_architecture() -> Result<()> { [[package]] name = "package-a" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } dependencies = [ { name = "package-b", marker = "platform_machine == 'x86_64'" }, { name = "package-c", marker = "platform_machine == 'aarch64'" }, @@ -5649,7 +5649,7 @@ fn specific_architecture() -> Result<()> { [[package]] name = "package-b" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/specific_architecture_b-1.0.0-cp313-cp313-freebsd_13_aarch64.whl", hash = "sha256:4ce70a68440d4aaa31cc1c6174b83b741e9b8f3074ad0f3ef41c572795378999" }, { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/specific_architecture_b-1.0.0-cp313-cp313-freebsd_13_x86_64.whl", hash = "sha256:4ce70a68440d4aaa31cc1c6174b83b741e9b8f3074ad0f3ef41c572795378999" }, @@ -5660,7 +5660,7 @@ fn specific_architecture() -> Result<()> { [[package]] name = "package-c" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/specific_architecture_c-1.0.0-cp313-cp313-freebsd_13_aarch64.whl", hash = "sha256:b028c88fe496724cea4a7d95eb789a000b7f000067f95c922b09461be2746a3d" }, { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/specific_architecture_c-1.0.0-cp313-cp313-freebsd_13_x86_64.whl", hash = "sha256:b028c88fe496724cea4a7d95eb789a000b7f000067f95c922b09461be2746a3d" }, @@ -5671,7 +5671,7 @@ fn specific_architecture() -> Result<()> { [[package]] name = "package-d" version = "1.0.0" - source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" } + source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" } wheels = [ { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/specific_architecture_d-1.0.0-cp313-cp313-freebsd_13_aarch64.whl", hash = "sha256:842864c1348694fab33199eb05921602c2abfc77844a81085a55db02edd30da4" }, { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/specific_architecture_d-1.0.0-cp313-cp313-freebsd_13_x86_64.whl", hash = "sha256:842864c1348694fab33199eb05921602c2abfc77844a81085a55db02edd30da4" }, diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index f9a71fe82..690079abf 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -9939,7 +9939,7 @@ fn sync_required_environment_hint() -> Result<()> { ----- stderr ----- Resolved 2 packages in [TIME] - error: Distribution `no-sdist-no-wheels-with-matching-platform-a==1.0.0 @ registry+https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html` can't be installed because it doesn't have a source distribution or wheel for the current platform + error: Distribution `no-sdist-no-wheels-with-matching-platform-a==1.0.0 @ registry+https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/` can't be installed because it doesn't have a source distribution or wheel for the current platform hint: You're on [PLATFORM] (`[TAG]`), but `no-sdist-no-wheels-with-matching-platform-a` (v1.0.0) only has wheels for the following platform: `macosx_10_0_ppc64`; consider adding your platform to `tool.uv.required-environments` to ensure uv resolves to a version with compatible wheels "); From b1dc2b71a3c0c1ce39024457e2cadcebbcda793a Mon Sep 17 00:00:00 2001 From: "Yu, Guangye" <106960996+guangyey@users.noreply.github.com> Date: Wed, 9 Jul 2025 06:31:08 -0700 Subject: [PATCH 166/349] Add auto-detection for Intel GPUs (#14386) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary This PR intends to enable `--torch-backend=auto` to detect Intel GPUs automatically: - On Linux, detection is performed using the `lspci` command via `Display controller` id. - On Windows, ~~detection is done via a `powershell` query to `Win32_VideoController`~~. Skip support for now—revisit once a better solution is available. Currently, Intel GPUs (XPU) do not rely on specific driver or toolkit versions to distribute different PyTorch wheels. ## Test Plan On Linux: ![image](https://github.com/user-attachments/assets/f7f238e3-a797-42ea-b8fa-9b028dfd4db5) ~~On Windows: ![image](https://github.com/user-attachments/assets/a10d774e-1cb9-431b-bb85-e3e8225df98f)~~ --------- Co-authored-by: Charlie Marsh --- crates/uv-torch/src/accelerator.rs | 63 +++++++++++++++++++++++++++--- crates/uv-torch/src/backend.rs | 27 +++++++++++-- docs/guides/integration/pytorch.md | 16 ++++++-- 3 files changed, 94 insertions(+), 12 deletions(-) diff --git a/crates/uv-torch/src/accelerator.rs b/crates/uv-torch/src/accelerator.rs index 3165bd4c5..696adc9a1 100644 --- a/crates/uv-torch/src/accelerator.rs +++ b/crates/uv-torch/src/accelerator.rs @@ -1,3 +1,4 @@ +use std::path::Path; use std::str::FromStr; use tracing::debug; @@ -13,6 +14,8 @@ pub enum AcceleratorError { Version(#[from] uv_pep440::VersionParseError), #[error(transparent)] Utf8(#[from] std::string::FromUtf8Error), + #[error(transparent)] + ParseInt(#[from] std::num::ParseIntError), #[error("Unknown AMD GPU architecture: {0}")] UnknownAmdGpuArchitecture(String), } @@ -30,6 +33,10 @@ pub enum Accelerator { Amd { gpu_architecture: AmdGpuArchitecture, }, + /// The Intel GPU (XPU). + /// + /// Currently, Intel GPUs do not depend on a driver or toolkit version at this level. + Xpu, } impl std::fmt::Display for Accelerator { @@ -37,21 +44,28 @@ impl std::fmt::Display for Accelerator { match self { Self::Cuda { driver_version } => write!(f, "CUDA {driver_version}"), Self::Amd { gpu_architecture } => write!(f, "AMD {gpu_architecture}"), + Self::Xpu => write!(f, "Intel GPU (XPU)"), } } } impl Accelerator { - /// Detect the CUDA driver version from the system. + /// Detect the GPU driver and/or architecture version from the system. /// /// Query, in order: /// 1. The `UV_CUDA_DRIVER_VERSION` environment variable. /// 2. The `UV_AMD_GPU_ARCHITECTURE` environment variable. - /// 2. `/sys/module/nvidia/version`, which contains the driver version (e.g., `550.144.03`). - /// 3. `/proc/driver/nvidia/version`, which contains the driver version among other information. - /// 4. `nvidia-smi --query-gpu=driver_version --format=csv,noheader`. - /// 5. `rocm_agent_enumerator`, which lists the AMD GPU architectures. + /// 3. `/sys/module/nvidia/version`, which contains the driver version (e.g., `550.144.03`). + /// 4. `/proc/driver/nvidia/version`, which contains the driver version among other information. + /// 5. `nvidia-smi --query-gpu=driver_version --format=csv,noheader`. + /// 6. `rocm_agent_enumerator`, which lists the AMD GPU architectures. + /// 7. `/sys/bus/pci/devices`, filtering for the Intel GPU via PCI. pub fn detect() -> Result, AcceleratorError> { + // Constants used for PCI device detection. + const PCI_BASE_CLASS_MASK: u32 = 0x00ff_0000; + const PCI_BASE_CLASS_DISPLAY: u32 = 0x0003_0000; + const PCI_VENDOR_ID_INTEL: u32 = 0x8086; + // Read from `UV_CUDA_DRIVER_VERSION`. if let Ok(driver_version) = std::env::var(EnvVars::UV_CUDA_DRIVER_VERSION) { let driver_version = Version::from_str(&driver_version)?; @@ -150,6 +164,29 @@ impl Accelerator { } } + // Read from `/sys/bus/pci/devices` to filter for Intel GPU via PCI. + match fs_err::read_dir("/sys/bus/pci/devices") { + Ok(entries) => { + for entry in entries.flatten() { + match parse_pci_device_ids(&entry.path()) { + Ok((class, vendor)) => { + if (class & PCI_BASE_CLASS_MASK) == PCI_BASE_CLASS_DISPLAY + && vendor == PCI_VENDOR_ID_INTEL + { + debug!("Detected Intel GPU from PCI: vendor=0x{:04x}", vendor); + return Ok(Some(Self::Xpu)); + } + } + Err(e) => { + debug!("Failed to parse PCI device IDs: {e}"); + } + } + } + } + Err(e) if e.kind() == std::io::ErrorKind::NotFound => {} + Err(e) => return Err(e.into()), + } + debug!("Failed to detect GPU driver version"); Ok(None) @@ -180,6 +217,22 @@ fn parse_proc_driver_nvidia_version(content: &str) -> Result, Ac Ok(Some(driver_version)) } +/// Reads and parses the PCI class and vendor ID from a given device path under `/sys/bus/pci/devices`. +fn parse_pci_device_ids(device_path: &Path) -> Result<(u32, u32), AcceleratorError> { + // Parse, e.g.: + // ```text + // - `class`: a hexadecimal string such as `0x030000` + // - `vendor`: a hexadecimal string such as `0x8086` + // ``` + let class_content = fs_err::read_to_string(device_path.join("class"))?; + let pci_class = u32::from_str_radix(class_content.trim().trim_start_matches("0x"), 16)?; + + let vendor_content = fs_err::read_to_string(device_path.join("vendor"))?; + let pci_vendor = u32::from_str_radix(vendor_content.trim().trim_start_matches("0x"), 16)?; + + Ok((pci_class, pci_vendor)) +} + /// A GPU architecture for AMD GPUs. /// /// See: diff --git a/crates/uv-torch/src/backend.rs b/crates/uv-torch/src/backend.rs index 0f2b72077..5ad71b385 100644 --- a/crates/uv-torch/src/backend.rs +++ b/crates/uv-torch/src/backend.rs @@ -185,6 +185,8 @@ pub enum TorchStrategy { os: Os, gpu_architecture: AmdGpuArchitecture, }, + /// Select the appropriate PyTorch index based on the operating system and Intel GPU presence. + Xpu { os: Os }, /// Use the specified PyTorch index. Backend(TorchBackend), } @@ -202,6 +204,7 @@ impl TorchStrategy { os: os.clone(), gpu_architecture, }), + Some(Accelerator::Xpu) => Ok(Self::Xpu { os: os.clone() }), None => Ok(Self::Backend(TorchBackend::Cpu)), }, TorchMode::Cpu => Ok(Self::Backend(TorchBackend::Cpu)), @@ -347,9 +350,27 @@ impl TorchStrategy { Either::Right(Either::Left(std::iter::once(TorchBackend::Cpu.index_url()))) } }, - TorchStrategy::Backend(backend) => { - Either::Right(Either::Right(std::iter::once(backend.index_url()))) - } + TorchStrategy::Xpu { os } => match os { + Os::Manylinux { .. } => Either::Right(Either::Right(Either::Left( + std::iter::once(TorchBackend::Xpu.index_url()), + ))), + Os::Windows + | Os::Musllinux { .. } + | Os::Macos { .. } + | Os::FreeBsd { .. } + | Os::NetBsd { .. } + | Os::OpenBsd { .. } + | Os::Dragonfly { .. } + | Os::Illumos { .. } + | Os::Haiku { .. } + | Os::Android { .. } + | Os::Pyodide { .. } => { + Either::Right(Either::Left(std::iter::once(TorchBackend::Cpu.index_url()))) + } + }, + TorchStrategy::Backend(backend) => Either::Right(Either::Right(Either::Right( + std::iter::once(backend.index_url()), + ))), } } } diff --git a/docs/guides/integration/pytorch.md b/docs/guides/integration/pytorch.md index a90ebeb6b..0553c4449 100644 --- a/docs/guides/integration/pytorch.md +++ b/docs/guides/integration/pytorch.md @@ -444,10 +444,10 @@ $ # With an environment variable. $ UV_TORCH_BACKEND=auto uv pip install torch ``` -When enabled, uv will query for the installed CUDA driver and AMD GPU versions then use the -most-compatible PyTorch index for all relevant packages (e.g., `torch`, `torchvision`, 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. +When enabled, uv will query for the installed CUDA driver, AMD GPU versions, and Intel GPU presence, +then use the most-compatible PyTorch index for all relevant packages (e.g., `torch`, `torchvision`, +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.6) with `--torch-backend=cu126` (or `UV_TORCH_BACKEND=cu126`): @@ -460,4 +460,12 @@ $ # With an environment variable. $ UV_TORCH_BACKEND=cu126 uv pip install torch torchvision ``` +On Windows, Intel GPU (XPU) is not automatically selected with `--torch-backend=auto`, but you can +manually specify it using `--torch-backend=xpu`: + +```shell +$ # Manual selection for Intel GPU. +$ uv pip install torch torchvision --torch-backend=xpu +``` + At present, `--torch-backend` is only available in the `uv pip` interface. From 4d061a6fc349b6bb05ff60bdeb4b2fc2ed8e18a6 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Wed, 9 Jul 2025 11:46:53 -0400 Subject: [PATCH 167/349] Add `--workspace` flag to `uv add` (#14496) ## Summary You can now pass `--workspace` to `uv add` to add a path dependency as a workspace member. Closes https://github.com/astral-sh/uv/issues/14464. --- crates/uv-cli/src/lib.rs | 10 +- crates/uv/src/commands/project/add.rs | 89 +++++- crates/uv/src/lib.rs | 1 + crates/uv/src/settings.rs | 3 + crates/uv/tests/it/edit.rs | 411 ++++++++++++++++++++++++++ docs/reference/cli.md | 2 + 6 files changed, 509 insertions(+), 7 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index bf605198f..9f02490c6 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -3632,7 +3632,8 @@ pub struct AddArgs { long, conflicts_with = "dev", conflicts_with = "optional", - conflicts_with = "package" + conflicts_with = "package", + conflicts_with = "workspace" )] pub script: Option, @@ -3648,6 +3649,13 @@ pub struct AddArgs { value_parser = parse_maybe_string, )] pub python: Option>, + + /// Add the dependency as a workspace member. + /// + /// When used with a path dependency, the package will be added to the workspace's `members` + /// list in the root `pyproject.toml` file. + #[arg(long)] + pub workspace: bool, } #[derive(Args)] diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index 04fd7d822..959241b4b 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -83,6 +83,7 @@ pub(crate) async fn add( extras_of_dependency: Vec, package: Option, python: Option, + workspace: bool, install_mirrors: PythonInstallMirrors, settings: ResolverInstallerSettings, network_settings: NetworkSettings, @@ -151,7 +152,7 @@ pub(crate) async fn add( // Default groups we need the actual project for, interpreter discovery will use this! let defaulted_groups; - let target = if let Some(script) = script { + let mut target = if let Some(script) = script { // If we found a PEP 723 script and the user provided a project-only setting, warn. if package.is_some() { warn_user_once!( @@ -478,6 +479,9 @@ pub(crate) async fn add( } } + // Store the content prior to any modifications. + let snapshot = target.snapshot().await?; + // If the user provides a single, named index, pin all requirements to that index. let index = indexes .first() @@ -488,7 +492,72 @@ pub(crate) async fn add( debug!("Pinning all requirements to index: `{index}`"); }); - // Add the requirements to the `pyproject.toml` or script. + // Track modification status, for reverts. + let mut modified = false; + + // If `--workspace` is provided, add any members to the `workspace` section of the + // `pyproject.toml` file. + if workspace { + let AddTarget::Project(project, python_target) = target else { + unreachable!("`--workspace` and `--script` are conflicting options"); + }; + + let workspace = project.workspace(); + let mut toml = PyProjectTomlMut::from_toml( + &workspace.pyproject_toml().raw, + DependencyTarget::PyProjectToml, + )?; + + // Check each requirement to see if it's a path dependency + for requirement in &requirements { + if let RequirementSource::Directory { install_path, .. } = &requirement.source { + let absolute_path = if install_path.is_absolute() { + install_path.to_path_buf() + } else { + project.root().join(install_path) + }; + + // Check if the path is not already included in the workspace. + if !workspace.includes(&absolute_path)? { + let relative_path = absolute_path + .strip_prefix(workspace.install_path()) + .unwrap_or(&absolute_path); + + toml.add_workspace(relative_path)?; + modified |= true; + + writeln!( + printer.stderr(), + "Added `{}` to workspace members", + relative_path.user_display().cyan() + )?; + } + } + } + + // If we modified the workspace root, we need to reload it entirely, since this can impact + // the discovered members, etc. + target = if modified { + let workspace_content = toml.to_string(); + fs_err::write( + workspace.install_path().join("pyproject.toml"), + &workspace_content, + )?; + + AddTarget::Project( + VirtualProject::discover( + project.root(), + &DiscoveryOptions::default(), + &WorkspaceCache::default(), + ) + .await?, + python_target, + ) + } else { + AddTarget::Project(project, python_target) + } + } + let mut toml = match &target { AddTarget::Script(script, _) => { PyProjectTomlMut::from_toml(&script.metadata.raw, DependencyTarget::Script) @@ -498,6 +567,7 @@ pub(crate) async fn add( DependencyTarget::PyProjectToml, ), }?; + let edits = edits( requirements, &target, @@ -543,7 +613,7 @@ pub(crate) async fn add( let content = toml.to_string(); // Save the modified `pyproject.toml` or script. - let modified = target.write(&content)?; + modified |= target.write(&content)?; // If `--frozen`, exit early. There's no reason to lock and sync, since we don't need a `uv.lock` // to exist at all. @@ -563,9 +633,6 @@ pub(crate) async fn add( } } - // Store the content prior to any modifications. - let snapshot = target.snapshot().await?; - // Update the `pypackage.toml` in-memory. let target = target.update(&content)?; @@ -1296,6 +1363,16 @@ impl AddTargetSnapshot { Ok(()) } Self::Project(project, lock) => { + // Write the workspace `pyproject.toml` back to disk. + let workspace = project.workspace(); + if workspace.install_path() != project.root() { + debug!("Reverting changes to workspace `pyproject.toml`"); + fs_err::write( + workspace.install_path().join("pyproject.toml"), + workspace.pyproject_toml().as_ref(), + )?; + } + // Write the `pyproject.toml` back to disk. debug!("Reverting changes to `pyproject.toml`"); fs_err::write( diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index ab4aee9e9..e22eb801f 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -1965,6 +1965,7 @@ async fn run_project( args.extras, args.package, args.python, + args.workspace, args.install_mirrors, args.settings, globals.network_settings, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 004ce5053..673f76ebd 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -1326,6 +1326,7 @@ pub(crate) struct AddSettings { pub(crate) package: Option, pub(crate) script: Option, pub(crate) python: Option, + pub(crate) workspace: bool, pub(crate) install_mirrors: PythonInstallMirrors, pub(crate) refresh: Refresh, pub(crate) indexes: Vec, @@ -1363,6 +1364,7 @@ impl AddSettings { package, script, python, + workspace, } = args; let dependency_type = if let Some(extra) = optional { @@ -1463,6 +1465,7 @@ impl AddSettings { package, script, python: python.and_then(Maybe::into_option), + workspace, editable: flag(editable, no_editable, "editable"), extras: extra.unwrap_or_default(), refresh: Refresh::from(refresh), diff --git a/crates/uv/tests/it/edit.rs b/crates/uv/tests/it/edit.rs index 2aa5b651b..c1a74541f 100644 --- a/crates/uv/tests/it/edit.rs +++ b/crates/uv/tests/it/edit.rs @@ -7210,6 +7210,7 @@ fn remove_include_default_groups() -> Result<()> { Ok(()) } + /// Revert changes to the `pyproject.toml` and `uv.lock` when the `add` operation fails. #[test] fn fail_to_add_revert_project() -> Result<()> { @@ -7401,6 +7402,256 @@ fn fail_to_edit_revert_project() -> Result<()> { Ok(()) } +/// Revert changes to the root `pyproject.toml` and `uv.lock` when the `add` operation fails. +#[test] +fn fail_to_add_revert_workspace_root() -> Result<()> { + let context = TestContext::new("3.12"); + + context + .temp_dir + .child("pyproject.toml") + .write_str(indoc! {r#" + [project] + name = "parent" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + "#})?; + + // Add a dependency on a package that declares static metadata (so can always resolve), but + // can't be installed. + let pyproject_toml = context.temp_dir.child("child/pyproject.toml"); + pyproject_toml.write_str(indoc! {r#" + [project] + name = "child" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["iniconfig"] + + [build-system] + requires = ["setuptools"] + build-backend = "setuptools.build_meta" + "#})?; + context + .temp_dir + .child("child") + .child("setup.py") + .write_str("1/0")?; + + // Add a dependency on a package that declares static metadata (so can always resolve), but + // can't be installed. + let pyproject_toml = context.temp_dir.child("broken").child("pyproject.toml"); + pyproject_toml.write_str(indoc! {r#" + [project] + name = "broken" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["iniconfig"] + + [build-system] + requires = ["setuptools"] + build-backend = "setuptools.build_meta" + "#})?; + context + .temp_dir + .child("broken") + .child("setup.py") + .write_str("1/0")?; + + uv_snapshot!(context.filters(), context.add().arg("--workspace").arg("./broken"), @r#" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + Added `broken` to workspace members + Resolved 3 packages in [TIME] + × Failed to build `broken @ file://[TEMP_DIR]/broken` + ├─▶ The build backend returned an error + ╰─▶ Call to `setuptools.build_meta.build_editable` failed (exit status: 1) + + [stderr] + Traceback (most recent call last): + File "", line 14, in + File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 448, in get_requires_for_build_editable + return self.get_requires_for_build_wheel(config_settings) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 325, in get_requires_for_build_wheel + return self._get_build_requires(config_settings, requirements=['wheel']) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 295, in _get_build_requires + self.run_setup() + File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 311, in run_setup + exec(code, locals()) + File "", line 1, in + ZeroDivisionError: division by zero + + hint: This usually indicates a problem with the package or the build environment. + help: If you want to add the package regardless of the failed resolution, provide the `--frozen` flag to skip locking and syncing. + "#); + + let pyproject_toml = fs_err::read_to_string(context.temp_dir.join("pyproject.toml"))?; + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + pyproject_toml, @r#" + [project] + name = "parent" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + "# + ); + }); + + // The lockfile should not exist, even though resolution succeeded. + assert!(!context.temp_dir.join("uv.lock").exists()); + + Ok(()) +} + +/// Revert changes to the root `pyproject.toml` and `uv.lock` when the `add` operation fails. +#[test] +fn fail_to_add_revert_workspace_member() -> Result<()> { + let context = TestContext::new("3.12"); + + context + .temp_dir + .child("pyproject.toml") + .write_str(indoc! {r#" + [project] + name = "parent" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["child"] + + [tool.uv.workspace] + members = ["child"] + + [tool.uv.sources] + child = { workspace = true } + "#})?; + + // Add a workspace dependency. + let project = context.temp_dir.child("child"); + project.child("pyproject.toml").write_str(indoc! {r#" + [project] + name = "child" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["iniconfig"] + + [build-system] + requires = ["hatchling"] + build-backend = "hatchling.build" + "#})?; + project + .child("src") + .child("child") + .child("__init__.py") + .touch()?; + + // Add a dependency on a package that declares static metadata (so can always resolve), but + // can't be installed. + let pyproject_toml = context.temp_dir.child("broken/pyproject.toml"); + pyproject_toml.write_str(indoc! {r#" + [project] + name = "broken" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["iniconfig"] + + [build-system] + requires = ["setuptools"] + build-backend = "setuptools.build_meta" + "#})?; + context + .temp_dir + .child("broken") + .child("setup.py") + .write_str("1/0")?; + + uv_snapshot!(context.filters(), context.add().current_dir(&project).arg("--workspace").arg("../broken"), @r#" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + Added `broken` to workspace members + Resolved 4 packages in [TIME] + × Failed to build `broken @ file://[TEMP_DIR]/broken` + ├─▶ The build backend returned an error + ╰─▶ Call to `setuptools.build_meta.build_editable` failed (exit status: 1) + + [stderr] + Traceback (most recent call last): + File "", line 14, in + File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 448, in get_requires_for_build_editable + return self.get_requires_for_build_wheel(config_settings) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 325, in get_requires_for_build_wheel + return self._get_build_requires(config_settings, requirements=['wheel']) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 295, in _get_build_requires + self.run_setup() + File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 311, in run_setup + exec(code, locals()) + File "", line 1, in + ZeroDivisionError: division by zero + + hint: This usually indicates a problem with the package or the build environment. + help: If you want to add the package regardless of the failed resolution, provide the `--frozen` flag to skip locking and syncing. + "#); + + let pyproject_toml = fs_err::read_to_string(context.temp_dir.join("pyproject.toml"))?; + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + pyproject_toml, @r#" + [project] + name = "parent" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["child"] + + [tool.uv.workspace] + members = ["child"] + + [tool.uv.sources] + child = { workspace = true } + "# + ); + }); + + let pyproject_toml = + fs_err::read_to_string(context.temp_dir.join("child").join("pyproject.toml"))?; + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + pyproject_toml, @r#" + [project] + name = "child" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["iniconfig"] + + [build-system] + requires = ["hatchling"] + build-backend = "hatchling.build" + "# + ); + }); + + // The lockfile should not exist, even though resolution succeeded. + assert!(!context.temp_dir.join("uv.lock").exists()); + + Ok(()) +} + /// Ensure that the added dependencies are sorted if the dependency list was already sorted prior /// to the operation. #[test] @@ -12629,3 +12880,163 @@ fn add_bounds_requirement_over_bounds_kind() -> Result<()> { Ok(()) } + +/// Add a path dependency with `--workspace` flag to add it to workspace members. The root already +/// contains a workspace definition, so the package should be added to the workspace members. +#[test] +fn add_path_with_existing_workspace() -> Result<()> { + let context = TestContext::new("3.12"); + + let workspace_toml = context.temp_dir.child("pyproject.toml"); + workspace_toml.write_str(indoc! {r#" + [project] + name = "parent" + version = "0.1.0" + requires-python = ">=3.12" + + [tool.uv.workspace] + members = ["project"] + "#})?; + + // Create a project within the workspace. + let project_dir = context.temp_dir.child("project"); + project_dir.create_dir_all()?; + + let project_toml = project_dir.child("pyproject.toml"); + project_toml.write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + "#})?; + + // Create a dependency package outside the workspace members. + let dep_dir = context.temp_dir.child("dep"); + dep_dir.create_dir_all()?; + + let dep_toml = dep_dir.child("pyproject.toml"); + dep_toml.write_str(indoc! {r#" + [project] + name = "dep" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + "#})?; + + // Add the dependency with `--workspace` flag from the project directory. + uv_snapshot!(context.filters(), context + .add() + .current_dir(&project_dir) + .arg("../dep") + .arg("--workspace"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Added `dep` to workspace members + Resolved 3 packages in [TIME] + Audited in [TIME] + "); + + let pyproject_toml = context.read("pyproject.toml"); + assert_snapshot!( + pyproject_toml, @r#" + [project] + name = "parent" + version = "0.1.0" + requires-python = ">=3.12" + + [tool.uv.workspace] + members = [ + "project", + "dep", + ] + "# + ); + + let pyproject_toml = context.read("project/pyproject.toml"); + assert_snapshot!( + pyproject_toml, @r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [ + "dep", + ] + + [tool.uv.sources] + dep = { workspace = true } + "# + ); + + Ok(()) +} + +/// Add a path dependency with `--workspace` flag to add it to workspace members. The root doesn't +/// contain a workspace definition, so `uv add` should create one. +#[test] +fn add_path_with_workspace() -> Result<()> { + let context = TestContext::new("3.12"); + + let workspace_toml = context.temp_dir.child("pyproject.toml"); + workspace_toml.write_str(indoc! {r#" + [project] + name = "parent" + version = "0.1.0" + requires-python = ">=3.12" + "#})?; + + // Create a dependency package outside the workspace members. + let dep_dir = context.temp_dir.child("dep"); + dep_dir.create_dir_all()?; + + let dep_toml = dep_dir.child("pyproject.toml"); + dep_toml.write_str(indoc! {r#" + [project] + name = "dep" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + "#})?; + + // Add the dependency with `--workspace` flag from the project directory. + uv_snapshot!(context.filters(), context + .add() + .arg("./dep") + .arg("--workspace"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Added `dep` to workspace members + Resolved 2 packages in [TIME] + Audited in [TIME] + "); + + let pyproject_toml = context.read("pyproject.toml"); + assert_snapshot!( + pyproject_toml, @r#" + [project] + name = "parent" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [ + "dep", + ] + + [tool.uv.workspace] + members = [ + "dep", + ] + + [tool.uv.sources] + dep = { workspace = true } + "# + ); + + Ok(()) +} diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 82fe0fa3d..17fe6cfce 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -582,6 +582,8 @@ uv add [OPTIONS] >
    --upgrade-package, -P upgrade-package

    Allow upgrades for a specific package, ignoring pinned versions in any existing output file. Implies --refresh-package

    --verbose, -v

    Use verbose output.

    You can configure fine-grained logging using the RUST_LOG environment variable. (https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives)

    +
    --workspace

    Add the dependency as a workspace member.

    +

    When used with a path dependency, the package will be added to the workspace's members list in the root pyproject.toml file.

    ## uv remove From 57338e558cdf7f0e82a49ae5530439016e43a450 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Wed, 9 Jul 2025 11:51:06 -0400 Subject: [PATCH 168/349] Drop trailing arguments when writing shebangs (#14519) ## Summary You can see in pip that they read the full first line, then replace it with the rewritten shebang, thereby dropping any trailing arguments on the shebang: https://github.com/pypa/pip/blob/65da0ff5349297da64ccadb4dd22ab41185ea0b9/src/pip/_internal/operations/install/wheel.py#L94 In contrast, we currently retain them, but write them _after_ the shebang, which is wrong. Closes https://github.com/astral-sh/uv/issues/14470. --- crates/uv-install-wheel/src/wheel.rs | 43 ++++++++--- crates/uv/tests/it/pip_install.rs | 107 +++++++++++++++++++++++++++ 2 files changed, 138 insertions(+), 12 deletions(-) diff --git a/crates/uv-install-wheel/src/wheel.rs b/crates/uv-install-wheel/src/wheel.rs index d013ac5eb..250143016 100644 --- a/crates/uv-install-wheel/src/wheel.rs +++ b/crates/uv-install-wheel/src/wheel.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; use std::io; -use std::io::{BufReader, Read, Seek, Write}; +use std::io::{BufReader, Read, Write}; use std::path::{Path, PathBuf}; use data_encoding::BASE64URL_NOPAD; @@ -144,7 +144,7 @@ fn format_shebang(executable: impl AsRef, os_name: &str, relocatable: bool /// /// fn get_script_executable(python_executable: &Path, is_gui: bool) -> PathBuf { - // Only check for pythonw.exe on Windows + // Only check for `pythonw.exe` on Windows. if cfg!(windows) && is_gui { python_executable .file_name() @@ -431,22 +431,41 @@ fn install_script( Err(err) => return Err(Error::Io(err)), } let size_and_encoded_hash = if start == placeholder_python { - let is_gui = { - let mut buf = vec![0; 1]; - script.read_exact(&mut buf)?; - if buf == b"w" { - true - } else { - script.seek_relative(-1)?; - false + // Read the rest of the first line, one byte at a time, until we hit a newline. + let mut is_gui = false; + let mut first = true; + let mut byte = [0u8; 1]; + loop { + match script.read_exact(&mut byte) { + Ok(()) => { + if byte[0] == b'\n' || byte[0] == b'\r' { + break; + } + + // Check if this is a GUI script (starts with 'w'). + if first { + is_gui = byte[0] == b'w'; + first = false; + } + } + Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => break, + Err(err) => return Err(Error::Io(err)), } - }; + } + let executable = get_script_executable(&layout.sys_executable, is_gui); let executable = get_relocatable_executable(executable, layout, relocatable)?; - let start = format_shebang(&executable, &layout.os_name, relocatable) + let mut start = format_shebang(&executable, &layout.os_name, relocatable) .as_bytes() .to_vec(); + // Use appropriate line ending for the platform. + if layout.os_name == "nt" { + start.extend_from_slice(b"\r\n"); + } else { + start.push(b'\n'); + } + let mut target = uv_fs::tempfile_in(&layout.scheme.scripts)?; let size_and_encoded_hash = copy_and_hash(&mut start.chain(script), &mut target)?; diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index a33e08d90..f231198e4 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -11490,3 +11490,110 @@ fn conflicting_flags_clap_bug() { " ); } + +/// Test that shebang arguments are stripped when installing scripts +#[test] +#[cfg(unix)] +fn strip_shebang_arguments() -> Result<()> { + let context = TestContext::new("3.12"); + + let project_dir = context.temp_dir.child("shebang_test"); + project_dir.create_dir_all()?; + + // Create a package with scripts that have shebang arguments. + let pyproject_toml = project_dir.child("pyproject.toml"); + pyproject_toml.write_str(indoc! {r#" + [project] + name = "shebang-test" + version = "0.1.0" + + [build-system] + requires = ["setuptools>=61.0"] + build-backend = "setuptools.build_meta" + + [tool.setuptools] + packages = ["shebang_test"] + + [tool.setuptools.data-files] + "scripts" = ["scripts/custom_script", "scripts/custom_gui_script"] + "#})?; + + // Create the package directory. + let package_dir = project_dir.child("shebang_test"); + package_dir.create_dir_all()?; + + // Create an `__init__.py` file in the package directory. + let init_file = package_dir.child("__init__.py"); + init_file.touch()?; + + // Create scripts directory with scripts that have shebangs with arguments + let scripts_dir = project_dir.child("scripts"); + scripts_dir.create_dir_all()?; + + let script_with_args = scripts_dir.child("custom_script"); + script_with_args.write_str(indoc! {r#" + #!python -E -s + # This is a test script with shebang arguments + import sys + print(f"Hello from {sys.executable}") + print(f"Arguments: {sys.argv}") + "#})?; + + let gui_script_with_args = scripts_dir.child("custom_gui_script"); + gui_script_with_args.write_str(indoc! {r#" + #!pythonw -E + # This is a test GUI script with shebang arguments + import sys + print(f"Hello from GUI script: {sys.executable}") + "#})?; + + // Create a `setup.py` that explicitly handles scripts. + let setup_py = project_dir.child("setup.py"); + setup_py.write_str(indoc! {r" + from setuptools import setup + setup(scripts=['scripts/custom_script', 'scripts/custom_gui_script']) + "})?; + + // Install the package. + uv_snapshot!(context.filters(), context.pip_install().arg(project_dir.path()), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + shebang-test==0.1.0 (from file://[TEMP_DIR]/shebang_test) + "###); + + // Check the installed scripts have their shebangs stripped of arguments. + let custom_script_path = venv_bin_path(&context.venv).join("custom_script"); + let script_content = fs::read_to_string(&custom_script_path)?; + + insta::with_settings!({filters => context.filters() + }, { + insta::assert_snapshot!(script_content, @r#" + #![VENV]/bin/python3 + # This is a test script with shebang arguments + import sys + print(f"Hello from {sys.executable}") + print(f"Arguments: {sys.argv}") + "#); + }); + + let custom_gui_script_path = venv_bin_path(&context.venv).join("custom_gui_script"); + let gui_script_content = fs::read_to_string(&custom_gui_script_path)?; + + insta::with_settings!({filters => context.filters() + }, { + insta::assert_snapshot!(gui_script_content, @r#" + #![VENV]/bin/python3 + # This is a test GUI script with shebang arguments + import sys + print(f"Hello from GUI script: {sys.executable}") + "#); + }); + + Ok(()) +} From 1958aa26bd1f93b4345a5c954c8ad3ac763e1225 Mon Sep 17 00:00:00 2001 From: Kevin Nakamura Date: Wed, 9 Jul 2025 15:56:48 +0000 Subject: [PATCH 169/349] Add debug message when skipping Python downloads (#14509) # Description Several users, myself included, had some issues with Anki (recently migrated to uv). https://forums.ankiweb.net/t/bug-anki-25-07-fails-to-launch-on-linux/63475 zanieb came in and gave us pointers, including looking at our uv logs. https://github.com/ankitects/anki/pull/4074#issuecomment-3046992777 log: https://github.com/Grinkers/uv/pull/1#issuecomment-3047538135 The actual issue was that I had a system config in /etc/uv/uv.toml but uv wasn't giving useful feedback for its combining/unification. A higher level issue is that there's nice logs, however logging is initialized after! We want to log files read, but need to read the files to know what log level to use. https://github.com/astral-sh/uv/blob/7e48292fac968b015c4521e193b09e27af1d5c7b/crates/uv-settings/src/lib.rs#L68 https://github.com/astral-sh/uv/blob/7e48292fac968b015c4521e193b09e27af1d5c7b/crates/uv/src/lib.rs#L354 zanieb mentioned there's https://github.com/astral-sh/uv/issues/13123, so consider this a +1 to that. ## Result The end of the output will be: ``` DEBUG Downloads disabled. Skipping... DEBUG Released lock at `/tmp/uv-823c7b0e73da3e08.lock` error: No interpreter found for Python 3.13.5 in managed installations ``` Sorry for the minuscule sized PR. Feel free to close if there's a bigger logging pass. --------- Co-authored-by: Zanie Blue --- crates/uv-python/src/installation.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/uv-python/src/installation.rs b/crates/uv-python/src/installation.rs index d46643d21..35cbf9ce5 100644 --- a/crates/uv-python/src/installation.rs +++ b/crates/uv-python/src/installation.rs @@ -112,6 +112,7 @@ impl PythonInstallation { && client_builder.connectivity.is_online(); if !downloads_enabled { + debug!("Python downloads are disabled. Skipping check for available downloads..."); return Err(err); } From 812a3e7c34030e54937a64395e5a341c323f9d89 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 9 Jul 2025 12:15:41 -0500 Subject: [PATCH 170/349] Bump version to 0.7.20 (#14525) --- CHANGELOG.md | 40 +++++++++++++++++++++++++-- Cargo.lock | 6 ++-- crates/uv-build/Cargo.toml | 2 +- crates/uv-build/pyproject.toml | 2 +- crates/uv-version/Cargo.toml | 2 +- crates/uv/Cargo.toml | 2 +- docs/concepts/build-backend.md | 2 +- 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/pre-commit.md | 10 +++---- pyproject.toml | 2 +- 13 files changed, 62 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1c163331..067e3a16c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,42 @@ +## 0.7.20 + +### Python + +- Add Python 3.14.0b4 +- Add zstd support to Python 3.14 on Unix (it already was available on Windows) +- Add PyPy 7.3.20 (for Python 3.11.13) + +See the [PyPy](https://pypy.org/posts/2025/07/pypy-v7320-release.html) and [`python-build-standalone`](https://github.com/astral-sh/python-build-standalone/releases/tag/20250708) release notes for more details. + +### Enhancements + +- Add `--workspace` flag to `uv add` ([#14496](https://github.com/astral-sh/uv/pull/14496)) +- Add auto-detection for Intel GPUs ([#14386](https://github.com/astral-sh/uv/pull/14386)) +- Drop trailing arguments when writing shebangs ([#14519](https://github.com/astral-sh/uv/pull/14519)) +- Add debug message when skipping Python downloads ([#14509](https://github.com/astral-sh/uv/pull/14509)) + +### Bug fixes + +- Revert normalization of trailing slashes on index URLs ([#14511](https://github.com/astral-sh/uv/pull/14511)) +- Fix forced resolution with all extras in `uv version` ([#14434](https://github.com/astral-sh/uv/pull/14434)) +- Fix handling of pre-releases in preferences ([#14498](https://github.com/astral-sh/uv/pull/14498)) +- Remove transparent variants in `uv-extract` to enable retries ([#14450](https://github.com/astral-sh/uv/pull/14450)) + +### Rust API + +- Add method to get packages involved in a `NoSolutionError` ([#14457](https://github.com/astral-sh/uv/pull/14457)) +- Make `ErrorTree` for `NoSolutionError` public ([#14444](https://github.com/astral-sh/uv/pull/14444)) + +### Documentation + +- Finish incomplete sentence in pip migration guide ([#14432](https://github.com/astral-sh/uv/pull/14432)) +- Remove `cache-dependency-glob` examples for `setup-uv` ([#14493](https://github.com/astral-sh/uv/pull/14493)) +- Remove `uv pip sync` suggestion with `pyproject.toml` ([#14510](https://github.com/astral-sh/uv/pull/14510)) +- Update documentation for GitHub to use `setup-uv@v6` ([#14490](https://github.com/astral-sh/uv/pull/14490)) + ## 0.7.19 The **[uv build backend](https://docs.astral.sh/uv/concepts/build-backend/) is now stable**, and considered ready for production use. @@ -45,9 +81,9 @@ See the [python-build-standalone release](https://github.com/astral-sh/python-bu ### Python - Added arm64 Windows Python 3.11, 3.12, 3.13, and 3.14 - + These are not downloaded by default, since x86-64 Python has broader ecosystem support on Windows. - However, they can be requested with `cpython--windows-aarch64`. +However, they can be requested with `cpython--windows-aarch64`. See the [python-build-standalone release](https://github.com/astral-sh/python-build-standalone/releases/tag/20250630) for more details. diff --git a/Cargo.lock b/Cargo.lock index ef7511af5..8b62ed404 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4608,7 +4608,7 @@ dependencies = [ [[package]] name = "uv" -version = "0.7.19" +version = "0.7.20" dependencies = [ "anstream", "anyhow", @@ -4772,7 +4772,7 @@ dependencies = [ [[package]] name = "uv-build" -version = "0.7.19" +version = "0.7.20" dependencies = [ "anyhow", "uv-build-backend", @@ -5962,7 +5962,7 @@ dependencies = [ [[package]] name = "uv-version" -version = "0.7.19" +version = "0.7.20" [[package]] name = "uv-virtualenv" diff --git a/crates/uv-build/Cargo.toml b/crates/uv-build/Cargo.toml index 34dfa996a..ffbea0ea9 100644 --- a/crates/uv-build/Cargo.toml +++ b/crates/uv-build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-build" -version = "0.7.19" +version = "0.7.20" edition.workspace = true rust-version.workspace = true homepage.workspace = true diff --git a/crates/uv-build/pyproject.toml b/crates/uv-build/pyproject.toml index 660e95c95..13c21edd8 100644 --- a/crates/uv-build/pyproject.toml +++ b/crates/uv-build/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uv-build" -version = "0.7.19" +version = "0.7.20" description = "The uv build backend" authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }] requires-python = ">=3.8" diff --git a/crates/uv-version/Cargo.toml b/crates/uv-version/Cargo.toml index 9b9ccd9bd..f1b47dd1d 100644 --- a/crates/uv-version/Cargo.toml +++ b/crates/uv-version/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-version" -version = "0.7.19" +version = "0.7.20" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml index 0a352d2b1..7fa28ed67 100644 --- a/crates/uv/Cargo.toml +++ b/crates/uv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv" -version = "0.7.19" +version = "0.7.20" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } diff --git a/docs/concepts/build-backend.md b/docs/concepts/build-backend.md index e68069ddb..551ac5cf4 100644 --- a/docs/concepts/build-backend.md +++ b/docs/concepts/build-backend.md @@ -36,7 +36,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.7.19,<0.8.0"] +requires = ["uv_build>=0.7.20,<0.8.0"] build-backend = "uv_build" ``` diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index a507a3ade..12895b56e 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.7.19/install.sh | sh + $ curl -LsSf https://astral.sh/uv/0.7.20/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.7.19/install.ps1 | iex" + PS> powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/0.7.20/install.ps1 | iex" ``` !!! tip diff --git a/docs/guides/integration/aws-lambda.md b/docs/guides/integration/aws-lambda.md index 233969420..1e6c7c47a 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.7.19 AS uv +FROM ghcr.io/astral-sh/uv:0.7.20 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.7.19 AS uv +FROM ghcr.io/astral-sh/uv:0.7.20 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 bfbae2a7b..0445b155c 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.7.19` +- `ghcr.io/astral-sh/uv:{major}.{minor}.{patch}`, e.g., `ghcr.io/astral-sh/uv:0.7.20` - `ghcr.io/astral-sh/uv:{major}.{minor}`, e.g., `ghcr.io/astral-sh/uv:0.7` (the latest patch version) @@ -75,7 +75,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.7.19-alpine`. +`ghcr.io/astral-sh/uv:{major}.{minor}-{base}`, e.g., `ghcr.io/astral-sh/uv:0.7.20-alpine`. For more details, see the [GitHub Container](https://github.com/astral-sh/uv/pkgs/container/uv) page. @@ -113,7 +113,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.7.19 /uv /uvx /bin/ +COPY --from=ghcr.io/astral-sh/uv:0.7.20 /uv /uvx /bin/ ``` !!! tip @@ -131,7 +131,7 @@ COPY --from=ghcr.io/astral-sh/uv:0.7.19 /uv /uvx /bin/ Or, with the installer: ```dockerfile -ADD https://astral.sh/uv/0.7.19/install.sh /uv-installer.sh +ADD https://astral.sh/uv/0.7.20/install.sh /uv-installer.sh ``` ### Installing a project @@ -557,5 +557,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.7.19`, or (even better) the specific image digest, + version tag, e.g., `ghcr.io/astral-sh/uv:0.7.20`, 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 b41b99e2d..5d5026b52 100644 --- a/docs/guides/integration/github.md +++ b/docs/guides/integration/github.md @@ -47,7 +47,7 @@ jobs: uses: astral-sh/setup-uv@v6 with: # Install a specific version of uv. - version: "0.7.19" + version: "0.7.20" ``` ## Setting up Python diff --git a/docs/guides/integration/pre-commit.md b/docs/guides/integration/pre-commit.md index b2f637a42..0495581c2 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.7.19 + rev: 0.7.20 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.7.19 + rev: 0.7.20 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.7.19 + rev: 0.7.20 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.7.19 + rev: 0.7.20 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.7.19 + rev: 0.7.20 hooks: # Compile requirements - id: pip-compile diff --git a/pyproject.toml b/pyproject.toml index 5d4261360..df118d720 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "uv" -version = "0.7.19" +version = "0.7.20" 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" From e798b09aa4bb52bf0c80d1e820a0c0f5fb82bb81 Mon Sep 17 00:00:00 2001 From: konsti Date: Wed, 9 Jul 2025 19:45:44 +0200 Subject: [PATCH 171/349] Multiple modules in namespace packages (#14460) Support multiple root modules in namespace packages by enumerating them: ```toml [tool.uv.build-backend] module-name = ["foo", "bar"] ``` This allows applications with multiple root packages without migrating to workspaces. Since those are regular module names (we iterate over them an process each one like a single module names), it allows combining dotted (namespace) names and regular names. It also technically allows combining regular and stub modules, even though this is even less recommends. We don't recommend this structure (please use a workspace instead, or structure everything in one root module), but it reduces the number of cases that need `namespace = true`. Fixes #14435 Fixes #14438 --------- Co-authored-by: Zanie Blue --- Cargo.lock | 1 + crates/uv-build-backend/Cargo.toml | 1 + crates/uv-build-backend/src/lib.rs | 188 ++++++++++++++++++--- crates/uv-build-backend/src/settings.rs | 19 ++- crates/uv-build-backend/src/source_dist.rs | 24 +-- crates/uv-build-backend/src/wheel.rs | 91 +++++----- docs/concepts/build-backend.md | 38 ++++- docs/reference/settings.md | 6 +- uv.schema.json | 28 ++- 9 files changed, 310 insertions(+), 86 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8b62ed404..5861cd325 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4790,6 +4790,7 @@ dependencies = [ "indoc", "insta", "itertools 0.14.0", + "rustc-hash", "schemars", "serde", "sha2", diff --git a/crates/uv-build-backend/Cargo.toml b/crates/uv-build-backend/Cargo.toml index f23581662..7714423d4 100644 --- a/crates/uv-build-backend/Cargo.toml +++ b/crates/uv-build-backend/Cargo.toml @@ -31,6 +31,7 @@ flate2 = { workspace = true, default-features = false } fs-err = { workspace = true } globset = { workspace = true } itertools = { workspace = true } +rustc-hash = { workspace = true } schemars = { workspace = true, optional = true } serde = { workspace = true } sha2 = { workspace = true } diff --git a/crates/uv-build-backend/src/lib.rs b/crates/uv-build-backend/src/lib.rs index 548214c32..2ec11aeeb 100644 --- a/crates/uv-build-backend/src/lib.rs +++ b/crates/uv-build-backend/src/lib.rs @@ -22,6 +22,7 @@ use uv_normalize::PackageName; use uv_pypi_types::{Identifier, IdentifierParseError}; use crate::metadata::ValidationError; +use crate::settings::ModuleName; #[derive(Debug, Error)] pub enum Error { @@ -184,7 +185,7 @@ fn check_metadata_directory( Ok(()) } -/// Returns the source root and the module path with the `__init__.py[i]` below to it while +/// Returns the source root and the module path(s) with the `__init__.py[i]` below to it while /// checking the project layout and names. /// /// Some target platforms have case-sensitive filesystems, while others have case-insensitive @@ -198,13 +199,15 @@ fn check_metadata_directory( /// dist-info-normalization, the rules are lowercasing, replacing `.` with `_` and /// replace `-` with `_`. Since `.` and `-` are not allowed in identifiers, we can use a string /// comparison with the module name. +/// +/// While we recommend one module per package, it is possible to declare a list of modules. fn find_roots( source_tree: &Path, pyproject_toml: &PyProjectToml, relative_module_root: &Path, - module_name: Option<&str>, + module_name: Option<&ModuleName>, namespace: bool, -) -> Result<(PathBuf, PathBuf), Error> { +) -> Result<(PathBuf, Vec), Error> { let relative_module_root = uv_fs::normalize_path(relative_module_root); let src_root = source_tree.join(&relative_module_root); if !src_root.starts_with(source_tree) { @@ -215,22 +218,45 @@ fn find_roots( if namespace { // `namespace = true` disables module structure checks. - let module_relative = if let Some(module_name) = module_name { - module_name.split('.').collect::() + let modules_relative = if let Some(module_name) = module_name { + match module_name { + ModuleName::Name(name) => { + vec![name.split('.').collect::()] + } + ModuleName::Names(names) => names + .iter() + .map(|name| name.split('.').collect::()) + .collect(), + } } else { - PathBuf::from(pyproject_toml.name().as_dist_info_name().to_string()) + vec![PathBuf::from( + pyproject_toml.name().as_dist_info_name().to_string(), + )] }; - debug!("Namespace module path: {}", module_relative.user_display()); - return Ok((src_root, module_relative)); + for module_relative in &modules_relative { + debug!("Namespace module path: {}", module_relative.user_display()); + } + return Ok((src_root, modules_relative)); } - let module_relative = if let Some(module_name) = module_name { - module_path_from_module_name(&src_root, module_name)? + let modules_relative = if let Some(module_name) = module_name { + match module_name { + ModuleName::Name(name) => vec![module_path_from_module_name(&src_root, name)?], + ModuleName::Names(names) => names + .iter() + .map(|name| module_path_from_module_name(&src_root, name)) + .collect::>()?, + } } else { - find_module_path_from_package_name(&src_root, pyproject_toml.name())? + vec![find_module_path_from_package_name( + &src_root, + pyproject_toml.name(), + )?] }; - debug!("Module path: {}", module_relative.user_display()); - Ok((src_root, module_relative)) + for module_relative in &modules_relative { + debug!("Module path: {}", module_relative.user_display()); + } + Ok((src_root, modules_relative)) } /// Infer stubs packages from package name alone. @@ -410,6 +436,15 @@ mod tests { }) } + fn build_err(source_root: &Path) -> String { + let dist = TempDir::new().unwrap(); + let build_err = build(source_root, dist.path()).unwrap_err(); + let err_message: String = format_err(&build_err) + .replace(&source_root.user_display().to_string(), "[TEMP_PATH]") + .replace('\\', "/"); + err_message + } + fn sdist_contents(source_dist_path: &Path) -> Vec { let sdist_reader = BufReader::new(File::open(source_dist_path).unwrap()); let mut source_dist = tar::Archive::new(GzDecoder::new(sdist_reader)); @@ -998,13 +1033,8 @@ mod tests { fs_err::create_dir_all(src.path().join("src").join("simple_namespace").join("part")) .unwrap(); - let dist = TempDir::new().unwrap(); - let build_err = build(src.path(), dist.path()).unwrap_err(); - let err_message = format_err(&build_err) - .replace(&src.path().user_display().to_string(), "[TEMP_PATH]") - .replace('\\', "/"); assert_snapshot!( - err_message, + build_err(src.path()), @"Expected a Python module at: `[TEMP_PATH]/src/simple_namespace/part/__init__.py`" ); @@ -1025,16 +1055,13 @@ mod tests { .join("simple_namespace") .join("__init__.py"); File::create(&bogus_init_py).unwrap(); - let build_err = build(src.path(), dist.path()).unwrap_err(); - let err_message = format_err(&build_err) - .replace(&src.path().user_display().to_string(), "[TEMP_PATH]") - .replace('\\', "/"); assert_snapshot!( - err_message, + build_err(src.path()), @"For namespace packages, `__init__.py[i]` is not allowed in parent directory: `[TEMP_PATH]/src/simple_namespace`" ); fs_err::remove_file(bogus_init_py).unwrap(); + let dist = TempDir::new().unwrap(); let build1 = build(src.path(), dist.path()).unwrap(); assert_snapshot!(build1.source_dist_contents.join("\n"), @r" simple_namespace_part-1.0.0/ @@ -1209,4 +1236,117 @@ mod tests { cloud_db_schema_stubs-1.0.0.dist-info/WHEEL "); } + + /// A package with multiple modules, one a regular module and two namespace modules. + #[test] + fn multiple_module_names() { + let src = TempDir::new().unwrap(); + let pyproject_toml = indoc! {r#" + [project] + name = "simple-namespace-part" + version = "1.0.0" + + [tool.uv.build-backend] + module-name = ["foo", "simple_namespace.part_a", "simple_namespace.part_b"] + + [build-system] + requires = ["uv_build>=0.5.15,<0.6"] + build-backend = "uv_build" + "# + }; + fs_err::write(src.path().join("pyproject.toml"), pyproject_toml).unwrap(); + fs_err::create_dir_all(src.path().join("src").join("foo")).unwrap(); + fs_err::create_dir_all( + src.path() + .join("src") + .join("simple_namespace") + .join("part_a"), + ) + .unwrap(); + fs_err::create_dir_all( + src.path() + .join("src") + .join("simple_namespace") + .join("part_b"), + ) + .unwrap(); + + // Most of these checks exist in other tests too, but we want to ensure that they apply + // with multiple modules too. + + // The first module is missing an `__init__.py`. + assert_snapshot!( + build_err(src.path()), + @"Expected a Python module at: `[TEMP_PATH]/src/foo/__init__.py`" + ); + + // Create the first correct `__init__.py` file + File::create(src.path().join("src").join("foo").join("__init__.py")).unwrap(); + + // The second module, a namespace, is missing an `__init__.py`. + assert_snapshot!( + build_err(src.path()), + @"Expected a Python module at: `[TEMP_PATH]/src/simple_namespace/part_a/__init__.py`" + ); + + // Create the other two correct `__init__.py` files + File::create( + src.path() + .join("src") + .join("simple_namespace") + .join("part_a") + .join("__init__.py"), + ) + .unwrap(); + File::create( + src.path() + .join("src") + .join("simple_namespace") + .join("part_b") + .join("__init__.py"), + ) + .unwrap(); + + // For the second module, a namespace, there must not be an `__init__.py` here. + let bogus_init_py = src + .path() + .join("src") + .join("simple_namespace") + .join("__init__.py"); + File::create(&bogus_init_py).unwrap(); + assert_snapshot!( + build_err(src.path()), + @"For namespace packages, `__init__.py[i]` is not allowed in parent directory: `[TEMP_PATH]/src/simple_namespace`" + ); + fs_err::remove_file(bogus_init_py).unwrap(); + + let dist = TempDir::new().unwrap(); + let build = build(src.path(), dist.path()).unwrap(); + assert_snapshot!(build.source_dist_contents.join("\n"), @r" + simple_namespace_part-1.0.0/ + simple_namespace_part-1.0.0/PKG-INFO + simple_namespace_part-1.0.0/pyproject.toml + simple_namespace_part-1.0.0/src + simple_namespace_part-1.0.0/src/foo + simple_namespace_part-1.0.0/src/foo/__init__.py + simple_namespace_part-1.0.0/src/simple_namespace + simple_namespace_part-1.0.0/src/simple_namespace/part_a + simple_namespace_part-1.0.0/src/simple_namespace/part_a/__init__.py + simple_namespace_part-1.0.0/src/simple_namespace/part_b + simple_namespace_part-1.0.0/src/simple_namespace/part_b/__init__.py + "); + assert_snapshot!(build.wheel_contents.join("\n"), @r" + foo/ + foo/__init__.py + simple_namespace/ + simple_namespace/part_a/ + simple_namespace/part_a/__init__.py + simple_namespace/part_b/ + simple_namespace/part_b/__init__.py + simple_namespace_part-1.0.0.dist-info/ + simple_namespace_part-1.0.0.dist-info/METADATA + simple_namespace_part-1.0.0.dist-info/RECORD + simple_namespace_part-1.0.0.dist-info/WHEEL + "); + } } diff --git a/crates/uv-build-backend/src/settings.rs b/crates/uv-build-backend/src/settings.rs index 3b413e8e3..9e9e44961 100644 --- a/crates/uv-build-backend/src/settings.rs +++ b/crates/uv-build-backend/src/settings.rs @@ -34,15 +34,19 @@ pub struct BuildBackendSettings { /// For namespace packages with a single module, the path can be dotted, e.g., `foo.bar` or /// `foo-stubs.bar`. /// + /// For namespace packages with multiple modules, the path can be a list, e.g., + /// `["foo", "bar"]`. We recommend using a single module per package, splitting multiple + /// packages into a workspace. + /// /// Note that using this option runs the risk of creating two packages with different names but /// the same module names. Installing such packages together leads to unspecified behavior, /// often with corrupted files or directory trees. #[option( default = r#"None"#, - value_type = "str", + value_type = "str | list[str]", example = r#"module-name = "sklearn""# )] - pub module_name: Option, + pub module_name: Option, /// Glob expressions which files and directories to additionally include in the source /// distribution. @@ -181,6 +185,17 @@ impl Default for BuildBackendSettings { } } +/// Whether to include a single module or multiple modules. +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[serde(untagged)] +pub enum ModuleName { + /// A single module name. + Name(String), + /// Multiple module names, which are all included. + Names(Vec), +} + /// Data includes for wheels. /// /// See `BuildBackendSettings::data`. diff --git a/crates/uv-build-backend/src/source_dist.rs b/crates/uv-build-backend/src/source_dist.rs index 0a302ccf2..3b6d11ba4 100644 --- a/crates/uv-build-backend/src/source_dist.rs +++ b/crates/uv-build-backend/src/source_dist.rs @@ -68,22 +68,24 @@ fn source_dist_matcher( includes.push(globset::escape("pyproject.toml")); // Check that the source tree contains a module. - let (src_root, module_relative) = find_roots( + let (src_root, modules_relative) = find_roots( source_tree, pyproject_toml, &settings.module_root, - settings.module_name.as_deref(), + settings.module_name.as_ref(), settings.namespace, )?; - // The wheel must not include any files included by the source distribution (at least until we - // have files generated in the source dist -> wheel build step). - let import_path = uv_fs::normalize_path( - &uv_fs::relative_to(src_root.join(module_relative), source_tree) - .expect("module root is inside source tree"), - ) - .portable_display() - .to_string(); - includes.push(format!("{}/**", globset::escape(&import_path))); + for module_relative in modules_relative { + // The wheel must not include any files included by the source distribution (at least until we + // have files generated in the source dist -> wheel build step). + let import_path = uv_fs::normalize_path( + &uv_fs::relative_to(src_root.join(module_relative), source_tree) + .expect("module root is inside source tree"), + ) + .portable_display() + .to_string(); + includes.push(format!("{}/**", globset::escape(&import_path))); + } for include in includes { let glob = PortableGlobParser::Uv .parse(&include) diff --git a/crates/uv-build-backend/src/wheel.rs b/crates/uv-build-backend/src/wheel.rs index 7da232941..6eeb899d0 100644 --- a/crates/uv-build-backend/src/wheel.rs +++ b/crates/uv-build-backend/src/wheel.rs @@ -1,6 +1,7 @@ use fs_err::File; use globset::{GlobSet, GlobSetBuilder}; use itertools::Itertools; +use rustc_hash::FxHashSet; use sha2::{Digest, Sha256}; use std::io::{BufReader, Read, Write}; use std::path::{Path, PathBuf}; @@ -127,55 +128,61 @@ fn write_wheel( source_tree, pyproject_toml, &settings.module_root, - settings.module_name.as_deref(), + settings.module_name.as_ref(), settings.namespace, )?; - // For convenience, have directories for the whole tree in the wheel - for ancestor in module_relative.ancestors().skip(1) { - if ancestor == Path::new("") { - continue; - } - wheel_writer.write_directory(&ancestor.portable_display().to_string())?; - } - let mut files_visited = 0; - for entry in WalkDir::new(src_root.join(module_relative)) - .sort_by_file_name() - .into_iter() - .filter_entry(|entry| !exclude_matcher.is_match(entry.path())) - { - let entry = entry.map_err(|err| Error::WalkDir { - root: source_tree.to_path_buf(), - err, - })?; + let mut prefix_directories = FxHashSet::default(); + for module_relative in module_relative { + // For convenience, have directories for the whole tree in the wheel + for ancestor in module_relative.ancestors().skip(1) { + if ancestor == Path::new("") { + continue; + } + // Avoid duplicate directories in the zip. + if prefix_directories.insert(ancestor.to_path_buf()) { + wheel_writer.write_directory(&ancestor.portable_display().to_string())?; + } + } - files_visited += 1; - if files_visited > 10000 { - warn_user_once!( - "Visited more than 10,000 files for wheel build. \ + for entry in WalkDir::new(src_root.join(module_relative)) + .sort_by_file_name() + .into_iter() + .filter_entry(|entry| !exclude_matcher.is_match(entry.path())) + { + let entry = entry.map_err(|err| Error::WalkDir { + root: source_tree.to_path_buf(), + err, + })?; + + files_visited += 1; + if files_visited > 10000 { + warn_user_once!( + "Visited more than 10,000 files for wheel build. \ Consider using more constrained includes or more excludes." - ); - } + ); + } - // We only want to take the module root, but since excludes start at the source tree root, - // we strip higher than we iterate. - let match_path = entry - .path() - .strip_prefix(source_tree) - .expect("walkdir starts with root"); - let entry_path = entry - .path() - .strip_prefix(&src_root) - .expect("walkdir starts with root"); - if exclude_matcher.is_match(match_path) { - trace!("Excluding from module: `{}`", match_path.user_display()); - continue; - } + // We only want to take the module root, but since excludes start at the source tree root, + // we strip higher than we iterate. + let match_path = entry + .path() + .strip_prefix(source_tree) + .expect("walkdir starts with root"); + let entry_path = entry + .path() + .strip_prefix(&src_root) + .expect("walkdir starts with root"); + if exclude_matcher.is_match(match_path) { + trace!("Excluding from module: `{}`", match_path.user_display()); + continue; + } - let entry_path = entry_path.portable_display().to_string(); - debug!("Adding to wheel: {entry_path}"); - wheel_writer.write_dir_entry(&entry, &entry_path)?; + let entry_path = entry_path.portable_display().to_string(); + debug!("Adding to wheel: {entry_path}"); + wheel_writer.write_dir_entry(&entry, &entry_path)?; + } } debug!("Visited {files_visited} files for wheel build"); @@ -269,7 +276,7 @@ pub fn build_editable( source_tree, &pyproject_toml, &settings.module_root, - settings.module_name.as_deref(), + settings.module_name.as_ref(), settings.namespace, )?; diff --git a/docs/concepts/build-backend.md b/docs/concepts/build-backend.md index 551ac5cf4..a34bc7658 100644 --- a/docs/concepts/build-backend.md +++ b/docs/concepts/build-backend.md @@ -134,16 +134,50 @@ the project structure: pyproject.toml src ├── foo -│   └── __init__.py +│ └── __init__.py └── bar └── __init__.py ``` While we do not recommend this structure (i.e., you should use a workspace with multiple packages -instead), it is supported via the `namespace` option: +instead), it is supported by setting `module-name` to a list of names: ```toml title="pyproject.toml" [tool.uv.build-backend] +module-name = ["foo", "bar"] +``` + +For packages with many modules or complex namespaces, the `namespace = true` option can be used to +avoid explicitly declaring each module name, e.g.: + +```toml title="pyproject.toml" +[tool.uv.build-backend] +namespace = true +``` + +!!! warning + + Using `namespace = true` disables safety checks. Using an explicit list of module names is + strongly recommended outside of legacy projects. + +The `namespace` option can also be used with `module-name` to explicitly declare the root, e.g., for +the project structure: + +```text +pyproject.toml +src +└── foo + ├── bar + │ └── __init__.py + └── baz + └── __init__.py +``` + +The recommended configuration would be: + +```toml title="pyproject.toml" +[tool.uv.build-backend] +module-name = "foo" namespace = true ``` diff --git a/docs/reference/settings.md b/docs/reference/settings.md index 58948c80e..bdee1e4a1 100644 --- a/docs/reference/settings.md +++ b/docs/reference/settings.md @@ -474,13 +474,17 @@ being the module name, and which contain a `__init__.pyi` file. For namespace packages with a single module, the path can be dotted, e.g., `foo.bar` or `foo-stubs.bar`. +For namespace packages with multiple modules, the path can be a list, e.g., +`["foo", "bar"]`. We recommend using a single module per package, splitting multiple +packages into a workspace. + Note that using this option runs the risk of creating two packages with different names but the same module names. Installing such packages together leads to unspecified behavior, often with corrupted files or directory trees. **Default value**: `None` -**Type**: `str` +**Type**: `str | list[str]` **Example usage**: diff --git a/uv.schema.json b/uv.schema.json index dbc4f1168..aba25a46e 100644 --- a/uv.schema.json +++ b/uv.schema.json @@ -668,10 +668,14 @@ "default": true }, "module-name": { - "description": "The name of the module directory inside `module-root`.\n\nThe default module name is the package name with dots and dashes replaced by underscores.\n\nPackage names need to be valid Python identifiers, and the directory needs to contain a\n`__init__.py`. An exception are stubs packages, whose name ends with `-stubs`, with the stem\nbeing the module name, and which contain a `__init__.pyi` file.\n\nFor namespace packages with a single module, the path can be dotted, e.g., `foo.bar` or\n`foo-stubs.bar`.\n\nNote that using this option runs the risk of creating two packages with different names but\nthe same module names. Installing such packages together leads to unspecified behavior,\noften with corrupted files or directory trees.", - "type": [ - "string", - "null" + "description": "The name of the module directory inside `module-root`.\n\nThe default module name is the package name with dots and dashes replaced by underscores.\n\nPackage names need to be valid Python identifiers, and the directory needs to contain a\n`__init__.py`. An exception are stubs packages, whose name ends with `-stubs`, with the stem\nbeing the module name, and which contain a `__init__.pyi` file.\n\nFor namespace packages with a single module, the path can be dotted, e.g., `foo.bar` or\n`foo-stubs.bar`.\n\nFor namespace packages with multiple modules, the path can be a list, e.g.,\n`[\"foo\", \"bar\"]`. We recommend using a single module per package, splitting multiple\npackages into a workspace.\n\nNote that using this option runs the risk of creating two packages with different names but\nthe same module names. Installing such packages together leads to unspecified behavior,\noften with corrupted files or directory trees.", + "anyOf": [ + { + "$ref": "#/definitions/ModuleName" + }, + { + "type": "null" + } ], "default": null }, @@ -1052,6 +1056,22 @@ "description": "A PEP 508-compliant marker expression, e.g., `sys_platform == 'Darwin'`", "type": "string" }, + "ModuleName": { + "description": "Whether to include a single module or multiple modules.", + "anyOf": [ + { + "description": "A single module name.", + "type": "string" + }, + { + "description": "Multiple module names, which are all included.", + "type": "array", + "items": { + "type": "string" + } + } + ] + }, "PackageName": { "description": "The normalized name of a package.\n\nConverts the name to lowercase and collapses runs of `-`, `_`, and `.` down to a single `-`.\nFor example, `---`, `.`, and `__` are all converted to a single `-`.\n\nSee: ", "type": "string" From 2514203964449fcd3a5cac3320963aa57383e6b6 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 9 Jul 2025 15:36:08 -0500 Subject: [PATCH 172/349] Add missing 0.7.20 changelog entry (#14528) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 067e3a16c..9fa93fb03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ See the [PyPy](https://pypy.org/posts/2025/07/pypy-v7320-release.html) and [`pyt - Add auto-detection for Intel GPUs ([#14386](https://github.com/astral-sh/uv/pull/14386)) - Drop trailing arguments when writing shebangs ([#14519](https://github.com/astral-sh/uv/pull/14519)) - Add debug message when skipping Python downloads ([#14509](https://github.com/astral-sh/uv/pull/14509)) +- Add support for declaring multiple modules in namespace packages ([#14460](https://github.com/astral-sh/uv/pull/14460)) ### Bug fixes From 803eb338a38c3b747791bfb91a0adee1003dea0a Mon Sep 17 00:00:00 2001 From: konsti Date: Thu, 10 Jul 2025 01:20:02 +0200 Subject: [PATCH 173/349] Simplify relative URL handling (#14449) I was trying to figure out what the correct relative-and-absolute URL handling function was and realized there are two and they are redundant. --- crates/uv-client/src/error.rs | 3 -- crates/uv-client/src/registry_client.rs | 52 +++++++------------ crates/uv-distribution-types/src/file.rs | 16 ++++-- .../src/distribution_database.rs | 10 +--- crates/uv-distribution/src/error.rs | 2 - crates/uv-distribution/src/source/mod.rs | 18 ++----- crates/uv-pypi-types/src/base_url.rs | 31 ----------- 7 files changed, 37 insertions(+), 95 deletions(-) diff --git a/crates/uv-client/src/error.rs b/crates/uv-client/src/error.rs index 368e1ad33..754237fe2 100644 --- a/crates/uv-client/src/error.rs +++ b/crates/uv-client/src/error.rs @@ -152,9 +152,6 @@ pub enum ErrorKind { #[error(transparent)] InvalidUrl(#[from] uv_distribution_types::ToUrlError), - #[error(transparent)] - JoinRelativeUrl(#[from] uv_pypi_types::JoinRelativeError), - #[error(transparent)] Flat(#[from] FlatIndexError), diff --git a/crates/uv-client/src/registry_client.rs b/crates/uv-client/src/registry_client.rs index 5788ea56c..c7694676c 100644 --- a/crates/uv-client/src/registry_client.rs +++ b/crates/uv-client/src/registry_client.rs @@ -21,8 +21,8 @@ use uv_configuration::KeyringProviderType; use uv_configuration::{IndexStrategy, TrustedHost}; use uv_distribution_filename::{DistFilename, SourceDistFilename, WheelFilename}; use uv_distribution_types::{ - BuiltDist, File, FileLocation, IndexCapabilities, IndexFormat, IndexLocations, - IndexMetadataRef, IndexStatusCodeDecision, IndexStatusCodeStrategy, IndexUrl, IndexUrls, Name, + BuiltDist, File, IndexCapabilities, IndexFormat, IndexLocations, IndexMetadataRef, + IndexStatusCodeDecision, IndexStatusCodeStrategy, IndexUrl, IndexUrls, Name, }; use uv_metadata::{read_metadata_async_seek, read_metadata_async_stream}; use uv_normalize::PackageName; @@ -682,30 +682,14 @@ impl RegistryClient { let wheel = wheels.best_wheel(); - let location = match &wheel.file.url { - FileLocation::RelativeUrl(base, url) => { - let url = uv_pypi_types::base_url_join_relative(base, url) - .map_err(ErrorKind::JoinRelativeUrl)?; - if url.scheme() == "file" { - let path = url - .to_file_path() - .map_err(|()| ErrorKind::NonFileUrl(url.clone()))?; - WheelLocation::Path(path) - } else { - WheelLocation::Url(url) - } - } - FileLocation::AbsoluteUrl(url) => { - let url = url.to_url().map_err(ErrorKind::InvalidUrl)?; - if url.scheme() == "file" { - let path = url - .to_file_path() - .map_err(|()| ErrorKind::NonFileUrl(url.clone()))?; - WheelLocation::Path(path) - } else { - WheelLocation::Url(url) - } - } + let url = wheel.file.url.to_url().map_err(ErrorKind::InvalidUrl)?; + let location = if url.scheme() == "file" { + let path = url + .to_file_path() + .map_err(|()| ErrorKind::NonFileUrl(url.clone()))?; + WheelLocation::Path(path) + } else { + WheelLocation::Url(url) }; match location { @@ -1233,17 +1217,18 @@ mod tests { use url::Url; use uv_normalize::PackageName; - use uv_pypi_types::{JoinRelativeError, SimpleJson}; + use uv_pypi_types::SimpleJson; use uv_redacted::DisplaySafeUrl; use crate::{SimpleMetadata, SimpleMetadatum, html::SimpleHtml}; + use crate::RegistryClientBuilder; use uv_cache::Cache; + use uv_distribution_types::{FileLocation, ToUrlError}; + use uv_small_str::SmallString; use wiremock::matchers::{basic_auth, method, path_regex}; use wiremock::{Mock, MockServer, ResponseTemplate}; - use crate::RegistryClientBuilder; - type Error = Box; async fn start_test_server(username: &'static str, password: &'static str) -> MockServer { @@ -1469,7 +1454,7 @@ mod tests { /// /// See: #[test] - fn relative_urls_code_artifact() -> Result<(), JoinRelativeError> { + fn relative_urls_code_artifact() -> Result<(), ToUrlError> { let text = r#" @@ -1492,12 +1477,13 @@ mod tests { let base = DisplaySafeUrl::parse("https://account.d.codeartifact.us-west-2.amazonaws.com/pypi/shared-packages-pypi/simple/flask") .unwrap(); let SimpleHtml { base, files } = SimpleHtml::parse(text, &base).unwrap(); + let base = SmallString::from(base.as_str()); // Test parsing of the file urls let urls = files - .iter() - .map(|file| uv_pypi_types::base_url_join_relative(base.as_url().as_str(), &file.url)) - .collect::, JoinRelativeError>>()?; + .into_iter() + .map(|file| FileLocation::new(file.url, &base).to_url()) + .collect::, _>>()?; let urls = urls .iter() .map(DisplaySafeUrl::to_string) diff --git a/crates/uv-distribution-types/src/file.rs b/crates/uv-distribution-types/src/file.rs index 89da7b87e..b16305f73 100644 --- a/crates/uv-distribution-types/src/file.rs +++ b/crates/uv-distribution-types/src/file.rs @@ -57,10 +57,7 @@ impl File { .map_err(|err| FileConversionError::RequiresPython(err.line().clone(), err))?, size: file.size, upload_time_utc_ms: file.upload_time.map(Timestamp::as_millisecond), - url: match split_scheme(&file.url) { - Some(..) => FileLocation::AbsoluteUrl(UrlString::new(file.url)), - None => FileLocation::RelativeUrl(base.clone(), file.url), - }, + url: FileLocation::new(file.url, base), yanked: file.yanked, }) } @@ -77,6 +74,17 @@ pub enum FileLocation { } impl FileLocation { + /// Parse a relative or absolute URL on a page with a base URL. + /// + /// This follows the HTML semantics where a link on a page is resolved relative to the URL of + /// that page. + pub fn new(url: SmallString, base: &SmallString) -> Self { + match split_scheme(&url) { + Some(..) => FileLocation::AbsoluteUrl(UrlString::new(url)), + None => FileLocation::RelativeUrl(base.clone(), url), + } + } + /// Convert this location to a URL. /// /// A relative URL has its base joined to the path. An absolute URL is diff --git a/crates/uv-distribution/src/distribution_database.rs b/crates/uv-distribution/src/distribution_database.rs index dcb0a17e3..d18269730 100644 --- a/crates/uv-distribution/src/distribution_database.rs +++ b/crates/uv-distribution/src/distribution_database.rs @@ -20,8 +20,7 @@ use uv_client::{ }; use uv_distribution_filename::WheelFilename; use uv_distribution_types::{ - BuildableSource, BuiltDist, Dist, FileLocation, HashPolicy, Hashed, InstalledDist, Name, - SourceDist, + BuildableSource, BuiltDist, Dist, HashPolicy, Hashed, InstalledDist, Name, SourceDist, }; use uv_extract::hash::Hasher; use uv_fs::write_atomic; @@ -179,12 +178,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> { match dist { BuiltDist::Registry(wheels) => { let wheel = wheels.best_wheel(); - let url = match &wheel.file.url { - FileLocation::RelativeUrl(base, url) => { - uv_pypi_types::base_url_join_relative(base, url)? - } - FileLocation::AbsoluteUrl(url) => url.to_url()?, - }; + let url = wheel.file.url.to_url()?; // Create a cache entry for the wheel. let wheel_entry = self.build_context.cache().entry( diff --git a/crates/uv-distribution/src/error.rs b/crates/uv-distribution/src/error.rs index 7c2a0f804..7d0a9593b 100644 --- a/crates/uv-distribution/src/error.rs +++ b/crates/uv-distribution/src/error.rs @@ -25,8 +25,6 @@ pub enum Error { RelativePath(PathBuf), #[error(transparent)] InvalidUrl(#[from] uv_distribution_types::ToUrlError), - #[error(transparent)] - JoinRelativeUrl(#[from] uv_pypi_types::JoinRelativeError), #[error("Expected a file URL, but received: {0}")] NonFileUrl(DisplaySafeUrl), #[error(transparent)] diff --git a/crates/uv-distribution/src/source/mod.rs b/crates/uv-distribution/src/source/mod.rs index 2b73eb4ff..92d83e6ce 100644 --- a/crates/uv-distribution/src/source/mod.rs +++ b/crates/uv-distribution/src/source/mod.rs @@ -32,8 +32,8 @@ use uv_client::{ use uv_configuration::{BuildKind, BuildOutput, SourceStrategy}; use uv_distribution_filename::{SourceDistExtension, WheelFilename}; use uv_distribution_types::{ - BuildableSource, DirectorySourceUrl, FileLocation, GitSourceUrl, HashPolicy, Hashed, - PathSourceUrl, SourceDist, SourceUrl, + BuildableSource, DirectorySourceUrl, GitSourceUrl, HashPolicy, Hashed, PathSourceUrl, + SourceDist, SourceUrl, }; use uv_extract::hash::Hasher; use uv_fs::{rename_with_retry, write_atomic}; @@ -122,12 +122,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .join(dist.version.to_string()), ); - let url = match &dist.file.url { - FileLocation::RelativeUrl(base, url) => { - uv_pypi_types::base_url_join_relative(base, url)? - } - FileLocation::AbsoluteUrl(url) => url.to_url()?, - }; + let url = dist.file.url.to_url()?; // If the URL is a file URL, use the local path directly. if url.scheme() == "file" { @@ -271,12 +266,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .join(dist.version.to_string()), ); - let url = match &dist.file.url { - FileLocation::RelativeUrl(base, url) => { - uv_pypi_types::base_url_join_relative(base, url)? - } - FileLocation::AbsoluteUrl(url) => url.to_url()?, - }; + let url = dist.file.url.to_url()?; // If the URL is a file URL, use the local path directly. if url.scheme() == "file" { diff --git a/crates/uv-pypi-types/src/base_url.rs b/crates/uv-pypi-types/src/base_url.rs index eca7dbdb7..e8dae7968 100644 --- a/crates/uv-pypi-types/src/base_url.rs +++ b/crates/uv-pypi-types/src/base_url.rs @@ -1,37 +1,6 @@ use serde::{Deserialize, Serialize}; use uv_redacted::DisplaySafeUrl; -/// Join a relative URL to a base URL. -pub fn base_url_join_relative( - base: &str, - relative: &str, -) -> Result { - let base_url = DisplaySafeUrl::parse(base).map_err(|err| JoinRelativeError::ParseError { - original: base.to_string(), - source: err, - })?; - - base_url - .join(relative) - .map_err(|err| JoinRelativeError::ParseError { - original: format!("{base}/{relative}"), - source: err, - }) -} - -/// An error that occurs when `base_url_join_relative` fails. -/// -/// The error message includes the URL (`base` or `maybe_relative`) passed to -/// `base_url_join_relative` that provoked the error. -#[derive(Clone, Debug, thiserror::Error)] -pub enum JoinRelativeError { - #[error("Failed to parse URL: `{original}`")] - ParseError { - original: String, - source: url::ParseError, - }, -} - #[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)] pub struct BaseUrl( #[serde( From 92716606e570fed04f8d33f3c67179d7637483a3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 10 Jul 2025 00:40:58 +0000 Subject: [PATCH 174/349] Sync latest Python releases (#14531) 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 f556922c6..b9f58dd92 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/20250702/cpython-unix/targets.yml"; +const TARGETS_YML_URL: &str = "https://raw.githubusercontent.com/astral-sh/python-build-standalone/refs/tags/20250708/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 2611c1ac0..54170aba5 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 f900ef9e574fc1d08b535ffc123e57fd9d646dc9 Mon Sep 17 00:00:00 2001 From: Arthur Kim Date: Thu, 10 Jul 2025 20:17:30 +0900 Subject: [PATCH 175/349] `setup-python/v5` exists, `v6` don't exists. (#14533) ## Summary I suddenly found wrong documentation with setup-python in https://docs.astral.sh/uv/guides/integration/github/#setting-up-python. `setup-python/v5` exists, `setup-python/v6` don't exists. ## Test Plan nothing --- docs/guides/integration/github.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/integration/github.md b/docs/guides/integration/github.md index 5d5026b52..e7fea7b29 100644 --- a/docs/guides/integration/github.md +++ b/docs/guides/integration/github.md @@ -96,7 +96,7 @@ jobs: uses: astral-sh/setup-uv@v6 - name: "Set up Python" - uses: actions/setup-python@v6 + uses: actions/setup-python@v5 with: python-version-file: ".python-version" ``` @@ -119,7 +119,7 @@ jobs: uses: astral-sh/setup-uv@v6 - name: "Set up Python" - uses: actions/setup-python@v6 + uses: actions/setup-python@v5 with: python-version-file: "pyproject.toml" ``` From 573b9913983d1ca211bdcc787c7c18df910c6c1c Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 10 Jul 2025 08:10:21 -0500 Subject: [PATCH 176/349] Add nesting groups documentation for dependency groups (#14539) --- docs/concepts/projects/dependencies.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/concepts/projects/dependencies.md b/docs/concepts/projects/dependencies.md index 22d030637..e5c64a3ee 100644 --- a/docs/concepts/projects/dependencies.md +++ b/docs/concepts/projects/dependencies.md @@ -679,6 +679,26 @@ to resolve the requirements of the project with an error. If you have dependency groups that conflict with one another, resolution will fail unless you explicitly [declare them as conflicting](./config.md#conflicting-dependencies). +### Nesting groups + +A dependency group can include other dependency groups, e.g.: + +```toml title="pyproject.toml" +[dependency-groups] +dev = [ + {include-group = "lint"} + {include-group = "test"} +] +lint = [ + "ruff" +] +test = [ + "pytest" +] +``` + +An included group's dependencies cannot conflict with the other dependencies declared in a group. + ### Default groups By default, uv includes the `dev` dependency group in the environment (e.g., during `uv run` or From 42fcc81b3dc523fb9d44eeb865b48a7599177cec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8D=86=E9=95=BF=E9=80=AF?= Date: Thu, 10 Jul 2025 21:25:38 +0800 Subject: [PATCH 177/349] chore: remove redundant words in comment (#14532) ## Summary remove redundant words in comment ## Test Plan Signed-off-by: jingchanglu --- crates/uv-workspace/src/dependency_groups.rs | 2 +- crates/uv/src/commands/project/environment.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/uv-workspace/src/dependency_groups.rs b/crates/uv-workspace/src/dependency_groups.rs index 8503ae3ad..2dc2090bf 100644 --- a/crates/uv-workspace/src/dependency_groups.rs +++ b/crates/uv-workspace/src/dependency_groups.rs @@ -148,7 +148,7 @@ impl FlatDependencyGroups { if let Some(included) = resolved.get(include_group) { requirements.extend(included.requirements.iter().cloned()); - // Intersect the requires-python for this group with the the included group's + // Intersect the requires-python for this group with the included group's requires_python_intersection = requires_python_intersection .into_iter() .chain(included.requires_python.clone().into_iter().flatten()) diff --git a/crates/uv/src/commands/project/environment.rs b/crates/uv/src/commands/project/environment.rs index f43587ff0..a3cda28c1 100644 --- a/crates/uv/src/commands/project/environment.rs +++ b/crates/uv/src/commands/project/environment.rs @@ -204,7 +204,7 @@ impl CachedEnvironment { /// environment's `site-packages` directory to Python's import search paths in addition to /// the ephemeral environment's `site-packages` directory. This works well at runtime, but /// is too dynamic for static analysis tools like ty to understand. As such, we - /// additionally write the `sys.prefix` of the parent environment to to the + /// additionally write the `sys.prefix` of the parent environment to the /// `extends-environment` key of the ephemeral environment's `pyvenv.cfg` file, making it /// easier for these tools to statically and reliably understand the relationship between /// the two environments. From 042df4a7de85a4732c40e4583c357f83f27d1ba7 Mon Sep 17 00:00:00 2001 From: Aria Desires Date: Thu, 10 Jul 2025 09:45:17 -0400 Subject: [PATCH 178/349] Expand the functionality of `uv version --bump` to support pre-releases (#13578) This adds `alpha`, `beta`, `rc`, `stable`, `post`, and `dev` modes to `uv version --bump`. The components that `--bump` accepts are ordered as follows: major > minor > patch > stable > alpha > beta > rc > post > dev Bumping a component "clears" all lesser component (`alpha`, `beta`, and `rc` all overwrite each other): * `--bump minor` on `1.2.3a4.post5.dev6` => `1.3.0` * `--bump alpha` on `1.2.3a4.post5.dev6` => `1.2.3a5` * `--bump dev ` on `1.2.3a4.post5.dev6` => `1.2.3a4.post5.dev7` In addition, `--bump` can now be repeated. The primary motivation of this is "bump stable version and also enter a prerelease", but it technically lets you express other things if you want them: * `--bump patch --bump alpha` on `1.2.3` => `1.2.4a1` ("bump patch version and go to alpha 1") * `--bump minor --bump patch` on `1.2.3` => `1.3.1` ("bump minor version and got to patch 1") * `--bump minor --bump minor` on `1.2.3` => `1.4.0` ("bump minor version twice") The `--bump` flags are sorted by their priority, so that you don't need to remember the priority yourself. This ordering is the only "useful" one that preserves every `--bump` you passed, so there's no concern about loss of expressiveness. For instance `--bump minor --bump major` would just be `--bump major` if we didn't sort, as the major bump clears the minor version. The ordering of `beta` after `alpha` means `--bump alpha --bump beta` will just result in beta 1; this is the one case where a bump request will effectively get overwritten. The `stable` mode "bumps to the next stable release", clearing the pre (`alpha`, `beta`, `rc`), `dev`, and `post` components from a version (`1.2.3a4.post5.dev6` => `1.2.3`). The choice to clear `post` here is a bit odd, in that `1.2.3.post4` => `1.2.3` is actually a version decrease, but I think this gives a more intuitive model (as preserving `post5` in the previous example is definitely wrong), and also post-releases are extremely obscure so probably no one will notice. In the cases where this behaviour isn't useful, you probably wanted to pass `--bump patch` or something anyway which *should* definitely clear the `post5` (putting it another way: the only cases where `--bump stable` has dubious behaviour is when you wanted it to do a noop, which, is a command you could have just not written at all). In all cases we preserve the "epoch" and "local" components of a version, so the `7!` and `+local` in `7!1.2.3+local` will never be modified by `--bump` (you can use the raw version set mode if you want to touch those). The preservation of `local` is another slightly odd choice, but it's a really obscure feature (so again it mostly won't come up) and when it's used it seems to mostly be used for referring to variant releases, in which case preserving it tends to be correct. Fixes #13223 --------- Co-authored-by: Zanie Blue --- crates/uv-cli/src/lib.rs | 54 +- crates/uv-pep440/src/lib.rs | 4 +- crates/uv-pep440/src/version.rs | 452 ++++++++++++++++ crates/uv/src/commands/project/version.rs | 174 +++++-- crates/uv/src/lib.rs | 2 +- crates/uv/src/settings.rs | 2 +- crates/uv/tests/it/version.rs | 606 +++++++++++++++++++++- docs/guides/package.md | 64 +++ docs/guides/projects.md | 32 ++ docs/reference/cli.md | 13 +- 10 files changed, 1347 insertions(+), 56 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 9f02490c6..3e9aba123 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -532,8 +532,10 @@ pub struct VersionArgs { pub value: Option, /// Update the project version using the given semantics + /// + /// This flag can be passed multiple times. #[arg(group = "operation", long)] - pub bump: Option, + pub bump: Vec, /// Don't write a new version to the `pyproject.toml` /// @@ -608,14 +610,56 @@ pub struct VersionArgs { pub python: Option>, } -#[derive(Debug, Copy, Clone, PartialEq, clap::ValueEnum)] +// Note that the ordering of the variants is significant, as when given a list of operations +// to perform, we sort them and apply them in order, so users don't have to think too hard about it. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)] pub enum VersionBump { - /// Increase the major version (1.2.3 => 2.0.0) + /// Increase the major version (e.g., 1.2.3 => 2.0.0) Major, - /// Increase the minor version (1.2.3 => 1.3.0) + /// Increase the minor version (e.g., 1.2.3 => 1.3.0) Minor, - /// Increase the patch version (1.2.3 => 1.2.4) + /// Increase the patch version (e.g., 1.2.3 => 1.2.4) Patch, + /// Move from a pre-release to stable version (e.g., 1.2.3b4.post5.dev6 => 1.2.3) + /// + /// Removes all pre-release components, but will not remove "local" components. + Stable, + /// Increase the alpha version (e.g., 1.2.3a4 => 1.2.3a5) + /// + /// To move from a stable to a pre-release version, combine this with a stable component, e.g., + /// for 1.2.3 => 2.0.0a1, you'd also include [`VersionBump::Major`]. + Alpha, + /// Increase the beta version (e.g., 1.2.3b4 => 1.2.3b5) + /// + /// To move from a stable to a pre-release version, combine this with a stable component, e.g., + /// for 1.2.3 => 2.0.0b1, you'd also include [`VersionBump::Major`]. + Beta, + /// Increase the rc version (e.g., 1.2.3rc4 => 1.2.3rc5) + /// + /// To move from a stable to a pre-release version, combine this with a stable component, e.g., + /// for 1.2.3 => 2.0.0rc1, you'd also include [`VersionBump::Major`].] + Rc, + /// Increase the post version (e.g., 1.2.3.post5 => 1.2.3.post6) + Post, + /// Increase the dev version (e.g., 1.2.3a4.dev6 => 1.2.3.dev7) + Dev, +} + +impl std::fmt::Display for VersionBump { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let string = match self { + VersionBump::Major => "major", + VersionBump::Minor => "minor", + VersionBump::Patch => "patch", + VersionBump::Stable => "stable", + VersionBump::Alpha => "alpha", + VersionBump::Beta => "beta", + VersionBump::Rc => "rc", + VersionBump::Post => "post", + VersionBump::Dev => "dev", + }; + string.fmt(f) + } } #[derive(Args)] diff --git a/crates/uv-pep440/src/lib.rs b/crates/uv-pep440/src/lib.rs index 0e8b50e72..40c7d97c6 100644 --- a/crates/uv-pep440/src/lib.rs +++ b/crates/uv-pep440/src/lib.rs @@ -29,8 +29,8 @@ pub use version_ranges::{ }; pub use { version::{ - LocalSegment, LocalVersion, LocalVersionSlice, MIN_VERSION, Operator, OperatorParseError, - Prerelease, PrereleaseKind, Version, VersionParseError, VersionPattern, + BumpCommand, LocalSegment, LocalVersion, LocalVersionSlice, MIN_VERSION, Operator, + OperatorParseError, Prerelease, PrereleaseKind, Version, VersionParseError, VersionPattern, VersionPatternParseError, }, version_specifier::{ diff --git a/crates/uv-pep440/src/version.rs b/crates/uv-pep440/src/version.rs index a496f95a2..223701692 100644 --- a/crates/uv-pep440/src/version.rs +++ b/crates/uv-pep440/src/version.rs @@ -643,6 +643,90 @@ impl Version { self.with_release(release) } + /// Various "increment the version" operations + pub fn bump(&mut self, bump: BumpCommand) { + // This code operates on the understanding that the components of a version form + // the following hierarchy: + // + // major > minor > patch > stable > pre > post > dev + // + // Any updates to something earlier in the hierarchy should clear all values lower + // in the hierarchy. So for instance: + // + // if you bump `minor`, then clear: patch, pre, post, dev + // if you bump `pre`, then clear: post, dev + // + // ...and so on. + // + // If you bump a value that doesn't exist, it will be set to "1". + // + // The special "stable" mode has no value, bumping it clears: pre, post, dev. + let full = self.make_full(); + + match bump { + BumpCommand::BumpRelease { index } => { + // Clear all sub-release items + full.pre = None; + full.post = None; + full.dev = None; + + // Use `max` here to try to do 0.2 => 0.3 instead of 0.2 => 0.3.0 + let old_parts = &full.release; + let len = old_parts.len().max(index + 1); + let new_release_vec = (0..len) + .map(|i| match i.cmp(&index) { + // Everything before the bumped value is preserved (or is an implicit 0) + Ordering::Less => old_parts.get(i).copied().unwrap_or(0), + // This is the value to bump (could be implicit 0) + Ordering::Equal => old_parts.get(i).copied().unwrap_or(0) + 1, + // Everything after the bumped value becomes 0 + Ordering::Greater => 0, + }) + .collect::>(); + full.release = new_release_vec; + } + BumpCommand::MakeStable => { + // Clear all sub-release items + full.pre = None; + full.post = None; + full.dev = None; + } + BumpCommand::BumpPrerelease { kind } => { + // Clear all sub-prerelease items + full.post = None; + full.dev = None; + + // Either bump the matching kind or set to 1 + if let Some(prerelease) = &mut full.pre { + if prerelease.kind == kind { + prerelease.number += 1; + return; + } + } + full.pre = Some(Prerelease { kind, number: 1 }); + } + BumpCommand::BumpPost => { + // Clear sub-post items + full.dev = None; + + // Either bump or set to 1 + if let Some(post) = &mut full.post { + *post += 1; + } else { + full.post = Some(1); + } + } + BumpCommand::BumpDev => { + // Either bump or set to 1 + if let Some(dev) = &mut full.dev { + *dev += 1; + } else { + full.dev = Some(1); + } + } + } + } + /// Set the min-release component and return the updated version. /// /// The "min" component is internal-only, and does not exist in PEP 440. @@ -879,6 +963,27 @@ impl FromStr for Version { } } +/// Various ways to "bump" a version +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum BumpCommand { + /// Bump the release component + BumpRelease { + /// The release component to bump (0 is major, 1 is minor, 2 is patch) + index: usize, + }, + /// Bump the prerelease component + BumpPrerelease { + /// prerelease component to bump + kind: PrereleaseKind, + }, + /// Bump to the associated stable release + MakeStable, + /// Bump the post component + BumpPost, + /// Bump the dev component + BumpDev, +} + /// A small representation of a version. /// /// This representation is used for a (very common) subset of versions: the @@ -4043,4 +4148,351 @@ mod tests { assert_eq!(size_of::(), size_of::() * 2); assert_eq!(size_of::(), size_of::() * 2); } + + /// Test major bumping + /// Explicitly using the string display because we want to preserve formatting where possible! + #[test] + fn bump_major() { + // one digit + let mut version = "0".parse::().unwrap(); + version.bump(BumpCommand::BumpRelease { index: 0 }); + assert_eq!(version.to_string().as_str(), "1"); + + // two digit + let mut version = "1.5".parse::().unwrap(); + version.bump(BumpCommand::BumpRelease { index: 0 }); + assert_eq!(version.to_string().as_str(), "2.0"); + + // three digit (zero major) + let mut version = "0.1.2".parse::().unwrap(); + version.bump(BumpCommand::BumpRelease { index: 0 }); + assert_eq!(version.to_string().as_str(), "1.0.0"); + + // three digit (non-zero major) + let mut version = "1.2.3".parse::().unwrap(); + version.bump(BumpCommand::BumpRelease { index: 0 }); + assert_eq!(version.to_string().as_str(), "2.0.0"); + + // four digit + let mut version = "1.2.3.4".parse::().unwrap(); + version.bump(BumpCommand::BumpRelease { index: 0 }); + assert_eq!(version.to_string().as_str(), "2.0.0.0"); + + // All the version junk + let mut version = "5!1.7.3.5b2.post345.dev456+local" + .parse::() + .unwrap(); + version.bump(BumpCommand::BumpRelease { index: 0 }); + assert_eq!(version.to_string().as_str(), "5!2.0.0.0+local"); + version.bump(BumpCommand::BumpRelease { index: 0 }); + assert_eq!(version.to_string().as_str(), "5!3.0.0.0+local"); + } + + /// Test minor bumping + /// Explicitly using the string display because we want to preserve formatting where possible! + #[test] + fn bump_minor() { + // one digit + let mut version = "0".parse::().unwrap(); + version.bump(BumpCommand::BumpRelease { index: 1 }); + assert_eq!(version.to_string().as_str(), "0.1"); + + // two digit + let mut version = "1.5".parse::().unwrap(); + version.bump(BumpCommand::BumpRelease { index: 1 }); + assert_eq!(version.to_string().as_str(), "1.6"); + + // three digit (non-zero major) + let mut version = "5.3.6".parse::().unwrap(); + version.bump(BumpCommand::BumpRelease { index: 1 }); + assert_eq!(version.to_string().as_str(), "5.4.0"); + + // four digit + let mut version = "1.2.3.4".parse::().unwrap(); + version.bump(BumpCommand::BumpRelease { index: 1 }); + assert_eq!(version.to_string().as_str(), "1.3.0.0"); + + // All the version junk + let mut version = "5!1.7.3.5b2.post345.dev456+local" + .parse::() + .unwrap(); + version.bump(BumpCommand::BumpRelease { index: 1 }); + assert_eq!(version.to_string().as_str(), "5!1.8.0.0+local"); + version.bump(BumpCommand::BumpRelease { index: 1 }); + assert_eq!(version.to_string().as_str(), "5!1.9.0.0+local"); + } + + /// Test patch bumping + /// Explicitly using the string display because we want to preserve formatting where possible! + #[test] + fn bump_patch() { + // one digit + let mut version = "0".parse::().unwrap(); + version.bump(BumpCommand::BumpRelease { index: 2 }); + assert_eq!(version.to_string().as_str(), "0.0.1"); + + // two digit + let mut version = "1.5".parse::().unwrap(); + version.bump(BumpCommand::BumpRelease { index: 2 }); + assert_eq!(version.to_string().as_str(), "1.5.1"); + + // three digit + let mut version = "5.3.6".parse::().unwrap(); + version.bump(BumpCommand::BumpRelease { index: 2 }); + assert_eq!(version.to_string().as_str(), "5.3.7"); + + // four digit + let mut version = "1.2.3.4".parse::().unwrap(); + version.bump(BumpCommand::BumpRelease { index: 2 }); + assert_eq!(version.to_string().as_str(), "1.2.4.0"); + + // All the version junk + let mut version = "5!1.7.3.5b2.post345.dev456+local" + .parse::() + .unwrap(); + version.bump(BumpCommand::BumpRelease { index: 2 }); + assert_eq!(version.to_string().as_str(), "5!1.7.4.0+local"); + version.bump(BumpCommand::BumpRelease { index: 2 }); + assert_eq!(version.to_string().as_str(), "5!1.7.5.0+local"); + } + + /// Test alpha bumping + /// Explicitly using the string display because we want to preserve formatting where possible! + #[test] + fn bump_alpha() { + // one digit + let mut version = "0".parse::().unwrap(); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Alpha, + }); + assert_eq!(version.to_string().as_str(), "0a1"); + + // two digit + let mut version = "1.5".parse::().unwrap(); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Alpha, + }); + assert_eq!(version.to_string().as_str(), "1.5a1"); + + // three digit + let mut version = "5.3.6".parse::().unwrap(); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Alpha, + }); + assert_eq!(version.to_string().as_str(), "5.3.6a1"); + + // four digit + let mut version = "1.2.3.4".parse::().unwrap(); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Alpha, + }); + assert_eq!(version.to_string().as_str(), "1.2.3.4a1"); + + // All the version junk + let mut version = "5!1.7.3.5b2.post345.dev456+local" + .parse::() + .unwrap(); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Alpha, + }); + assert_eq!(version.to_string().as_str(), "5!1.7.3.5a1+local"); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Alpha, + }); + assert_eq!(version.to_string().as_str(), "5!1.7.3.5a2+local"); + } + + /// Test beta bumping + /// Explicitly using the string display because we want to preserve formatting where possible! + #[test] + fn bump_beta() { + // one digit + let mut version = "0".parse::().unwrap(); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Beta, + }); + assert_eq!(version.to_string().as_str(), "0b1"); + + // two digit + let mut version = "1.5".parse::().unwrap(); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Beta, + }); + assert_eq!(version.to_string().as_str(), "1.5b1"); + + // three digit + let mut version = "5.3.6".parse::().unwrap(); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Beta, + }); + assert_eq!(version.to_string().as_str(), "5.3.6b1"); + + // four digit + let mut version = "1.2.3.4".parse::().unwrap(); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Beta, + }); + assert_eq!(version.to_string().as_str(), "1.2.3.4b1"); + + // All the version junk + let mut version = "5!1.7.3.5a2.post345.dev456+local" + .parse::() + .unwrap(); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Beta, + }); + assert_eq!(version.to_string().as_str(), "5!1.7.3.5b1+local"); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Beta, + }); + assert_eq!(version.to_string().as_str(), "5!1.7.3.5b2+local"); + } + + /// Test rc bumping + /// Explicitly using the string display because we want to preserve formatting where possible! + #[test] + fn bump_rc() { + // one digit + let mut version = "0".parse::().unwrap(); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Rc, + }); + assert_eq!(version.to_string().as_str(), "0rc1"); + + // two digit + let mut version = "1.5".parse::().unwrap(); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Rc, + }); + assert_eq!(version.to_string().as_str(), "1.5rc1"); + + // three digit + let mut version = "5.3.6".parse::().unwrap(); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Rc, + }); + assert_eq!(version.to_string().as_str(), "5.3.6rc1"); + + // four digit + let mut version = "1.2.3.4".parse::().unwrap(); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Rc, + }); + assert_eq!(version.to_string().as_str(), "1.2.3.4rc1"); + + // All the version junk + let mut version = "5!1.7.3.5b2.post345.dev456+local" + .parse::() + .unwrap(); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Rc, + }); + assert_eq!(version.to_string().as_str(), "5!1.7.3.5rc1+local"); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Rc, + }); + assert_eq!(version.to_string().as_str(), "5!1.7.3.5rc2+local"); + } + + /// Test post bumping + /// Explicitly using the string display because we want to preserve formatting where possible! + #[test] + fn bump_post() { + // one digit + let mut version = "0".parse::().unwrap(); + version.bump(BumpCommand::BumpPost); + assert_eq!(version.to_string().as_str(), "0.post1"); + + // two digit + let mut version = "1.5".parse::().unwrap(); + version.bump(BumpCommand::BumpPost); + assert_eq!(version.to_string().as_str(), "1.5.post1"); + + // three digit + let mut version = "5.3.6".parse::().unwrap(); + version.bump(BumpCommand::BumpPost); + assert_eq!(version.to_string().as_str(), "5.3.6.post1"); + + // four digit + let mut version = "1.2.3.4".parse::().unwrap(); + version.bump(BumpCommand::BumpPost); + assert_eq!(version.to_string().as_str(), "1.2.3.4.post1"); + + // All the version junk + let mut version = "5!1.7.3.5b2.dev123+local".parse::().unwrap(); + version.bump(BumpCommand::BumpPost); + assert_eq!(version.to_string().as_str(), "5!1.7.3.5b2.post1+local"); + version.bump(BumpCommand::BumpPost); + assert_eq!(version.to_string().as_str(), "5!1.7.3.5b2.post2+local"); + } + + /// Test dev bumping + /// Explicitly using the string display because we want to preserve formatting where possible! + #[test] + fn bump_dev() { + // one digit + let mut version = "0".parse::().unwrap(); + version.bump(BumpCommand::BumpDev); + assert_eq!(version.to_string().as_str(), "0.dev1"); + + // two digit + let mut version = "1.5".parse::().unwrap(); + version.bump(BumpCommand::BumpDev); + assert_eq!(version.to_string().as_str(), "1.5.dev1"); + + // three digit + let mut version = "5.3.6".parse::().unwrap(); + version.bump(BumpCommand::BumpDev); + assert_eq!(version.to_string().as_str(), "5.3.6.dev1"); + + // four digit + let mut version = "1.2.3.4".parse::().unwrap(); + version.bump(BumpCommand::BumpDev); + assert_eq!(version.to_string().as_str(), "1.2.3.4.dev1"); + + // All the version junk + let mut version = "5!1.7.3.5b2.post345+local".parse::().unwrap(); + version.bump(BumpCommand::BumpDev); + assert_eq!( + version.to_string().as_str(), + "5!1.7.3.5b2.post345.dev1+local" + ); + version.bump(BumpCommand::BumpDev); + assert_eq!( + version.to_string().as_str(), + "5!1.7.3.5b2.post345.dev2+local" + ); + } + + /// Test stable setting + /// Explicitly using the string display because we want to preserve formatting where possible! + #[test] + fn make_stable() { + // one digit + let mut version = "0".parse::().unwrap(); + version.bump(BumpCommand::MakeStable); + assert_eq!(version.to_string().as_str(), "0"); + + // two digit + let mut version = "1.5".parse::().unwrap(); + version.bump(BumpCommand::MakeStable); + assert_eq!(version.to_string().as_str(), "1.5"); + + // three digit + let mut version = "5.3.6".parse::().unwrap(); + version.bump(BumpCommand::MakeStable); + assert_eq!(version.to_string().as_str(), "5.3.6"); + + // four digit + let mut version = "1.2.3.4".parse::().unwrap(); + version.bump(BumpCommand::MakeStable); + assert_eq!(version.to_string().as_str(), "1.2.3.4"); + + // All the version junk + let mut version = "5!1.7.3.5b2.post345+local".parse::().unwrap(); + version.bump(BumpCommand::MakeStable); + assert_eq!(version.to_string().as_str(), "5!1.7.3.5+local"); + version.bump(BumpCommand::MakeStable); + assert_eq!(version.to_string().as_str(), "5!1.7.3.5+local"); + } } diff --git a/crates/uv/src/commands/project/version.rs b/crates/uv/src/commands/project/version.rs index bc79f8eb9..ec278d4b4 100644 --- a/crates/uv/src/commands/project/version.rs +++ b/crates/uv/src/commands/project/version.rs @@ -1,6 +1,6 @@ use std::fmt::Write; +use std::path::Path; use std::str::FromStr; -use std::{cmp::Ordering, path::Path}; use anyhow::{Context, Result, anyhow}; use owo_colors::OwoColorize; @@ -15,7 +15,7 @@ use uv_configuration::{ }; use uv_fs::Simplified; use uv_normalize::DefaultExtras; -use uv_pep440::Version; +use uv_pep440::{BumpCommand, PrereleaseKind, Version}; use uv_pep508::PackageName; use uv_python::{PythonDownloads, PythonPreference, PythonRequest}; use uv_settings::PythonInstallMirrors; @@ -55,7 +55,7 @@ pub(crate) fn self_version( #[allow(clippy::fn_params_excessive_bools)] pub(crate) async fn project_version( value: Option, - bump: Option, + mut bump: Vec, short: bool, output_format: VersionFormat, strict: bool, @@ -105,7 +105,7 @@ pub(crate) async fn project_version( }; // Short-circuit early for a frozen read - let is_read_only = value.is_none() && bump.is_none(); + let is_read_only = value.is_none() && bump.is_empty(); if frozen && is_read_only { return Box::pin(print_frozen_version( project, @@ -158,7 +158,8 @@ pub(crate) async fn project_version( match Version::from_str(&value) { Ok(version) => Some(version), Err(err) => match &*value { - "major" | "minor" | "patch" => { + "major" | "minor" | "patch" | "alpha" | "beta" | "rc" | "dev" | "post" + | "stable" => { return Err(anyhow!( "Invalid version `{value}`, did you mean to pass `--bump {value}`?" )); @@ -168,8 +169,135 @@ pub(crate) async fn project_version( } }, } - } else if let Some(bump) = bump { - Some(bumped_version(&old_version, bump, printer)?) + } else if !bump.is_empty() { + // While we can rationalize many of these combinations of operations together, + // we want to conservatively refuse to support any of them until users demand it. + // + // The most complex thing we *do* allow is `--bump major --bump beta --bump dev` + // because that makes perfect sense and is reasonable to do. + let release_components: Vec<_> = bump + .iter() + .filter(|bump| { + matches!( + bump, + VersionBump::Major | VersionBump::Minor | VersionBump::Patch + ) + }) + .collect(); + let prerelease_components: Vec<_> = bump + .iter() + .filter(|bump| { + matches!( + bump, + VersionBump::Alpha | VersionBump::Beta | VersionBump::Rc | VersionBump::Dev + ) + }) + .collect(); + let post_count = bump + .iter() + .filter(|bump| *bump == &VersionBump::Post) + .count(); + let stable_count = bump + .iter() + .filter(|bump| *bump == &VersionBump::Stable) + .count(); + + // Very little reason to do "bump to stable" and then do other things, + // even if we can make sense of it. + if stable_count > 0 && bump.len() > 1 { + let components = bump + .iter() + .map(ToString::to_string) + .collect::>() + .join(", "); + return Err(anyhow!( + "`--bump stable` cannot be used with another `--bump` value, got: {components}" + )); + } + + // Very little reason to "bump to post" and then do other things, + // how is it a post-release otherwise? + if post_count > 0 && bump.len() > 1 { + let components = bump + .iter() + .map(ToString::to_string) + .collect::>() + .join(", "); + return Err(anyhow!( + "`--bump post` cannot be used with another `--bump` value, got: {components}" + )); + } + + // `--bump major --bump minor` makes perfect sense (1.2.3 => 2.1.0) + // ...but it's weird and probably a mistake? + // `--bump major --bump major` perfect sense (1.2.3 => 3.0.0) + // ...but it's weird and probably a mistake? + if release_components.len() > 1 { + let components = release_components + .iter() + .map(ToString::to_string) + .collect::>() + .join(", "); + return Err(anyhow!( + "Only one release version component can be provided to `--bump`, got: {components}" + )); + } + + // `--bump alpha --bump beta` is basically completely incoherent + // `--bump beta --bump beta` makes perfect sense (1.2.3b4 => 1.2.3b6) + // ...but it's weird and probably a mistake? + // `--bump beta --bump dev` makes perfect sense (1.2.3 => 1.2.3b1.dev1) + // ...but we want to discourage mixing `dev` with pre-releases + if prerelease_components.len() > 1 { + let components = prerelease_components + .iter() + .map(ToString::to_string) + .collect::>() + .join(", "); + return Err(anyhow!( + "Only one pre-release version component can be provided to `--bump`, got: {components}" + )); + } + + // Sort the given commands so the user doesn't have to care about + // the ordering of `--bump minor --bump beta` (only one ordering is ever useful) + bump.sort(); + + // Apply all the bumps + let mut new_version = old_version.clone(); + for bump in &bump { + let command = match *bump { + VersionBump::Major => BumpCommand::BumpRelease { index: 0 }, + VersionBump::Minor => BumpCommand::BumpRelease { index: 1 }, + VersionBump::Patch => BumpCommand::BumpRelease { index: 2 }, + VersionBump::Alpha => BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Alpha, + }, + VersionBump::Beta => BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Beta, + }, + VersionBump::Rc => BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Rc, + }, + VersionBump::Post => BumpCommand::BumpPost, + VersionBump::Dev => BumpCommand::BumpDev, + VersionBump::Stable => BumpCommand::MakeStable, + }; + new_version.bump(command); + } + + if new_version <= old_version { + if old_version.is_stable() && new_version.is_pre() { + return Err(anyhow!( + "{old_version} => {new_version} didn't increase the version; when bumping to a pre-release version you also need to increase a release version component, e.g., with `--bump `" + )); + } + return Err(anyhow!( + "{old_version} => {new_version} didn't increase the version; provide the exact version to force an update" + )); + } + + Some(new_version) } else { None }; @@ -569,35 +697,3 @@ fn print_version( } Ok(()) } - -fn bumped_version(from: &Version, bump: VersionBump, printer: Printer) -> Result { - // All prereleasey details "carry to 0" with every currently supported mode of `--bump` - // We could go out of our way to preserve epoch information but no one uses those... - if from.any_prerelease() || from.is_post() || from.is_local() || from.epoch() > 0 { - writeln!( - printer.stderr(), - "warning: prerelease information will be cleared as part of the version bump" - )?; - } - - let index = match bump { - VersionBump::Major => 0, - VersionBump::Minor => 1, - VersionBump::Patch => 2, - }; - - // Use `max` here to try to do 0.2 => 0.3 instead of 0.2 => 0.3.0 - let old_parts = from.release(); - let len = old_parts.len().max(index + 1); - let new_release_vec = (0..len) - .map(|i| match i.cmp(&index) { - // Everything before the bumped value is preserved (or is an implicit 0) - Ordering::Less => old_parts.get(i).copied().unwrap_or(0), - // This is the value to bump (could be implicit 0) - Ordering::Equal => old_parts.get(i).copied().unwrap_or(0) + 1, - // Everything after the bumped value becomes 0 - Ordering::Greater => 0, - }) - .collect::>(); - Ok(Version::new(new_release_vec)) -} diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index e22eb801f..28a20f373 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -2041,7 +2041,7 @@ async fn run_project( let strict = project_was_explicit || globals.preview.is_enabled() || args.dry_run - || args.bump.is_some() + || !args.bump.is_empty() || args.value.is_some() || args.package.is_some(); Box::pin(commands::project_version( diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 673f76ebd..ed86608ed 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -1564,7 +1564,7 @@ impl RemoveSettings { #[derive(Debug, Clone)] pub(crate) struct VersionSettings { pub(crate) value: Option, - pub(crate) bump: Option, + pub(crate) bump: Vec, pub(crate) short: bool, pub(crate) output_format: VersionFormat, pub(crate) dry_run: bool, diff --git a/crates/uv/tests/it/version.rs b/crates/uv/tests/it/version.rs index 97d30f4f4..ab09833f9 100644 --- a/crates/uv/tests/it/version.rs +++ b/crates/uv/tests/it/version.rs @@ -512,7 +512,6 @@ requires-python = ">=3.12" myproject 1.10.31.dev10 => 2.0.0 ----- stderr ----- - warning: prerelease information will be cleared as part of the version bump Resolved 1 package in [TIME] Audited in [TIME] "); @@ -550,10 +549,9 @@ requires-python = ">=3.12" success: true exit_code: 0 ----- stdout ----- - myproject 1!2a3.post4.dev5+deadbeef6 => 3 + myproject 1!2a3.post4.dev5+deadbeef6 => 1!3+deadbeef6 ----- stderr ----- - warning: prerelease information will be cleared as part of the version bump Resolved 1 package in [TIME] Audited in [TIME] "); @@ -564,7 +562,295 @@ requires-python = ">=3.12" @r#" [project] name = "myproject" - version = "3" + version = "1!3+deadbeef6" + requires-python = ">=3.12" + "# + ); + Ok(()) +} + +// Pass a ton of bump flags to a complex version +// The flags are in a messy order and some are duplicated, +// Under extremely permissive semantics this could be allowed, but right +// now it fails for a dozen reasons! +#[test] +fn many_bump_complex() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "9!2.3.4a5.post6.dev7+deadbeef6" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("major") + .arg("--bump").arg("patch") + .arg("--bump").arg("alpha") + .arg("--bump").arg("minor") + .arg("--bump").arg("dev") + .arg("--bump").arg("minor") + .arg("--bump").arg("post") + .arg("--bump").arg("post"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: `--bump post` cannot be used with another `--bump` value, got: major, patch, alpha, minor, dev, minor, post, post + "); + + let pyproject = fs_err::read_to_string(&pyproject_toml)?; + assert_snapshot!( + pyproject, + @r#" + [project] + name = "myproject" + version = "9!2.3.4a5.post6.dev7+deadbeef6" + requires-python = ">=3.12" + "# + ); + Ok(()) +} + +// --bump stable +#[test] +fn bump_stable() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "9!2.3.4a5.post6.dev7+deadbeef6" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("stable"), @r" + success: true + exit_code: 0 + ----- stdout ----- + myproject 9!2.3.4a5.post6.dev7+deadbeef6 => 9!2.3.4+deadbeef6 + + ----- stderr ----- + Resolved 1 package in [TIME] + Audited in [TIME] + "); + + let pyproject = fs_err::read_to_string(&pyproject_toml)?; + assert_snapshot!( + pyproject, + @r#" + [project] + name = "myproject" + version = "9!2.3.4+deadbeef6" + requires-python = ">=3.12" + "# + ); + Ok(()) +} + +// --bump alpha +#[test] +fn bump_alpha() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "9!2.3.4a5.post6.dev7+deadbeef6" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("alpha"), @r" + success: true + exit_code: 0 + ----- stdout ----- + myproject 9!2.3.4a5.post6.dev7+deadbeef6 => 9!2.3.4a6+deadbeef6 + + ----- stderr ----- + Resolved 1 package in [TIME] + Audited in [TIME] + "); + + let pyproject = fs_err::read_to_string(&pyproject_toml)?; + assert_snapshot!( + pyproject, + @r#" + [project] + name = "myproject" + version = "9!2.3.4a6+deadbeef6" + requires-python = ">=3.12" + "# + ); + Ok(()) +} + +// --bump beta +#[test] +fn bump_beta() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "9!2.3.4a5.post6.dev7+deadbeef6" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("beta"), @r" + success: true + exit_code: 0 + ----- stdout ----- + myproject 9!2.3.4a5.post6.dev7+deadbeef6 => 9!2.3.4b1+deadbeef6 + + ----- stderr ----- + Resolved 1 package in [TIME] + Audited in [TIME] + "); + + let pyproject = fs_err::read_to_string(&pyproject_toml)?; + assert_snapshot!( + pyproject, + @r#" + [project] + name = "myproject" + version = "9!2.3.4b1+deadbeef6" + requires-python = ">=3.12" + "# + ); + Ok(()) +} + +// --bump rc +#[test] +fn bump_rc() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "9!2.3.4a5.post6.dev7+deadbeef6" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("rc"), @r" + success: true + exit_code: 0 + ----- stdout ----- + myproject 9!2.3.4a5.post6.dev7+deadbeef6 => 9!2.3.4rc1+deadbeef6 + + ----- stderr ----- + Resolved 1 package in [TIME] + Audited in [TIME] + "); + + let pyproject = fs_err::read_to_string(&pyproject_toml)?; + assert_snapshot!( + pyproject, + @r#" + [project] + name = "myproject" + version = "9!2.3.4rc1+deadbeef6" + requires-python = ">=3.12" + "# + ); + Ok(()) +} + +// --bump post +#[test] +fn bump_post() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "9!2.3.4a5.post6.dev7+deadbeef6" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("post"), @r" + success: true + exit_code: 0 + ----- stdout ----- + myproject 9!2.3.4a5.post6.dev7+deadbeef6 => 9!2.3.4a5.post7+deadbeef6 + + ----- stderr ----- + Resolved 1 package in [TIME] + Audited in [TIME] + "); + + let pyproject = fs_err::read_to_string(&pyproject_toml)?; + assert_snapshot!( + pyproject, + @r#" + [project] + name = "myproject" + version = "9!2.3.4a5.post7+deadbeef6" + requires-python = ">=3.12" + "# + ); + Ok(()) +} + +// --bump dev +#[test] +fn bump_dev() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "9!2.3.4a5.post6.dev7+deadbeef6" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("dev"), @r" + success: true + exit_code: 0 + ----- stdout ----- + myproject 9!2.3.4a5.post6.dev7+deadbeef6 => 9!2.3.4a5.post6.dev8+deadbeef6 + + ----- stderr ----- + Resolved 1 package in [TIME] + Audited in [TIME] + "); + + let pyproject = fs_err::read_to_string(&pyproject_toml)?; + assert_snapshot!( + pyproject, + @r#" + [project] + name = "myproject" + version = "9!2.3.4a5.post6.dev8+deadbeef6" requires-python = ">=3.12" "# ); @@ -594,7 +880,6 @@ requires-python = ">=3.12" myproject 1.10.31.post10 => 2.0.0 ----- stderr ----- - warning: prerelease information will be cleared as part of the version bump Resolved 1 package in [TIME] Audited in [TIME] "); @@ -612,6 +897,317 @@ requires-python = ">=3.12" Ok(()) } +// --bump stable but it decreases the version +#[test] +fn bump_decrease_stable() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "2.3.4.post6" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("stable"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: 2.3.4.post6 => 2.3.4 didn't increase the version; provide the exact version to force an update + "); + Ok(()) +} + +// --bump alpha but it decreases the version by reverting beta +#[test] +fn bump_decrease_alpha_beta() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "2.3.4b5" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("alpha"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: 2.3.4b5 => 2.3.4a1 didn't increase the version; provide the exact version to force an update + "); + Ok(()) +} + +// --bump alpha but it decreases the version from a stable +#[test] +fn bump_decrease_alpha_stable() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "2.3.4" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("alpha"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: 2.3.4 => 2.3.4a1 didn't increase the version; when bumping to a pre-release version you also need to increase a release version component, e.g., with `--bump ` + "); + Ok(()) +} + +// --bump major twice +#[test] +fn bump_double_major() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "2.3.4" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("major") + .arg("--bump").arg("major"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Only one release version component can be provided to `--bump`, got: major, major + "); + Ok(()) +} + +// --bump alpha twice +#[test] +fn bump_double_alpha() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "2.3.4" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("alpha") + .arg("--bump").arg("alpha"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Only one pre-release version component can be provided to `--bump`, got: alpha, alpha + "); + Ok(()) +} + +// --bump stable --bump major +#[test] +fn bump_stable_major() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "2.3.4" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("stable") + .arg("--bump").arg("major"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: `--bump stable` cannot be used with another `--bump` value, got: stable, major + "); + Ok(()) +} + +// --bump major --bump alpha +#[test] +fn bump_alpha_major() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "2.3.4" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("major") + .arg("--bump").arg("alpha"), @r" + success: true + exit_code: 0 + ----- stdout ----- + myproject 2.3.4 => 3.0.0a1 + + ----- stderr ----- + Resolved 1 package in [TIME] + Audited in [TIME] + "); + Ok(()) +} + +// --bump major --bump minor +#[test] +fn bump_minor_major() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "2.3.4" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("major") + .arg("--bump").arg("alpha"), @r" + success: true + exit_code: 0 + ----- stdout ----- + myproject 2.3.4 => 3.0.0a1 + + ----- stderr ----- + Resolved 1 package in [TIME] + Audited in [TIME] + "); + Ok(()) +} + +// --bump alpha --bump dev +#[test] +fn bump_alpha_dev() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "2.3.4" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("alpha") + .arg("--bump").arg("dev"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Only one pre-release version component can be provided to `--bump`, got: alpha, dev + "); + Ok(()) +} + +// --bump major --bump dev +#[test] +fn bump_dev_major() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "2.3.4" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("major") + .arg("--bump").arg("dev"), @r" + success: true + exit_code: 0 + ----- stdout ----- + myproject 2.3.4 => 3.0.0.dev1 + + ----- stderr ----- + Resolved 1 package in [TIME] + Audited in [TIME] + "); + Ok(()) +} + +// --bump major --bump post +#[test] +fn bump_post_major() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "2.3.4" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("major") + .arg("--bump").arg("post"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: `--bump post` cannot be used with another `--bump` value, got: major, post + "); + Ok(()) +} + // Set version --dry-run #[test] fn version_set_dry() -> Result<()> { diff --git a/docs/guides/package.md b/docs/guides/package.md index ce5bae7f9..c6017986d 100644 --- a/docs/guides/package.md +++ b/docs/guides/package.md @@ -55,6 +55,70 @@ Alternatively, `uv build ` will build the package in the specified director running `uv build --no-sources` to ensure that the package builds correctly when `tool.uv.sources` is disabled, as is the case when using other build tools, like [`pypa/build`](https://github.com/pypa/build). +## Updating your version + +The `uv version` command provides conveniences for updating the version of your package before you +publish it. +[See the project docs for reading your package's version](./projects.md#managing-version). + +To update to an exact version, provide it as a positional argument: + +```console +$ uv version 1.0.0 +hello-world 0.7.0 => 1.0.0 +``` + +To preview the change without updating the `pyproject.toml`, use the `--dry-run` flag: + +```console +$ uv version 2.0.0 --dry-run +hello-world 1.0.0 => 2.0.0 +$ uv version +hello-world 1.0.0 +``` + +To increase the version of your package semantics, use the `--bump` option: + +```console +$ uv version --bump minor +hello-world 1.2.3 => 1.3.0 +``` + +The `--bump` option supports the following common version components: `major`, `minor`, `patch`, +`stable`, `alpha`, `beta`, `rc`, `post`, and `dev`. When provided more than once, the components +will be applied in order, from largest (`major`) to smallest (`dev`). + +To move from a stable to pre-release version, bump one of the major, minor, or patch components in +addition to the pre-release component: + +```console +$ uv version --bump patch --bump beta +hello-world 1.3.0 => 1.3.1b1 +$ uv version --bump major --bump alpha +hello-world 1.3.0 => 2.0.0a1 +``` + +When moving from a pre-release to a new pre-release version, just bump the relevant pre-release +component: + +```console +uv version --bump beta +hello-world 1.3.0b1 => 1.3.1b2 +``` + +When moving from a pre-release to a stable version, the `stable` option can be used to clear the +pre-release component: + +```console +uv version --bump stable +hello-world 1.3.1b2 => 1.3.1 +``` + +!!! info + + By default, when `uv version` modifies the project it will perform a lock and sync. To + prevent locking and syncing, use `--frozen`, or, to just prevent syncing, use `--no-sync`. + ## Publishing your package Publish your package with `uv publish`: diff --git a/docs/guides/projects.md b/docs/guides/projects.md index 0fd97eb0d..fc6f34f2d 100644 --- a/docs/guides/projects.md +++ b/docs/guides/projects.md @@ -160,6 +160,38 @@ version, while keeping the rest of the lockfile intact. See the documentation on [managing dependencies](../concepts/projects/dependencies.md) for more details. +## Managing version + +The `uv version` command can be used to read your package's version. + +To get the version of your package, run `uv version`: + +```console +$ uv version +hello-world 0.7.0 +``` + +To get the version without the package name, use the `--short` option: + +```console +$ uv version --short +0.7.0 +``` + +To get version information in a JSON format, use the `--output-format json` option: + +```console +$ uv version --output-format json +{ + "package_name": "hello-world", + "version": "0.7.0", + "commit_info": null +} +``` + +See the [publishing guide](./package.md#updating-your-version) for details on updating your package +version. + ## Running commands `uv run` can be used to run arbitrary scripts or commands in your project environment. diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 17fe6cfce..bd828acda 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -787,11 +787,18 @@ uv version [OPTIONS] [VALUE]

    Expects to receive either a hostname (e.g., localhost), a host-port pair (e.g., localhost:8080), or a URL (e.g., https://localhost).

    WARNING: Hosts included in this list will not be verified against the system's certificate store. Only use --allow-insecure-host in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.

    May also be set with the UV_INSECURE_HOST environment variable.

    --bump bump

    Update the project version using the given semantics

    +

    This flag can be passed multiple times.

    Possible values:

      -
    • major: Increase the major version (1.2.3 => 2.0.0)
    • -
    • minor: Increase the minor version (1.2.3 => 1.3.0)
    • -
    • patch: Increase the patch version (1.2.3 => 1.2.4)
    • +
    • major: Increase the major version (e.g., 1.2.3 => 2.0.0)
    • +
    • minor: Increase the minor version (e.g., 1.2.3 => 1.3.0)
    • +
    • patch: Increase the patch version (e.g., 1.2.3 => 1.2.4)
    • +
    • stable: Move from a pre-release to stable version (e.g., 1.2.3b4.post5.dev6 => 1.2.3)
    • +
    • alpha: Increase the alpha version (e.g., 1.2.3a4 => 1.2.3a5)
    • +
    • beta: Increase the beta version (e.g., 1.2.3b4 => 1.2.3b5)
    • +
    • rc: Increase the rc version (e.g., 1.2.3rc4 => 1.2.3rc5)
    • +
    • post: Increase the post version (e.g., 1.2.3.post5 => 1.2.3.post6)
    • +
    • dev: Increase the dev version (e.g., 1.2.3a4.dev6 => 1.2.3.dev7)
    --cache-dir cache-dir

    Path to the cache directory.

    Defaults to $XDG_CACHE_HOME/uv or $HOME/.cache/uv on macOS and Linux, and %LOCALAPPDATA%\uv\cache on Windows.

    To view the location of the cache directory, run uv cache dir.

    From 1dff18897afd447a968d40f866f40acd077cac77 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 10 Jul 2025 08:53:27 -0500 Subject: [PATCH 179/349] Only run macOS tests on `main` without opt-in (#14541) These runners are expensive and have limited concurrency, let's just run them on `main`. --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bc77abd93..4a8e8fb12 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -232,7 +232,8 @@ jobs: cargo-test-macos: timeout-minutes: 15 needs: determine_changes - if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }} + # Only run macOS tests on main without opt-in + if: ${{ contains(github.event.pull_request.labels.*.name, 'test:macos' || github.ref == 'refs/heads/main') }} runs-on: macos-latest-xlarge # github-macos-14-aarch64-6 name: "cargo test | macos" steps: From 02345a5a7d7d86c96820e2c1a3d9dbf91d1ec538 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 10 Jul 2025 12:06:24 -0500 Subject: [PATCH 180/349] Add hint when Python downloads are disabled (#14522) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to https://github.com/astral-sh/uv/pull/14509 to provide the _reason_ downloads are disabled and surface it as a hint rather than a debug log. e.g., ``` ❯ cargo run -q -- run --no-managed-python -p 3.13.4 python error: No interpreter found for Python 3.13.4 in virtual environments or search path hint: A managed Python download is available for Python 3.13.4, but the Python preference is set to 'only system' ``` --- crates/uv-python/src/installation.rs | 109 +++++++++++++++++++++------ crates/uv-python/src/lib.rs | 20 ++++- crates/uv/src/commands/python/pin.rs | 3 +- crates/uv/tests/it/common/mod.rs | 6 ++ crates/uv/tests/it/lock.rs | 6 +- crates/uv/tests/it/python_find.rs | 2 + crates/uv/tests/it/python_install.rs | 6 +- crates/uv/tests/it/python_pin.rs | 46 +++++------ crates/uv/tests/it/tool_run.rs | 48 ++++++++++++ 9 files changed, 187 insertions(+), 59 deletions(-) diff --git a/crates/uv-python/src/installation.rs b/crates/uv-python/src/installation.rs index 35cbf9ce5..a5dbb55f2 100644 --- a/crates/uv-python/src/installation.rs +++ b/crates/uv-python/src/installation.rs @@ -107,18 +107,9 @@ impl PythonInstallation { Err(err) => err, }; - let downloads_enabled = preference.allows_managed() - && python_downloads.is_automatic() - && client_builder.connectivity.is_online(); - - if !downloads_enabled { - debug!("Python downloads are disabled. Skipping check for available downloads..."); - return Err(err); - } - match err { // If Python is missing, we should attempt a download - Error::MissingPython(_) => {} + Error::MissingPython(..) => {} // If we raised a non-critical error, we should attempt a download Error::Discovery(ref err) if !err.is_critical() => {} // Otherwise, this is fatal @@ -126,40 +117,109 @@ impl PythonInstallation { } // If we can't convert the request to a download, throw the original error - let Some(request) = PythonDownloadRequest::from_request(request) else { + let Some(download_request) = PythonDownloadRequest::from_request(request) else { return Err(err); }; - debug!("Requested Python not found, checking for available download..."); - match Self::fetch( - request.fill()?, + let downloads_enabled = preference.allows_managed() + && python_downloads.is_automatic() + && client_builder.connectivity.is_online(); + + let download = download_request.clone().fill().map(|request| { + ManagedPythonDownload::from_request(&request, python_downloads_json_url) + }); + + // Regardless of whether downloads are enabled, we want to determine if the download is + // available to power error messages. However, if downloads aren't enabled, we don't want to + // report any errors related to them. + let download = match download { + Ok(Ok(download)) => Some(download), + // If the download cannot be found, return the _original_ discovery error + Ok(Err(downloads::Error::NoDownloadFound(_))) => { + if downloads_enabled { + debug!("No downloads are available for {request}"); + return Err(err); + } + None + } + Err(err) | Ok(Err(err)) => { + if downloads_enabled { + // We failed to determine the platform information + return Err(err.into()); + } + None + } + }; + + let Some(download) = download else { + // N.B. We should only be in this case when downloads are disabled; when downloads are + // enabled, we should fail eagerly when something goes wrong with the download. + debug_assert!(!downloads_enabled); + return Err(err); + }; + + // If the download is available, but not usable, we attach a hint to the original error. + if !downloads_enabled { + let for_request = match request { + PythonRequest::Default | PythonRequest::Any => String::new(), + _ => format!(" for {request}"), + }; + + match python_downloads { + PythonDownloads::Automatic => {} + PythonDownloads::Manual => { + return Err(err.with_missing_python_hint(format!( + "A managed Python download is available{for_request}, but Python downloads are set to 'manual', use `uv python install {}` to install the required version", + request.to_canonical_string(), + ))); + } + PythonDownloads::Never => { + return Err(err.with_missing_python_hint(format!( + "A managed Python download is available{for_request}, but Python downloads are set to 'never'" + ))); + } + } + + match preference { + PythonPreference::OnlySystem => { + return Err(err.with_missing_python_hint(format!( + "A managed Python download is available{for_request}, but the Python preference is set to 'only system'" + ))); + } + PythonPreference::Managed + | PythonPreference::OnlyManaged + | PythonPreference::System => {} + } + + if !client_builder.connectivity.is_online() { + return Err(err.with_missing_python_hint(format!( + "A managed Python download is available{for_request}, but uv is set to offline mode" + ))); + } + + return Err(err); + } + + Self::fetch( + download, client_builder, cache, reporter, python_install_mirror, pypy_install_mirror, - python_downloads_json_url, preview, ) .await - { - Ok(installation) => Ok(installation), - // Throw the original error if we couldn't find a download - Err(Error::Download(downloads::Error::NoDownloadFound(_))) => Err(err), - // But if the download failed, throw that error - Err(err) => Err(err), - } } /// Download and install the requested installation. pub async fn fetch( - request: PythonDownloadRequest, + download: &'static ManagedPythonDownload, client_builder: &BaseClientBuilder<'_>, cache: &Cache, reporter: Option<&dyn Reporter>, python_install_mirror: Option<&str>, pypy_install_mirror: Option<&str>, - python_downloads_json_url: Option<&str>, preview: PreviewMode, ) -> Result { let installations = ManagedPythonInstallations::from_settings(None)?.init()?; @@ -167,7 +227,6 @@ impl PythonInstallation { let scratch_dir = installations.scratch(); let _lock = installations.lock().await?; - let download = ManagedPythonDownload::from_request(&request, python_downloads_json_url)?; let client = client_builder.build(); info!("Fetching requested Python..."); diff --git a/crates/uv-python/src/lib.rs b/crates/uv-python/src/lib.rs index d408bc199..ea6f0db61 100644 --- a/crates/uv-python/src/lib.rs +++ b/crates/uv-python/src/lib.rs @@ -1,4 +1,5 @@ //! Find requested Python interpreters and query interpreters for information. +use owo_colors::OwoColorize; use thiserror::Error; #[cfg(test)] @@ -93,8 +94,8 @@ pub enum Error { #[error(transparent)] KeyError(#[from] installation::PythonInstallationKeyError), - #[error(transparent)] - MissingPython(#[from] PythonNotFound), + #[error("{}{}", .0, if let Some(hint) = .1 { format!("\n\n{}{} {hint}", "hint".bold().cyan(), ":".bold()) } else { String::new() })] + MissingPython(PythonNotFound, Option), #[error(transparent)] MissingEnvironment(#[from] environment::EnvironmentNotFound), @@ -103,6 +104,21 @@ pub enum Error { InvalidEnvironment(#[from] environment::InvalidEnvironment), } +impl Error { + pub(crate) fn with_missing_python_hint(self, hint: String) -> Self { + match self { + Error::MissingPython(err, _) => Error::MissingPython(err, Some(hint)), + _ => self, + } + } +} + +impl From for Error { + fn from(err: PythonNotFound) -> Self { + Error::MissingPython(err, None) + } +} + // The mock interpreters are not valid on Windows so we don't have unit test coverage there // TODO(zanieb): We should write a mock interpreter script that works on Windows #[cfg(all(test, unix))] diff --git a/crates/uv/src/commands/python/pin.rs b/crates/uv/src/commands/python/pin.rs index f0dc06cff..395981751 100644 --- a/crates/uv/src/commands/python/pin.rs +++ b/crates/uv/src/commands/python/pin.rs @@ -129,7 +129,8 @@ pub(crate) async fn pin( { Ok(python) => Some(python), // If no matching Python version is found, don't fail unless `resolved` was requested - Err(uv_python::Error::MissingPython(err)) if !resolved => { + Err(uv_python::Error::MissingPython(err, ..)) if !resolved => { + // N.B. We omit the hint and just show the inner error message warn_user_once!("{err}"); None } diff --git a/crates/uv/tests/it/common/mod.rs b/crates/uv/tests/it/common/mod.rs index 7b13c49b5..90f436f6f 100644 --- a/crates/uv/tests/it/common/mod.rs +++ b/crates/uv/tests/it/common/mod.rs @@ -195,6 +195,12 @@ impl TestContext { "managed installations, search path, or registry".to_string(), "[PYTHON SOURCES]".to_string(), )); + self.filters.push(( + "registry or search path".to_string(), + "[PYTHON SOURCES]".to_string(), + )); + self.filters + .push(("search path".to_string(), "[PYTHON SOURCES]".to_string())); self } diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index cb9c72c03..d5757b6ef 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -4318,14 +4318,16 @@ fn lock_requires_python() -> Result<()> { // Install from the lockfile. // Note we need to disable Python fetches or we'll just download 3.12 - uv_snapshot!(context_unsupported.filters(), context_unsupported.sync().arg("--frozen").arg("--no-python-downloads"), @r###" + uv_snapshot!(context_unsupported.filters(), context_unsupported.sync().arg("--frozen").arg("--no-python-downloads"), @r" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- error: No interpreter found for Python >=3.12 in [PYTHON SOURCES] - "###); + + hint: A managed Python download is available for Python >=3.12, but Python downloads are set to 'never' + "); Ok(()) } diff --git a/crates/uv/tests/it/python_find.rs b/crates/uv/tests/it/python_find.rs index f438e9b4d..b8b42d61b 100644 --- a/crates/uv/tests/it/python_find.rs +++ b/crates/uv/tests/it/python_find.rs @@ -873,6 +873,8 @@ fn python_find_script_python_not_found() { ----- stderr ----- No interpreter found in [PYTHON SOURCES] + + hint: A managed Python download is available, but Python downloads are set to 'never' "); } diff --git a/crates/uv/tests/it/python_install.rs b/crates/uv/tests/it/python_install.rs index 0fc89df21..bd723e5d1 100644 --- a/crates/uv/tests/it/python_install.rs +++ b/crates/uv/tests/it/python_install.rs @@ -196,14 +196,16 @@ fn python_install_automatic() { uv_snapshot!(context.filters(), context.run() .env_remove("VIRTUAL_ENV") .arg("--no-python-downloads") - .arg("python").arg("-c").arg("import sys; print(sys.version_info[:2])"), @r###" + .arg("python").arg("-c").arg("import sys; print(sys.version_info[:2])"), @r" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- error: No interpreter found in [PYTHON SOURCES] - "###); + + hint: A managed Python download is available, but Python downloads are set to 'never' + "); // Otherwise, we should fetch the latest Python version uv_snapshot!(context.filters(), context.run() diff --git a/crates/uv/tests/it/python_pin.rs b/crates/uv/tests/it/python_pin.rs index 4cbc98ab0..cf8849f42 100644 --- a/crates/uv/tests/it/python_pin.rs +++ b/crates/uv/tests/it/python_pin.rs @@ -164,7 +164,7 @@ fn python_pin() { // (skip on Windows because the snapshot is different and the behavior is not platform dependent) #[cfg(unix)] { - uv_snapshot!(context.filters(), context.python_pin().arg("pypy"), @r###" + uv_snapshot!(context.filters(), context.python_pin().arg("pypy"), @r" success: true exit_code: 0 ----- stdout ----- @@ -172,7 +172,7 @@ fn python_pin() { ----- stderr ----- warning: No interpreter found for PyPy in managed installations or search path - "###); + "); let python_version = context.read(PYTHON_VERSION_FILENAME); assert_snapshot!(python_version, @r###" @@ -361,7 +361,7 @@ fn python_pin_global_creates_parent_dirs() { fn python_pin_no_python() { let context: TestContext = TestContext::new_with_versions(&[]); - uv_snapshot!(context.filters(), context.python_pin().arg("3.12"), @r###" + uv_snapshot!(context.filters(), context.python_pin().arg("3.12"), @r" success: true exit_code: 0 ----- stdout ----- @@ -369,7 +369,7 @@ fn python_pin_no_python() { ----- stderr ----- warning: No interpreter found for Python 3.12 in managed installations or search path - "###); + "); } #[test] @@ -448,7 +448,7 @@ fn python_pin_compatible_with_requires_python() -> Result<()> { "###); // Request a version that is compatible and uses a Python variant - uv_snapshot!(context.filters(), context.python_pin().arg("3.13t"), @r###" + uv_snapshot!(context.filters(), context.python_pin().arg("3.13t"), @r" success: true exit_code: 0 ----- stdout ----- @@ -456,7 +456,7 @@ fn python_pin_compatible_with_requires_python() -> Result<()> { ----- stderr ----- warning: No interpreter found for Python 3.13t in [PYTHON SOURCES] - "###); + "); // Request a implementation version that is compatible uv_snapshot!(context.filters(), context.python_pin().arg("cpython@3.11"), @r###" @@ -587,27 +587,17 @@ fn warning_pinned_python_version_not_installed() -> Result<()> { /// We do need a Python interpreter for `--resolved` pins #[test] fn python_pin_resolve_no_python() { - let context: TestContext = TestContext::new_with_versions(&[]); + let context: TestContext = TestContext::new_with_versions(&[]).with_filtered_python_sources(); + uv_snapshot!(context.filters(), context.python_pin().arg("--resolved").arg("3.12"), @r" + success: false + exit_code: 2 + ----- stdout ----- - if cfg!(windows) { - uv_snapshot!(context.filters(), context.python_pin().arg("--resolved").arg("3.12"), @r###" - success: false - exit_code: 2 - ----- stdout ----- + ----- stderr ----- + error: No interpreter found for Python 3.12 in [PYTHON SOURCES] - ----- stderr ----- - error: No interpreter found for Python 3.12 in managed installations, search path, or registry - "###); - } else { - uv_snapshot!(context.filters(), context.python_pin().arg("--resolved").arg("3.12"), @r###" - success: false - exit_code: 2 - ----- stdout ----- - - ----- stderr ----- - error: No interpreter found for Python 3.12 in managed installations or search path - "###); - } + hint: A managed Python download is available for Python 3.12, but Python downloads are set to 'never' + "); } #[test] @@ -741,14 +731,16 @@ fn python_pin_resolve() { // Request an implementation that is not installed // (skip on Windows because the snapshot is different and the behavior is not platform dependent) #[cfg(unix)] - uv_snapshot!(context.filters(), context.python_pin().arg("--resolved").arg("pypy"), @r###" + uv_snapshot!(context.filters(), context.python_pin().arg("--resolved").arg("pypy"), @r" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- error: No interpreter found for PyPy in managed installations or search path - "###); + + hint: A managed Python download is available for PyPy, but Python downloads are set to 'never' + "); let python_version = context.read(PYTHON_VERSION_FILENAME); insta::with_settings!({ diff --git a/crates/uv/tests/it/tool_run.rs b/crates/uv/tests/it/tool_run.rs index d4dcb216c..153adeb51 100644 --- a/crates/uv/tests/it/tool_run.rs +++ b/crates/uv/tests/it/tool_run.rs @@ -2049,6 +2049,54 @@ fn tool_run_python_at_version() { "###); } +#[test] +fn tool_run_hint_version_not_available() { + let context = TestContext::new_with_versions(&[]) + .with_filtered_counts() + .with_filtered_python_sources(); + + uv_snapshot!(context.filters(), context.tool_run() + .arg("python@3.12") + .env(EnvVars::UV_PYTHON_DOWNLOADS, "never"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: No interpreter found for Python 3.12 in [PYTHON SOURCES] + + hint: A managed Python download is available for Python 3.12, but Python downloads are set to 'never' + "); + + uv_snapshot!(context.filters(), context.tool_run() + .arg("python@3.12") + .env(EnvVars::UV_PYTHON_DOWNLOADS, "auto") + .env(EnvVars::UV_OFFLINE, "true"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: No interpreter found for Python 3.12 in [PYTHON SOURCES] + + hint: A managed Python download is available for Python 3.12, but uv is set to offline mode + "); + + uv_snapshot!(context.filters(), context.tool_run() + .arg("python@3.12") + .env(EnvVars::UV_PYTHON_DOWNLOADS, "auto") + .env(EnvVars::UV_NO_MANAGED_PYTHON, "true"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: No interpreter found for Python 3.12 in [PYTHON SOURCES] + + hint: A managed Python download is available for Python 3.12, but the Python preference is set to 'only system' + "); +} + #[test] fn tool_run_python_from() { let context = TestContext::new_with_versions(&["3.12", "3.11"]) From b0348ee2a9dbad692ed70e8c71c046187871d000 Mon Sep 17 00:00:00 2001 From: Ben Beasley Date: Thu, 10 Jul 2025 14:19:36 -0400 Subject: [PATCH 181/349] Conditionalize version_extras test on the pypi feature (#14536) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary The `version_extras` test added in 85c0fc963b1d4f6c8130c4b4446d15b8f6ac8ac4 needs to connect to PyPI. This PR conditionalizes it on the `pypi` extra so that people running the tests offline don’t have to skip that test explicitly. ## Test Plan I already ran `cargo test` in the git checkout to confirm I didn’t somehow introduce a syntax error. I am also applying this PR as a patch to [the `uv` package in Fedora](https://src.fedoraproject.org/rpms/uv), which runs tests offline with the `pypi` feature disabled. --- crates/uv/tests/it/version.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/uv/tests/it/version.rs b/crates/uv/tests/it/version.rs index ab09833f9..3c5e28e0f 100644 --- a/crates/uv/tests/it/version.rs +++ b/crates/uv/tests/it/version.rs @@ -2558,6 +2558,7 @@ fn version_set_evil_constraints() -> Result<()> { /// Bump the version with conflicting extras, to ensure we're activating the correct subset of /// extras during the resolve. #[test] +#[cfg(feature = "pypi")] fn version_extras() -> Result<()> { let context = TestContext::new("3.12"); From 43dbdba578179d8e6ced763916b658dcfb9c53c6 Mon Sep 17 00:00:00 2001 From: Noam Teyssier <22600644+noamteyssier@users.noreply.github.com> Date: Thu, 10 Jul 2025 11:50:50 -0700 Subject: [PATCH 182/349] feature: shorthand for --with (-w) in uvx and uv tool run (#14530) ## Summary This is a small quality of life feature that adds a shorthand (`-w`) to the `--with` flag for minimizing keystrokes. Pretty minor, but I didn't see any conflicts with `-w` and thought this could be a nice place for it. ```bash # proposed addition (short) uvx -w numpy ipython # original (long) uvx --with numpy ipython ``` ## Test Plan Added testing already in the P.R. - just copied over tests from the `--with` flag --- crates/uv-cli/src/lib.rs | 6 ++-- crates/uv/tests/it/tool_run.rs | 64 ++++++++++++++++++++++++++++++++++ docs/concepts/tools.md | 6 ++++ docs/reference/cli.md | 6 ++-- 4 files changed, 76 insertions(+), 6 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 3e9aba123..39dac54ba 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -3045,7 +3045,7 @@ pub struct RunArgs { /// When used in a project, these dependencies will be layered on top of the project environment /// in a separate, ephemeral environment. These dependencies are allowed to conflict with those /// specified by the project. - #[arg(long)] + #[arg(short = 'w', long)] pub with: Vec, /// Run with the given packages installed in editable mode. @@ -4256,7 +4256,7 @@ pub struct ToolRunArgs { pub from: Option, /// Run with the given packages installed. - #[arg(long)] + #[arg(short = 'w', long)] pub with: Vec, /// Run with the given packages installed in editable mode @@ -4371,7 +4371,7 @@ pub struct ToolInstallArgs { pub from: Option, /// Include the following additional requirements. - #[arg(long)] + #[arg(short = 'w', long)] pub with: Vec, /// Include all requirements listed in the given `requirements.txt` files. diff --git a/crates/uv/tests/it/tool_run.rs b/crates/uv/tests/it/tool_run.rs index 153adeb51..fb6287454 100644 --- a/crates/uv/tests/it/tool_run.rs +++ b/crates/uv/tests/it/tool_run.rs @@ -1125,6 +1125,70 @@ fn tool_run_without_output() { "###); } +#[test] +#[cfg(not(windows))] +fn tool_run_csv_with_shorthand() -> anyhow::Result<()> { + let context = TestContext::new("3.12").with_filtered_counts(); + let tool_dir = context.temp_dir.child("tools"); + let bin_dir = context.temp_dir.child("bin"); + + let anyio_local = context.temp_dir.child("src").child("anyio_local"); + copy_dir_all( + context.workspace_root.join("scripts/packages/anyio_local"), + &anyio_local, + )?; + + let black_editable = context.temp_dir.child("src").child("black_editable"); + copy_dir_all( + context + .workspace_root + .join("scripts/packages/black_editable"), + &black_editable, + )?; + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str(indoc! { r#" + [project] + name = "foo" + version = "1.0.0" + requires-python = ">=3.8" + dependencies = ["anyio", "sniffio==1.3.1"] + "# + })?; + + let test_script = context.temp_dir.child("main.py"); + test_script.write_str(indoc! { r" + import sniffio + " + })?; + + // Performs a tool run with a comma-separated `--with` flag. + uv_snapshot!(context.filters(), context.tool_run() + .arg("-w") + .arg("iniconfig,typing-extensions") + .arg("pytest") + .arg("--version") + .env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str()) + .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r###" + success: true + exit_code: 0 + ----- stdout ----- + pytest 8.1.1 + + ----- stderr ----- + Resolved [N] packages in [TIME] + Prepared [N] packages in [TIME] + Installed [N] packages in [TIME] + + iniconfig==2.0.0 + + packaging==24.0 + + pluggy==1.4.0 + + pytest==8.1.1 + + typing-extensions==4.10.0 + "###); + + Ok(()) +} + #[test] #[cfg(not(windows))] fn tool_run_csv_with() -> anyhow::Result<()> { diff --git a/docs/concepts/tools.md b/docs/concepts/tools.md index d5069e725..7c5eb9564 100644 --- a/docs/concepts/tools.md +++ b/docs/concepts/tools.md @@ -200,6 +200,12 @@ The `--with` option supports package specifications, so a specific version can b $ uvx --with == ``` +The `-w` shorthand can be used in place of the `--with` option: + +```console +$ uvx -w +``` + If the requested version conflicts with the requirements of the tool package, package resolution will fail and the command will error. diff --git a/docs/reference/cli.md b/docs/reference/cli.md index bd828acda..f20bcf7ec 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -246,7 +246,7 @@ used.

    --upgrade-package, -P upgrade-package

    Allow upgrades for a specific package, ignoring pinned versions in any existing output file. Implies --refresh-package

    --verbose, -v

    Use verbose output.

    You can configure fine-grained logging using the RUST_LOG environment variable. (https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives)

    -
    --with with

    Run with the given packages installed.

    +
    --with, -w with

    Run with the given packages installed.

    When used in a project, these dependencies will be layered on top of the project environment in a separate, ephemeral environment. These dependencies are allowed to conflict with those specified by the project.

    --with-editable with-editable

    Run with the given packages installed in editable mode.

    When used in a project, these dependencies will be layered on top of the project environment in a separate, ephemeral environment. These dependencies are allowed to conflict with those specified by the project.

    @@ -1935,7 +1935,7 @@ uv tool run [OPTIONS] [COMMAND]
    --upgrade-package, -P upgrade-package

    Allow upgrades for a specific package, ignoring pinned versions in any existing output file. Implies --refresh-package

    --verbose, -v

    Use verbose output.

    You can configure fine-grained logging using the RUST_LOG environment variable. (https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives)

    -
    --with with

    Run with the given packages installed

    +
    --with, -w with

    Run with the given packages installed

    --with-editable with-editable

    Run with the given packages installed in editable mode

    When used in a project, these dependencies will be layered on top of the uv tool's environment in a separate, ephemeral environment. These dependencies are allowed to conflict with those specified.

    --with-requirements with-requirements

    Run with all packages listed in the given requirements.txt files

    @@ -2104,7 +2104,7 @@ uv tool install [OPTIONS]
    --upgrade-package, -P upgrade-package

    Allow upgrades for a specific package, ignoring pinned versions in any existing output file. Implies --refresh-package

    --verbose, -v

    Use verbose output.

    You can configure fine-grained logging using the RUST_LOG environment variable. (https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives)

    -
    --with with

    Include the following additional requirements

    +
    --with, -w with

    Include the following additional requirements

    --with-editable with-editable

    Include the given packages in editable mode

    --with-requirements with-requirements

    Include all requirements listed in the given requirements.txt files

    From a3bc30c1a7f6ebf230227ea8aa0b77013ab20412 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 10 Jul 2025 19:00:22 -0400 Subject: [PATCH 183/349] Use `astral-sh` fork of `rs-async-zip` (#14552) ## Summary I transferred ownership from my personal GitHub to `astral-sh`. There's no change in contents, etc. --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5861cd325..bc42e30af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -251,7 +251,7 @@ dependencies = [ [[package]] name = "async_zip" version = "0.0.17" -source = "git+https://github.com/charliermarsh/rs-async-zip?rev=c909fda63fcafe4af496a07bfda28a5aae97e58d#c909fda63fcafe4af496a07bfda28a5aae97e58d" +source = "git+https://github.com/astral-sh/rs-async-zip?rev=c909fda63fcafe4af496a07bfda28a5aae97e58d#c909fda63fcafe4af496a07bfda28a5aae97e58d" dependencies = [ "async-compression", "crc32fast", diff --git a/Cargo.toml b/Cargo.toml index fc19dcc9a..ecdc11701 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,7 +80,7 @@ async-channel = { version = "2.3.1" } async-compression = { version = "0.4.12", features = ["bzip2", "gzip", "xz", "zstd"] } async-trait = { version = "0.1.82" } async_http_range_reader = { version = "0.9.1" } -async_zip = { git = "https://github.com/charliermarsh/rs-async-zip", rev = "c909fda63fcafe4af496a07bfda28a5aae97e58d", features = ["bzip2", "deflate", "lzma", "tokio", "xz", "zstd"] } +async_zip = { git = "https://github.com/astral-sh/rs-async-zip", rev = "c909fda63fcafe4af496a07bfda28a5aae97e58d", features = ["bzip2", "deflate", "lzma", "tokio", "xz", "zstd"] } axoupdater = { version = "0.9.0", default-features = false } backon = { version = "1.3.0" } base64 = { version = "0.22.1" } From 1b2ac40568eaa788c45752c6fc206da1130688b5 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 10 Jul 2025 18:19:45 -0500 Subject: [PATCH 184/349] Fix `if` on macos test job (#14551) --- .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 4a8e8fb12..b0d8e18a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -233,7 +233,7 @@ jobs: timeout-minutes: 15 needs: determine_changes # Only run macOS tests on main without opt-in - if: ${{ contains(github.event.pull_request.labels.*.name, 'test:macos' || github.ref == 'refs/heads/main') }} + if: ${{ contains(github.event.pull_request.labels.*.name, 'test:macos') || github.ref == 'refs/heads/main' }} runs-on: macos-latest-xlarge # github-macos-14-aarch64-6 name: "cargo test | macos" steps: From 0fb8c2b1d70820f24675b863570574f6b26aa1cb Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 10 Jul 2025 21:38:28 -0400 Subject: [PATCH 185/349] Add `--python-platform` to `uv sync` (#14320) ## Summary Closes https://github.com/astral-sh/uv/issues/14273. --- crates/uv-cli/src/lib.rs | 17 +++++++++ crates/uv/src/commands/project/add.rs | 1 + crates/uv/src/commands/project/remove.rs | 1 + crates/uv/src/commands/project/run.rs | 2 + crates/uv/src/commands/project/sync.rs | 22 ++++++----- crates/uv/src/commands/project/version.rs | 1 + crates/uv/src/lib.rs | 1 + crates/uv/src/settings.rs | 3 ++ crates/uv/tests/it/sync.rs | 39 +++++++++++++++++++ docs/reference/cli.md | 46 ++++++++++++++++++++++- 10 files changed, 123 insertions(+), 10 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 39dac54ba..056447959 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -3439,6 +3439,23 @@ pub struct SyncArgs { )] pub python: Option>, + /// The platform for which requirements should be installed. + /// + /// Represented as a "target triple", a string that describes the target platform in terms of + /// its CPU, vendor, and operating system name, like `x86_64-unknown-linux-gnu` or + /// `aarch64-apple-darwin`. + /// + /// When targeting macOS (Darwin), the default minimum version is `12.0`. Use + /// `MACOSX_DEPLOYMENT_TARGET` to specify a different minimum version, e.g., `13.0`. + /// + /// WARNING: When specified, uv will select wheels that are compatible with the _target_ + /// platform; as a result, the installed distributions may not be compatible with the _current_ + /// platform. Conversely, any distributions that are built from source may be incompatible with + /// the _target_ platform, as they will be built for the _current_ platform. The + /// `--python-platform` option is intended for advanced use cases. + #[arg(long)] + pub python_platform: Option, + /// Check if the Python environment is synchronized with the project. /// /// If the environment is not up to date, uv will exit with an error. diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index 959241b4b..f255194de 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -1080,6 +1080,7 @@ async fn lock_and_sync( EditableMode::Editable, InstallOptions::default(), Modifications::Sufficient, + None, settings.into(), network_settings, &sync_state, diff --git a/crates/uv/src/commands/project/remove.rs b/crates/uv/src/commands/project/remove.rs index 6bc04160e..50615699e 100644 --- a/crates/uv/src/commands/project/remove.rs +++ b/crates/uv/src/commands/project/remove.rs @@ -357,6 +357,7 @@ pub(crate) async fn remove( EditableMode::Editable, InstallOptions::default(), Modifications::Exact, + None, (&settings).into(), &network_settings, &state, diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index a4fd4ae7d..f0a46f16a 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -305,6 +305,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl editable, install_options, modifications, + None, (&settings).into(), &network_settings, &sync_state, @@ -816,6 +817,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl editable, install_options, modifications, + None, (&settings).into(), &network_settings, &sync_state, diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index 6e057446e..664eb2a94 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -13,7 +13,7 @@ use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{ Concurrency, Constraints, DependencyGroups, DependencyGroupsWithDefaults, DryRun, EditableMode, ExtrasSpecification, ExtrasSpecificationWithDefaults, HashCheckingMode, InstallOptions, - PreviewMode, + PreviewMode, TargetTriple, }; use uv_dispatch::BuildDispatch; use uv_distribution_types::{ @@ -34,8 +34,9 @@ use uv_workspace::pyproject::Source; use uv_workspace::{DiscoveryOptions, MemberDiscovery, VirtualProject, Workspace, WorkspaceCache}; use crate::commands::pip::loggers::{DefaultInstallLogger, DefaultResolveLogger, InstallLogger}; -use crate::commands::pip::operations; use crate::commands::pip::operations::Modifications; +use crate::commands::pip::resolution_markers; +use crate::commands::pip::{operations, resolution_tags}; use crate::commands::project::install_target::InstallTarget; use crate::commands::project::lock::{LockMode, LockOperation, LockResult}; use crate::commands::project::lock_target::LockTarget; @@ -63,6 +64,7 @@ pub(crate) async fn sync( install_options: InstallOptions, modifications: Modifications, python: Option, + python_platform: Option, install_mirrors: PythonInstallMirrors, python_preference: PythonPreference, python_downloads: PythonDownloads, @@ -453,6 +455,7 @@ pub(crate) async fn sync( editable, install_options, modifications, + python_platform.as_ref(), (&settings).into(), &network_settings, &state, @@ -589,6 +592,7 @@ pub(super) async fn do_sync( editable: EditableMode, install_options: InstallOptions, modifications: Modifications, + python_platform: Option<&TargetTriple>, settings: InstallerSettingsRef<'_>, network_settings: &NetworkSettings, state: &PlatformState, @@ -644,7 +648,7 @@ pub(super) async fn do_sync( target.validate_groups(groups)?; // Determine the markers to use for resolution. - let marker_env = venv.interpreter().resolver_marker_environment(); + let marker_env = resolution_markers(None, python_platform, venv.interpreter()); // Validate that the platform is supported by the lockfile. let environments = target.lock().supported_environments(); @@ -670,13 +674,13 @@ pub(super) async fn do_sync( } } - // Determine the tags to use for resolution. - let tags = venv.interpreter().tags()?; + // Determine the tags to use for the resolution. + let tags = resolution_tags(None, python_platform, venv.interpreter())?; // Read the lockfile. let resolution = target.to_resolution( &marker_env, - tags, + &tags, extras, groups, build_options, @@ -728,7 +732,7 @@ pub(super) async fn do_sync( let entries = client .fetch_all(index_locations.flat_indexes().map(Index::url)) .await?; - FlatIndex::from_entries(entries, Some(tags), &hasher, build_options) + FlatIndex::from_entries(entries, Some(&tags), &hasher, build_options) }; // Create a build dispatch. @@ -768,7 +772,7 @@ pub(super) async fn do_sync( index_locations, config_setting, &hasher, - tags, + &tags, &client, state.in_flight(), concurrency, @@ -847,7 +851,7 @@ fn apply_editable_mode(resolution: Resolution, editable: EditableMode) -> Resolu /// These credentials can come from any of `tool.uv.sources`, `tool.uv.dev-dependencies`, /// `project.dependencies`, and `project.optional-dependencies`. fn store_credentials_from_target(target: InstallTarget<'_>) { - // Iterate over any idnexes in the target. + // Iterate over any indexes in the target. for index in target.indexes() { if let Some(credentials) = index.credentials() { let credentials = Arc::new(credentials); diff --git a/crates/uv/src/commands/project/version.rs b/crates/uv/src/commands/project/version.rs index ec278d4b4..ed1e9e246 100644 --- a/crates/uv/src/commands/project/version.rs +++ b/crates/uv/src/commands/project/version.rs @@ -634,6 +634,7 @@ async fn lock_and_sync( EditableMode::Editable, install_options, Modifications::Sufficient, + None, settings.into(), &network_settings, &state, diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 28a20f373..261dd8d7c 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -1802,6 +1802,7 @@ async fn run_project( args.install_options, args.modifications, args.python, + args.python_platform, args.install_mirrors, globals.python_preference, globals.python_downloads, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index ed86608ed..f89704d45 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -1150,6 +1150,7 @@ pub(crate) struct SyncSettings { pub(crate) all_packages: bool, pub(crate) package: Option, pub(crate) python: Option, + pub(crate) python_platform: Option, pub(crate) install_mirrors: PythonInstallMirrors, pub(crate) refresh: Refresh, pub(crate) settings: ResolverInstallerSettings, @@ -1190,6 +1191,7 @@ impl SyncSettings { package, script, python, + python_platform, check, no_check, } = args; @@ -1249,6 +1251,7 @@ impl SyncSettings { all_packages, package, python: python.and_then(Maybe::into_option), + python_platform, refresh: Refresh::from(refresh), settings, install_mirrors, diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index 690079abf..d4479296a 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -10026,3 +10026,42 @@ fn read_only() -> Result<()> { Ok(()) } + +#[test] +fn sync_python_platform() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["black"] + "#, + )?; + + // Lock the project + context.lock().assert().success(); + + // Sync with a specific platform should filter packages + uv_snapshot!(context.filters(), context.sync().arg("--python-platform").arg("linux"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 8 packages in [TIME] + Prepared 6 packages in [TIME] + Installed 6 packages in [TIME] + + black==24.3.0 + + click==8.1.7 + + mypy-extensions==1.0.0 + + packaging==24.0 + + pathspec==0.12.1 + + platformdirs==4.2.0 + "); + + Ok(()) +} diff --git a/docs/reference/cli.md b/docs/reference/cli.md index f20bcf7ec..989cbc54b 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -1138,7 +1138,51 @@ used.

    synced to the given environment. The interpreter will be used to create a virtual environment in the project.

    See uv python for details on Python discovery and supported request formats.

    -

    May also be set with the UV_PYTHON environment variable.

    --quiet, -q

    Use quiet output.

    +

    May also be set with the UV_PYTHON environment variable.

    --python-platform python-platform

    The platform for which requirements should be installed.

    +

    Represented as a "target triple", a string that describes the target platform in terms of its CPU, vendor, and operating system name, like x86_64-unknown-linux-gnu or aarch64-apple-darwin.

    +

    When targeting macOS (Darwin), the default minimum version is 12.0. Use MACOSX_DEPLOYMENT_TARGET to specify a different minimum version, e.g., 13.0.

    +

    WARNING: When specified, uv will select wheels that are compatible with the target platform; as a result, the installed distributions may not be compatible with the current platform. Conversely, any distributions that are built from source may be incompatible with the target platform, as they will be built for the current platform. The --python-platform option is intended for advanced use cases.

    +

    Possible values:

    +
      +
    • windows: An alias for x86_64-pc-windows-msvc, the default target for Windows
    • +
    • linux: An alias for x86_64-unknown-linux-gnu, the default target for Linux
    • +
    • macos: An alias for aarch64-apple-darwin, the default target for macOS
    • +
    • x86_64-pc-windows-msvc: A 64-bit x86 Windows target
    • +
    • i686-pc-windows-msvc: A 32-bit x86 Windows target
    • +
    • x86_64-unknown-linux-gnu: An x86 Linux target. Equivalent to x86_64-manylinux_2_17
    • +
    • aarch64-apple-darwin: An ARM-based macOS target, as seen on Apple Silicon devices
    • +
    • x86_64-apple-darwin: An x86 macOS target
    • +
    • aarch64-unknown-linux-gnu: An ARM64 Linux target. Equivalent to aarch64-manylinux_2_17
    • +
    • aarch64-unknown-linux-musl: An ARM64 Linux target
    • +
    • x86_64-unknown-linux-musl: An x86_64 Linux target
    • +
    • x86_64-manylinux2014: An x86_64 target for the manylinux2014 platform. Equivalent to x86_64-manylinux_2_17
    • +
    • x86_64-manylinux_2_17: An x86_64 target for the manylinux_2_17 platform
    • +
    • x86_64-manylinux_2_28: An x86_64 target for the manylinux_2_28 platform
    • +
    • x86_64-manylinux_2_31: An x86_64 target for the manylinux_2_31 platform
    • +
    • x86_64-manylinux_2_32: An x86_64 target for the manylinux_2_32 platform
    • +
    • x86_64-manylinux_2_33: An x86_64 target for the manylinux_2_33 platform
    • +
    • x86_64-manylinux_2_34: An x86_64 target for the manylinux_2_34 platform
    • +
    • x86_64-manylinux_2_35: An x86_64 target for the manylinux_2_35 platform
    • +
    • x86_64-manylinux_2_36: An x86_64 target for the manylinux_2_36 platform
    • +
    • x86_64-manylinux_2_37: An x86_64 target for the manylinux_2_37 platform
    • +
    • x86_64-manylinux_2_38: An x86_64 target for the manylinux_2_38 platform
    • +
    • x86_64-manylinux_2_39: An x86_64 target for the manylinux_2_39 platform
    • +
    • x86_64-manylinux_2_40: An x86_64 target for the manylinux_2_40 platform
    • +
    • aarch64-manylinux2014: An ARM64 target for the manylinux2014 platform. Equivalent to aarch64-manylinux_2_17
    • +
    • aarch64-manylinux_2_17: An ARM64 target for the manylinux_2_17 platform
    • +
    • aarch64-manylinux_2_28: An ARM64 target for the manylinux_2_28 platform
    • +
    • aarch64-manylinux_2_31: An ARM64 target for the manylinux_2_31 platform
    • +
    • aarch64-manylinux_2_32: An ARM64 target for the manylinux_2_32 platform
    • +
    • aarch64-manylinux_2_33: An ARM64 target for the manylinux_2_33 platform
    • +
    • aarch64-manylinux_2_34: An ARM64 target for the manylinux_2_34 platform
    • +
    • aarch64-manylinux_2_35: An ARM64 target for the manylinux_2_35 platform
    • +
    • aarch64-manylinux_2_36: An ARM64 target for the manylinux_2_36 platform
    • +
    • aarch64-manylinux_2_37: An ARM64 target for the manylinux_2_37 platform
    • +
    • aarch64-manylinux_2_38: An ARM64 target for the manylinux_2_38 platform
    • +
    • aarch64-manylinux_2_39: An ARM64 target for the manylinux_2_39 platform
    • +
    • aarch64-manylinux_2_40: An ARM64 target for the manylinux_2_40 platform
    • +
    • wasm32-pyodide2024: A wasm32 target using the the Pyodide 2024 platform. Meant for use with Python 3.12
    • +
    --quiet, -q

    Use quiet output.

    Repeating this option, e.g., -qq, will enable a silent mode in which uv will write no output to stdout.

    --refresh

    Refresh all cached data

    --refresh-package refresh-package

    Refresh cached data for a specific package

    From cc5d5d5fba546afca77788ced6ae2f2488b90f17 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 10 Jul 2025 21:41:32 -0400 Subject: [PATCH 186/349] Fix repeated word in Pyodide doc (#14554) --- crates/uv-configuration/src/target_triple.rs | 2 +- docs/reference/cli.md | 8 ++++---- uv.schema.json | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/uv-configuration/src/target_triple.rs b/crates/uv-configuration/src/target_triple.rs index b9ca3fafe..81499deff 100644 --- a/crates/uv-configuration/src/target_triple.rs +++ b/crates/uv-configuration/src/target_triple.rs @@ -227,7 +227,7 @@ pub enum TargetTriple { #[serde(alias = "aarch64-manylinux240")] Aarch64Manylinux240, - /// A wasm32 target using the the Pyodide 2024 platform. Meant for use with Python 3.12. + /// A wasm32 target using the Pyodide 2024 platform. Meant for use with Python 3.12. #[cfg_attr(feature = "clap", value(name = "wasm32-pyodide2024"))] Wasm32Pyodide2024, } diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 989cbc54b..f8247d5aa 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -1763,7 +1763,7 @@ interpreter. Use --universal to display the tree for all platforms,
  • aarch64-manylinux_2_38: An ARM64 target for the manylinux_2_38 platform
  • aarch64-manylinux_2_39: An ARM64 target for the manylinux_2_39 platform
  • aarch64-manylinux_2_40: An ARM64 target for the manylinux_2_40 platform
  • -
  • wasm32-pyodide2024: A wasm32 target using the the Pyodide 2024 platform. Meant for use with Python 3.12
  • +
  • wasm32-pyodide2024: A wasm32 target using the Pyodide 2024 platform. Meant for use with Python 3.12
  • --python-version python-version

    The Python version to use when filtering the tree.

    For example, pass --python-version 3.10 to display the dependencies that would be included when installing on Python 3.10.

    Defaults to the version of the discovered Python interpreter.

    @@ -3448,7 +3448,7 @@ by --python-version.

  • aarch64-manylinux_2_38: An ARM64 target for the manylinux_2_38 platform
  • aarch64-manylinux_2_39: An ARM64 target for the manylinux_2_39 platform
  • aarch64-manylinux_2_40: An ARM64 target for the manylinux_2_40 platform
  • -
  • wasm32-pyodide2024: A wasm32 target using the the Pyodide 2024 platform. Meant for use with Python 3.12
  • +
  • wasm32-pyodide2024: A wasm32 target using the Pyodide 2024 platform. Meant for use with Python 3.12
  • --python-version python-version

    The Python version to use for resolution.

    For example, 3.8 or 3.8.17.

    Defaults to the version of the Python interpreter used for resolution.

    @@ -3705,7 +3705,7 @@ be used with caution, as it can modify the system Python installation.

  • aarch64-manylinux_2_38: An ARM64 target for the manylinux_2_38 platform
  • aarch64-manylinux_2_39: An ARM64 target for the manylinux_2_39 platform
  • aarch64-manylinux_2_40: An ARM64 target for the manylinux_2_40 platform
  • -
  • wasm32-pyodide2024: A wasm32 target using the the Pyodide 2024 platform. Meant for use with Python 3.12
  • +
  • wasm32-pyodide2024: A wasm32 target using the Pyodide 2024 platform. Meant for use with Python 3.12
  • --python-version python-version

    The minimum Python version that should be supported by the requirements (e.g., 3.7 or 3.7.9).

    If a patch version is omitted, the minimum patch version is assumed. For example, 3.7 is mapped to 3.7.0.

    --quiet, -q

    Use quiet output.

    @@ -3987,7 +3987,7 @@ should be used with caution, as it can modify the system Python installation.

    aarch64-manylinux_2_38: An ARM64 target for the manylinux_2_38 platform
  • aarch64-manylinux_2_39: An ARM64 target for the manylinux_2_39 platform
  • aarch64-manylinux_2_40: An ARM64 target for the manylinux_2_40 platform
  • -
  • wasm32-pyodide2024: A wasm32 target using the the Pyodide 2024 platform. Meant for use with Python 3.12
  • +
  • wasm32-pyodide2024: A wasm32 target using the Pyodide 2024 platform. Meant for use with Python 3.12
  • --python-version python-version

    The minimum Python version that should be supported by the requirements (e.g., 3.7 or 3.7.9).

    If a patch version is omitted, the minimum patch version is assumed. For example, 3.7 is mapped to 3.7.0.

    --quiet, -q

    Use quiet output.

    diff --git a/uv.schema.json b/uv.schema.json index aba25a46e..4190672e9 100644 --- a/uv.schema.json +++ b/uv.schema.json @@ -2242,7 +2242,7 @@ "const": "aarch64-manylinux_2_40" }, { - "description": "A wasm32 target using the the Pyodide 2024 platform. Meant for use with Python 3.12.", + "description": "A wasm32 target using the Pyodide 2024 platform. Meant for use with Python 3.12.", "type": "string", "const": "wasm32-pyodide2024" } From 2e0f399eeb57e06dadf6e4d652d31329ada64e85 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 10 Jul 2025 21:46:44 -0400 Subject: [PATCH 187/349] Run `cargo dev generate-all` (#14555) ## Summary I think we had a missing rebase. --- docs/reference/cli.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/cli.md b/docs/reference/cli.md index f8247d5aa..0364703c2 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -1181,7 +1181,7 @@ environment in the project.

  • aarch64-manylinux_2_38: An ARM64 target for the manylinux_2_38 platform
  • aarch64-manylinux_2_39: An ARM64 target for the manylinux_2_39 platform
  • aarch64-manylinux_2_40: An ARM64 target for the manylinux_2_40 platform
  • -
  • wasm32-pyodide2024: A wasm32 target using the the Pyodide 2024 platform. Meant for use with Python 3.12
  • +
  • wasm32-pyodide2024: A wasm32 target using the Pyodide 2024 platform. Meant for use with Python 3.12
  • --quiet, -q

    Use quiet output.

    Repeating this option, e.g., -qq, will enable a silent mode in which uv will write no output to stdout.

    --refresh

    Refresh all cached data

    From 71470b7b1ae41867846822716b11d66f23ecdabd Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Fri, 11 Jul 2025 07:35:27 -0500 Subject: [PATCH 188/349] Add `UV_HTTP_RETRIES` to customize retry counts (#14544) I want to increase this number in CI and was surprised we didn't support configuration yet. --- crates/uv-client/src/base_client.rs | 26 +++++++++- crates/uv-client/src/registry_client.rs | 5 ++ crates/uv-requirements/src/lib.rs | 3 ++ crates/uv-static/src/env_vars.rs | 6 +++ crates/uv/src/commands/build_frontend.rs | 1 + crates/uv/src/commands/pip/compile.rs | 1 + crates/uv/src/commands/pip/install.rs | 1 + crates/uv/src/commands/pip/list.rs | 1 + crates/uv/src/commands/pip/sync.rs | 1 + crates/uv/src/commands/pip/tree.rs | 1 + crates/uv/src/commands/pip/uninstall.rs | 1 + crates/uv/src/commands/project/add.rs | 2 + crates/uv/src/commands/project/init.rs | 2 + crates/uv/src/commands/project/lock.rs | 2 + crates/uv/src/commands/project/mod.rs | 7 +++ crates/uv/src/commands/project/run.rs | 4 ++ crates/uv/src/commands/project/sync.rs | 1 + crates/uv/src/commands/project/tree.rs | 1 + crates/uv/src/commands/publish.rs | 1 + crates/uv/src/commands/python/install.rs | 1 + crates/uv/src/commands/python/pin.rs | 1 + crates/uv/src/commands/tool/install.rs | 2 + crates/uv/src/commands/tool/run.rs | 1 + crates/uv/src/commands/tool/upgrade.rs | 1 + crates/uv/src/commands/venv.rs | 3 ++ crates/uv/tests/it/pip_install.rs | 60 ++++++++++++++++++++++++ docs/reference/environment.md | 8 ++++ 27 files changed, 143 insertions(+), 1 deletion(-) diff --git a/crates/uv-client/src/base_client.rs b/crates/uv-client/src/base_client.rs index e11845adb..9ddc30e75 100644 --- a/crates/uv-client/src/base_client.rs +++ b/crates/uv-client/src/base_client.rs @@ -6,6 +6,7 @@ use std::sync::Arc; use std::time::Duration; use std::{env, io, iter}; +use anyhow::Context; use anyhow::anyhow; use http::{ HeaderMap, HeaderName, HeaderValue, Method, StatusCode, @@ -166,6 +167,25 @@ impl<'a> BaseClientBuilder<'a> { self } + /// Read the retry count from [`EnvVars::UV_HTTP_RETRIES`] if set, otherwise, make no change. + /// + /// Errors when [`EnvVars::UV_HTTP_RETRIES`] is not a valid u32. + pub fn retries_from_env(self) -> anyhow::Result { + // TODO(zanieb): We should probably parse this in another layer, but there's not a natural + // fit for it right now + if let Some(value) = env::var_os(EnvVars::UV_HTTP_RETRIES) { + Ok(self.retries( + value + .to_string_lossy() + .as_ref() + .parse::() + .context("Failed to parse `UV_HTTP_RETRIES`")?, + )) + } else { + Ok(self) + } + } + #[must_use] pub fn native_tls(mut self, native_tls: bool) -> Self { self.native_tls = native_tls; @@ -238,7 +258,11 @@ impl<'a> BaseClientBuilder<'a> { /// Create a [`RetryPolicy`] for the client. fn retry_policy(&self) -> ExponentialBackoff { - ExponentialBackoff::builder().build_with_max_retries(self.retries) + let mut builder = ExponentialBackoff::builder(); + if env::var_os(EnvVars::UV_TEST_NO_HTTP_RETRY_DELAY).is_some() { + builder = builder.retry_bounds(Duration::from_millis(0), Duration::from_millis(0)); + } + builder.build_with_max_retries(self.retries) } pub fn build(&self) -> BaseClient { diff --git a/crates/uv-client/src/registry_client.rs b/crates/uv-client/src/registry_client.rs index c7694676c..afa1b03ae 100644 --- a/crates/uv-client/src/registry_client.rs +++ b/crates/uv-client/src/registry_client.rs @@ -115,6 +115,11 @@ impl<'a> RegistryClientBuilder<'a> { self } + pub fn retries_from_env(mut self) -> anyhow::Result { + self.base_client_builder = self.base_client_builder.retries_from_env()?; + Ok(self) + } + #[must_use] pub fn native_tls(mut self, native_tls: bool) -> Self { self.base_client_builder = self.base_client_builder.native_tls(native_tls); diff --git a/crates/uv-requirements/src/lib.rs b/crates/uv-requirements/src/lib.rs index 812f9141f..68fe84abc 100644 --- a/crates/uv-requirements/src/lib.rs +++ b/crates/uv-requirements/src/lib.rs @@ -31,6 +31,9 @@ pub enum Error { #[error(transparent)] WheelFilename(#[from] uv_distribution_filename::WheelFilenameError), + #[error("Failed to construct HTTP client")] + ClientError(#[source] anyhow::Error), + #[error(transparent)] Io(#[from] std::io::Error), } diff --git a/crates/uv-static/src/env_vars.rs b/crates/uv-static/src/env_vars.rs index 4ac2976d9..5b91fccea 100644 --- a/crates/uv-static/src/env_vars.rs +++ b/crates/uv-static/src/env_vars.rs @@ -402,6 +402,9 @@ impl EnvVars { /// Timeout (in seconds) for HTTP requests. (default: 30 s) pub const UV_HTTP_TIMEOUT: &'static str = "UV_HTTP_TIMEOUT"; + /// The number of retries for HTTP requests. (default: 3) + pub const UV_HTTP_RETRIES: &'static str = "UV_HTTP_RETRIES"; + /// Timeout (in seconds) for HTTP requests. Equivalent to `UV_HTTP_TIMEOUT`. pub const UV_REQUEST_TIMEOUT: &'static str = "UV_REQUEST_TIMEOUT"; @@ -659,6 +662,9 @@ impl EnvVars { #[attr_hidden] pub const UV_TEST_VENDOR_LINKS_URL: &'static str = "UV_TEST_VENDOR_LINKS_URL"; + /// Used to disable delay for HTTP retries in tests. + pub const UV_TEST_NO_HTTP_RETRY_DELAY: &'static str = "UV_TEST_NO_HTTP_RETRY_DELAY"; + /// Used to set an index url for tests. #[attr_hidden] pub const UV_TEST_INDEX_URL: &'static str = "UV_TEST_INDEX_URL"; diff --git a/crates/uv/src/commands/build_frontend.rs b/crates/uv/src/commands/build_frontend.rs index 2cef9a406..fd6ed73d7 100644 --- a/crates/uv/src/commands/build_frontend.rs +++ b/crates/uv/src/commands/build_frontend.rs @@ -207,6 +207,7 @@ async fn build_impl( } = settings; let client_builder = BaseClientBuilder::default() + .retries_from_env()? .connectivity(network_settings.connectivity) .native_tls(network_settings.native_tls) .allow_insecure_host(network_settings.allow_insecure_host.clone()); diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs index a1846d418..c40716763 100644 --- a/crates/uv/src/commands/pip/compile.rs +++ b/crates/uv/src/commands/pip/compile.rs @@ -179,6 +179,7 @@ pub(crate) async fn pip_compile( } let client_builder = BaseClientBuilder::new() + .retries_from_env()? .connectivity(network_settings.connectivity) .native_tls(network_settings.native_tls) .keyring(keyring_provider) diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs index aa6e6a6c9..bbfe99c50 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -99,6 +99,7 @@ pub(crate) async fn pip_install( let start = std::time::Instant::now(); let client_builder = BaseClientBuilder::new() + .retries_from_env()? .connectivity(network_settings.connectivity) .native_tls(network_settings.native_tls) .keyring(keyring_provider) diff --git a/crates/uv/src/commands/pip/list.rs b/crates/uv/src/commands/pip/list.rs index 356574436..40e8c770d 100644 --- a/crates/uv/src/commands/pip/list.rs +++ b/crates/uv/src/commands/pip/list.rs @@ -87,6 +87,7 @@ pub(crate) async fn pip_list( let capabilities = IndexCapabilities::default(); let client_builder = BaseClientBuilder::new() + .retries_from_env()? .connectivity(network_settings.connectivity) .native_tls(network_settings.native_tls) .keyring(keyring_provider) diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index 8f26aaea2..6858ddad0 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -81,6 +81,7 @@ pub(crate) async fn pip_sync( preview: PreviewMode, ) -> Result { let client_builder = BaseClientBuilder::new() + .retries_from_env()? .connectivity(network_settings.connectivity) .native_tls(network_settings.native_tls) .keyring(keyring_provider) diff --git a/crates/uv/src/commands/pip/tree.rs b/crates/uv/src/commands/pip/tree.rs index b0ba44c35..81a566b8e 100644 --- a/crates/uv/src/commands/pip/tree.rs +++ b/crates/uv/src/commands/pip/tree.rs @@ -86,6 +86,7 @@ pub(crate) async fn pip_tree( let capabilities = IndexCapabilities::default(); let client_builder = BaseClientBuilder::new() + .retries_from_env()? .connectivity(network_settings.connectivity) .native_tls(network_settings.native_tls) .keyring(keyring_provider) diff --git a/crates/uv/src/commands/pip/uninstall.rs b/crates/uv/src/commands/pip/uninstall.rs index 835e7de65..f617a0203 100644 --- a/crates/uv/src/commands/pip/uninstall.rs +++ b/crates/uv/src/commands/pip/uninstall.rs @@ -42,6 +42,7 @@ pub(crate) async fn pip_uninstall( let start = std::time::Instant::now(); let client_builder = BaseClientBuilder::new() + .retries_from_env()? .connectivity(network_settings.connectivity) .native_tls(network_settings.native_tls) .keyring(keyring_provider) diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index f255194de..d65866483 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -176,6 +176,7 @@ pub(crate) async fn add( } let client_builder = BaseClientBuilder::new() + .retries_from_env()? .connectivity(network_settings.connectivity) .native_tls(network_settings.native_tls) .allow_insecure_host(network_settings.allow_insecure_host.clone()); @@ -329,6 +330,7 @@ pub(crate) async fn add( .ok(); let client_builder = BaseClientBuilder::new() + .retries_from_env()? .connectivity(network_settings.connectivity) .native_tls(network_settings.native_tls) .keyring(settings.resolver.keyring_provider) diff --git a/crates/uv/src/commands/project/init.rs b/crates/uv/src/commands/project/init.rs index 15fed409e..9ff321a72 100644 --- a/crates/uv/src/commands/project/init.rs +++ b/crates/uv/src/commands/project/init.rs @@ -218,6 +218,7 @@ async fn init_script( warn_user_once!("`--package` is a no-op for Python scripts, which are standalone"); } let client_builder = BaseClientBuilder::new() + .retries_from_env()? .connectivity(network_settings.connectivity) .native_tls(network_settings.native_tls) .allow_insecure_host(network_settings.allow_insecure_host.clone()); @@ -348,6 +349,7 @@ async fn init_project( let reporter = PythonDownloadReporter::single(printer); let client_builder = BaseClientBuilder::new() + .retries_from_env()? .connectivity(network_settings.connectivity) .native_tls(network_settings.native_tls) .allow_insecure_host(network_settings.allow_insecure_host.clone()); diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index 9cbd43ea9..f79557d9e 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -99,6 +99,7 @@ pub(crate) async fn lock( let script = match script { Some(ScriptPath::Path(path)) => { let client_builder = BaseClientBuilder::new() + .retries_from_env()? .connectivity(network_settings.connectivity) .native_tls(network_settings.native_tls) .allow_insecure_host(network_settings.allow_insecure_host.clone()); @@ -588,6 +589,7 @@ async fn do_lock( // Initialize the client. let client_builder = BaseClientBuilder::new() + .retries_from_env()? .connectivity(network_settings.connectivity) .native_tls(network_settings.native_tls) .keyring(*keyring_provider) diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index a012e2855..1a0274cac 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -690,6 +690,7 @@ impl ScriptInterpreter { } let client_builder = BaseClientBuilder::new() + .retries_from_env()? .connectivity(network_settings.connectivity) .native_tls(network_settings.native_tls) .allow_insecure_host(network_settings.allow_insecure_host.clone()); @@ -946,6 +947,7 @@ impl ProjectInterpreter { } let client_builder = BaseClientBuilder::default() + .retries_from_env()? .connectivity(network_settings.connectivity) .native_tls(network_settings.native_tls) .allow_insecure_host(network_settings.allow_insecure_host.clone()); @@ -1656,6 +1658,8 @@ pub(crate) async fn resolve_names( } = settings; let client_builder = BaseClientBuilder::new() + .retries_from_env() + .map_err(uv_requirements::Error::ClientError)? .connectivity(network_settings.connectivity) .native_tls(network_settings.native_tls) .keyring(*keyring_provider) @@ -1813,6 +1817,7 @@ pub(crate) async fn resolve_environment( } = spec.requirements; let client_builder = BaseClientBuilder::new() + .retries_from_env()? .connectivity(network_settings.connectivity) .native_tls(network_settings.native_tls) .keyring(*keyring_provider) @@ -1984,6 +1989,7 @@ pub(crate) async fn sync_environment( } = settings; let client_builder = BaseClientBuilder::new() + .retries_from_env()? .connectivity(network_settings.connectivity) .native_tls(network_settings.native_tls) .keyring(keyring_provider) @@ -2147,6 +2153,7 @@ pub(crate) async fn update_environment( } = settings; let client_builder = BaseClientBuilder::new() + .retries_from_env()? .connectivity(network_settings.connectivity) .native_tls(network_settings.native_tls) .keyring(*keyring_provider) diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index f0a46f16a..3eece5432 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -618,6 +618,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl // If we're isolating the environment, use an ephemeral virtual environment as the // base environment for the project. let client_builder = BaseClientBuilder::new() + .retries_from_env()? .connectivity(network_settings.connectivity) .native_tls(network_settings.native_tls) .allow_insecure_host(network_settings.allow_insecure_host.clone()); @@ -859,6 +860,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl let interpreter = { let client_builder = BaseClientBuilder::new() + .retries_from_env()? .connectivity(network_settings.connectivity) .native_tls(network_settings.native_tls) .allow_insecure_host(network_settings.allow_insecure_host.clone()); @@ -929,6 +931,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl None } else { let client_builder = BaseClientBuilder::new() + .retries_from_env()? .connectivity(network_settings.connectivity) .native_tls(network_settings.native_tls) .allow_insecure_host(network_settings.allow_insecure_host.clone()); @@ -1526,6 +1529,7 @@ impl RunCommand { .tempfile()?; let client = BaseClientBuilder::new() + .retries_from_env()? .connectivity(network_settings.connectivity) .native_tls(network_settings.native_tls) .allow_insecure_host(network_settings.allow_insecure_host.clone()) diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index 664eb2a94..a9a161527 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -623,6 +623,7 @@ pub(super) async fn do_sync( } = settings; let client_builder = BaseClientBuilder::new() + .retries_from_env()? .connectivity(network_settings.connectivity) .native_tls(network_settings.native_tls) .keyring(keyring_provider) diff --git a/crates/uv/src/commands/project/tree.rs b/crates/uv/src/commands/project/tree.rs index d401940d9..cd1339d3e 100644 --- a/crates/uv/src/commands/project/tree.rs +++ b/crates/uv/src/commands/project/tree.rs @@ -215,6 +215,7 @@ pub(crate) async fn tree( let client = RegistryClientBuilder::new( cache.clone().with_refresh(Refresh::All(Timestamp::now())), ) + .retries_from_env()? .native_tls(network_settings.native_tls) .connectivity(network_settings.connectivity) .allow_insecure_host(network_settings.allow_insecure_host.clone()) diff --git a/crates/uv/src/commands/publish.rs b/crates/uv/src/commands/publish.rs index 63a0f2756..e7f5e00a2 100644 --- a/crates/uv/src/commands/publish.rs +++ b/crates/uv/src/commands/publish.rs @@ -95,6 +95,7 @@ pub(crate) async fn publish( false, ); let registry_client_builder = RegistryClientBuilder::new(cache.clone()) + .retries_from_env()? .native_tls(network_settings.native_tls) .connectivity(network_settings.connectivity) .allow_insecure_host(network_settings.allow_insecure_host.clone()) diff --git a/crates/uv/src/commands/python/install.rs b/crates/uv/src/commands/python/install.rs index 3df0cf91d..8c8387d07 100644 --- a/crates/uv/src/commands/python/install.rs +++ b/crates/uv/src/commands/python/install.rs @@ -376,6 +376,7 @@ pub(crate) async fn install( // Download and unpack the Python versions concurrently let client = uv_client::BaseClientBuilder::new() + .retries_from_env()? .connectivity(network_settings.connectivity) .native_tls(network_settings.native_tls) .allow_insecure_host(network_settings.allow_insecure_host.clone()) diff --git a/crates/uv/src/commands/python/pin.rs b/crates/uv/src/commands/python/pin.rs index 395981751..f4d10cdfa 100644 --- a/crates/uv/src/commands/python/pin.rs +++ b/crates/uv/src/commands/python/pin.rs @@ -107,6 +107,7 @@ pub(crate) async fn pin( } let client_builder = BaseClientBuilder::new() + .retries_from_env()? .connectivity(network_settings.connectivity) .native_tls(network_settings.native_tls) .allow_insecure_host(network_settings.allow_insecure_host.clone()); diff --git a/crates/uv/src/commands/tool/install.rs b/crates/uv/src/commands/tool/install.rs index 5ced211b3..27f18abe4 100644 --- a/crates/uv/src/commands/tool/install.rs +++ b/crates/uv/src/commands/tool/install.rs @@ -66,6 +66,7 @@ pub(crate) async fn install( preview: PreviewMode, ) -> Result { let client_builder = BaseClientBuilder::new() + .retries_from_env()? .connectivity(network_settings.connectivity) .native_tls(network_settings.native_tls) .allow_insecure_host(network_settings.allow_insecure_host.clone()); @@ -97,6 +98,7 @@ pub(crate) async fn install( let workspace_cache = WorkspaceCache::default(); let client_builder = BaseClientBuilder::new() + .retries_from_env()? .connectivity(network_settings.connectivity) .native_tls(network_settings.native_tls) .allow_insecure_host(network_settings.allow_insecure_host.clone()); diff --git a/crates/uv/src/commands/tool/run.rs b/crates/uv/src/commands/tool/run.rs index 2746d65ad..c8297243d 100644 --- a/crates/uv/src/commands/tool/run.rs +++ b/crates/uv/src/commands/tool/run.rs @@ -690,6 +690,7 @@ async fn get_or_create_environment( preview: PreviewMode, ) -> Result<(ToolRequirement, PythonEnvironment), ProjectError> { let client_builder = BaseClientBuilder::new() + .retries_from_env()? .connectivity(network_settings.connectivity) .native_tls(network_settings.native_tls) .allow_insecure_host(network_settings.allow_insecure_host.clone()); diff --git a/crates/uv/src/commands/tool/upgrade.rs b/crates/uv/src/commands/tool/upgrade.rs index 95b7d1e2d..9d2d32a21 100644 --- a/crates/uv/src/commands/tool/upgrade.rs +++ b/crates/uv/src/commands/tool/upgrade.rs @@ -80,6 +80,7 @@ pub(crate) async fn upgrade( let reporter = PythonDownloadReporter::single(printer); let client_builder = BaseClientBuilder::new() + .retries_from_env()? .connectivity(network_settings.connectivity) .native_tls(network_settings.native_tls) .allow_insecure_host(network_settings.allow_insecure_host.clone()); diff --git a/crates/uv/src/commands/venv.rs b/crates/uv/src/commands/venv.rs index 9334d844d..6d6e15758 100644 --- a/crates/uv/src/commands/venv.rs +++ b/crates/uv/src/commands/venv.rs @@ -193,6 +193,9 @@ async fn venv_impl( .unwrap_or(PathBuf::from(".venv")), ); + // TODO(zanieb): We don't use [`BaseClientBuilder::retries_from_env`] here because it's a pain + // to map into a miette diagnostic. We should just remove miette diagnostics here, we're not + // using them elsewhere. let client_builder = BaseClientBuilder::default() .connectivity(network_settings.connectivity) .native_tls(network_settings.native_tls) diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index f231198e4..f142beefa 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -499,6 +499,66 @@ fn install_package() { context.assert_command("import flask").success(); } +#[tokio::test] +async fn install_http_retries() { + let context = TestContext::new("3.12"); + + let server = MockServer::start().await; + + // Create a server that always fails, so we can see the number of retries used + Mock::given(method("GET")) + .respond_with(ResponseTemplate::new(503)) + .mount(&server) + .await; + + uv_snapshot!(context.filters(), context.pip_install() + .arg("anyio") + .arg("--index") + .arg(server.uri()) + .env(EnvVars::UV_HTTP_RETRIES, "foo"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Failed to parse `UV_HTTP_RETRIES` + Caused by: invalid digit found in string + " + ); + + uv_snapshot!(context.filters(), context.pip_install() + .arg("anyio") + .arg("--index") + .arg(server.uri()) + .env(EnvVars::UV_HTTP_RETRIES, "999999999999"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Failed to parse `UV_HTTP_RETRIES` + Caused by: number too large to fit in target type + " + ); + + uv_snapshot!(context.filters(), context.pip_install() + .arg("anyio") + .arg("--index") + .arg(server.uri()) + .env(EnvVars::UV_HTTP_RETRIES, "5") + .env(EnvVars::UV_TEST_NO_HTTP_RETRY_DELAY, "true"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Request failed after 5 retries + Caused by: Failed to fetch: `http://[LOCALHOST]/anyio/` + Caused by: HTTP status server error (503 Service Unavailable) for url (http://[LOCALHOST]/anyio/) + " + ); +} + /// Install a package from a `requirements.txt` into a virtual environment. #[test] fn install_requirements_txt() -> Result<()> { diff --git a/docs/reference/environment.md b/docs/reference/environment.md index 61889ddb3..bf8bf29ec 100644 --- a/docs/reference/environment.md +++ b/docs/reference/environment.md @@ -102,6 +102,10 @@ Equivalent to the `--token` argument for self update. A GitHub token for authent Enables fetching files stored in Git LFS when installing a package from a Git repository. +### `UV_HTTP_RETRIES` + +The number of retries for HTTP requests. (default: 3) + ### `UV_HTTP_TIMEOUT` Timeout (in seconds) for HTTP requests. (default: 30 s) @@ -416,6 +420,10 @@ WARNING: `UV_SYSTEM_PYTHON=true` is intended for use in continuous integration ( or containerized environments and should be used with caution, as modifying the system Python can lead to unexpected behavior. +### `UV_TEST_NO_HTTP_RETRY_DELAY` + +Used to disable delay for HTTP retries in tests. + ### `UV_TOOL_BIN_DIR` Specifies the "bin" directory for installing tool executables. From 567468ce72a3c7c1644cb061f9ed0d396118b2fb Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Fri, 11 Jul 2025 16:01:54 +0100 Subject: [PATCH 189/349] More efficient cache-key globbing + support parent paths in globs (#13469) ## Summary (Related PR: #13438 - would be nice to have it merged as well since it touches on the same globwalker code) There's a few issues with `cache-key` globs, which this PR attempts to address: - As of the current state, parent or absolute paths are not allowed, which is not obvious and is not documented. E.g., cache-key paths of the form `{file = "../dep/**"}` will be essentially ignored. - Absolute glob patterns also don't work (funnily enough, there's logic in `globwalk` itself that attempts to address it in [`globwalk::glob_builder()`](https://github.com/Gilnaa/globwalk/blob/8973fa2bc560be54c91448131238fa50d56ee121/src/lib.rs#L415), which serves as inspiration to some parts of this PR). - The reason for parent paths being ignored is the way globwalker is currently being triggered in `uv-cache-info`: the base directory is being walked over completely and each entry is then being matched to one of the provided match patterns. - This may also end up being very inefficient if you have a huge root folder with thousands of files: if your match patterns are `a/b/*.rs` and `a/c/*.py` then instead of walking over the root directory, you can just walk over `a/b` and `a/c` and match the relevant patterns there. - Why supporting parent paths may be important to the point of being a blocker: in large codebases with python projects depending on other local non-python projects (e.g. rust crates), cache-keys can be very useful to track dependency on the source code of the latter (e.g. `cache-keys = [{ file = "../../crates/some-dep/**" }]`. - TLDR: parent/absolute cache-key globs don't work, glob walk can be slow. ## Solution - In this PR, user-provided glob patterns are first clustered (LCP-style) into pattern groups with longest common path prefix; each of these groups can then be walked over separately. - Pattern groups do not overlap, so we would never walk over the same directory twice (unless there's symlinks pointing to same folders). - Paths are not canonicalized nor virtually normalized (which is impossible on Unix without FS access), so the method is symlink-safe (i.e. we don't treat `a/b/..` as `a`) and should work fine with #13438. - Because of LCP logic, the minimal amount of directory space will be traversed to cover all patterns. - Absolute glob patterns will now work. - Parent-relative glob patterns will now work. - Glob walking will be more efficient in some cases. ## Possible improvements - Efficiency can be further greatly improved if we limit max depth for globwalk. Currently, a simple ".toml" will deep-traverse the whole folder. Essentially, max depth can be always set to either N or infinity. If a pattern at a pivot node contains `**`, we collect all children nodes from the subtree into the same group and don't limit max depth; otherwise, we set max depth to the length of the glob pattern. This wouldn't change correctness though and can we done separately if needed. - If this is considered important enough, docs can be updated to indicate that parent and absolute globs are supported (and symlinks are resolved, if the relevant PR is algo merged in). ## Test Plan - Glob splitting and clustering tests are included in the PR. - Relative and absolute glob cache-keys were tested in an actual codebase. --- crates/uv-cache-info/src/cache_info.rs | 50 ++-- crates/uv-cache-info/src/glob.rs | 318 +++++++++++++++++++++++++ crates/uv-cache-info/src/lib.rs | 1 + 3 files changed, 347 insertions(+), 22 deletions(-) create mode 100644 crates/uv-cache-info/src/glob.rs diff --git a/crates/uv-cache-info/src/cache_info.rs b/crates/uv-cache-info/src/cache_info.rs index ce98cc513..27a98ab54 100644 --- a/crates/uv-cache-info/src/cache_info.rs +++ b/crates/uv-cache-info/src/cache_info.rs @@ -7,6 +7,7 @@ use serde::Deserialize; use tracing::{debug, warn}; use crate::git_info::{Commit, Tags}; +use crate::glob::cluster_globs; use crate::timestamp::Timestamp; #[derive(Debug, thiserror::Error)] @@ -212,34 +213,39 @@ impl CacheInfo { } } - // If we have any globs, process them in a single pass. + // If we have any globs, first cluster them using LCP and then do a single pass on each group. if !globs.is_empty() { - let walker = globwalk::GlobWalkerBuilder::from_patterns(directory, &globs) + for (glob_base, glob_patterns) in cluster_globs(&globs) { + let walker = globwalk::GlobWalkerBuilder::from_patterns( + directory.join(glob_base), + &glob_patterns, + ) .file_type(globwalk::FileType::FILE | globwalk::FileType::SYMLINK) .build()?; - for entry in walker { - let entry = match entry { - Ok(entry) => entry, - Err(err) => { - warn!("Failed to read glob entry: {err}"); + for entry in walker { + let entry = match entry { + Ok(entry) => entry, + Err(err) => { + warn!("Failed to read glob entry: {err}"); + continue; + } + }; + let metadata = match entry.metadata() { + Ok(metadata) => metadata, + Err(err) => { + warn!("Failed to read metadata for glob entry: {err}"); + continue; + } + }; + if !metadata.is_file() { + warn!( + "Expected file for cache key, but found directory: `{}`", + entry.path().display() + ); continue; } - }; - let metadata = match entry.metadata() { - Ok(metadata) => metadata, - Err(err) => { - warn!("Failed to read metadata for glob entry: {err}"); - continue; - } - }; - if !metadata.is_file() { - warn!( - "Expected file for cache key, but found directory: `{}`", - entry.path().display() - ); - continue; + timestamp = max(timestamp, Some(Timestamp::from_metadata(&metadata))); } - timestamp = max(timestamp, Some(Timestamp::from_metadata(&metadata))); } } diff --git a/crates/uv-cache-info/src/glob.rs b/crates/uv-cache-info/src/glob.rs new file mode 100644 index 000000000..e9c85897f --- /dev/null +++ b/crates/uv-cache-info/src/glob.rs @@ -0,0 +1,318 @@ +use std::{ + collections::BTreeMap, + path::{Component, Components, Path, PathBuf}, +}; + +/// Check if a component of the path looks like it may be a glob pattern. +/// +/// Note: this function is being used when splitting a glob pattern into a long possible +/// base and the glob remainder (scanning through components until we hit the first component +/// for which this function returns true). It is acceptable for this function to return +/// false positives (e.g. patterns like 'foo[bar' or 'foo{bar') in which case correctness +/// will not be affected but efficiency might be (because we'll traverse more than we should), +/// however it should not return false negatives. +fn is_glob_like(part: Component) -> bool { + matches!(part, Component::Normal(_)) + && part.as_os_str().to_str().is_some_and(|part| { + ["*", "{", "}", "?", "[", "]"] + .into_iter() + .any(|c| part.contains(c)) + }) +} + +#[derive(Debug, Default, Clone, PartialEq, Eq)] +struct GlobParts { + base: PathBuf, + pattern: PathBuf, +} + +/// Split a glob into longest possible base + shortest possible glob pattern. +fn split_glob(pattern: impl AsRef) -> GlobParts { + let pattern: &Path = pattern.as_ref().as_ref(); + + let mut glob = GlobParts::default(); + let mut globbing = false; + let mut last = None; + + for part in pattern.components() { + if let Some(last) = last { + if last != Component::CurDir { + if globbing { + glob.pattern.push(last); + } else { + glob.base.push(last); + } + } + } + if !globbing { + globbing = is_glob_like(part); + } + // we don't know if this part is the last one, defer handling it by one iteration + last = Some(part); + } + + if let Some(last) = last { + // defer handling the last component to prevent draining entire pattern into base + if globbing || matches!(last, Component::Normal(_)) { + glob.pattern.push(last); + } else { + glob.base.push(last); + } + } + glob +} + +/// Classic trie with edges being path components and values being glob patterns. +#[derive(Default)] +struct Trie<'a> { + children: BTreeMap, Trie<'a>>, + patterns: Vec<&'a Path>, +} + +impl<'a> Trie<'a> { + fn insert(&mut self, mut components: Components<'a>, pattern: &'a Path) { + if let Some(part) = components.next() { + self.children + .entry(part) + .or_default() + .insert(components, pattern); + } else { + self.patterns.push(pattern); + } + } + + #[allow(clippy::needless_pass_by_value)] + fn collect_patterns( + &self, + pattern_prefix: PathBuf, + group_prefix: PathBuf, + patterns: &mut Vec, + groups: &mut Vec<(PathBuf, Vec)>, + ) { + // collect all patterns beneath and including this node + for pattern in &self.patterns { + patterns.push(pattern_prefix.join(pattern)); + } + for (part, child) in &self.children { + if let Component::Normal(_) = part { + // for normal components, collect all descendant patterns ('normal' edges only) + child.collect_patterns( + pattern_prefix.join(part), + group_prefix.join(part), + patterns, + groups, + ); + } else { + // for non-normal component edges, kick off separate group collection at this node + child.collect_groups(group_prefix.join(part), groups); + } + } + } + + #[allow(clippy::needless_pass_by_value)] + fn collect_groups(&self, prefix: PathBuf, groups: &mut Vec<(PathBuf, Vec)>) { + // LCP-style grouping of patterns + if self.patterns.is_empty() { + // no patterns in this node; child nodes can form independent groups + for (part, child) in &self.children { + child.collect_groups(prefix.join(part), groups); + } + } else { + // pivot point, we've hit a pattern node; we have to stop here and form a group + let mut group = Vec::new(); + self.collect_patterns(PathBuf::new(), prefix.clone(), &mut group, groups); + groups.push((prefix, group)); + } + } +} + +/// Given a collection of globs, cluster them into (base, globs) groups so that: +/// - base doesn't contain any glob symbols +/// - each directory would only be walked at most once +/// - base of each group is the longest common prefix of globs in the group +pub(crate) fn cluster_globs(patterns: &[impl AsRef]) -> Vec<(PathBuf, Vec)> { + // split all globs into base/pattern + let globs: Vec<_> = patterns.iter().map(split_glob).collect(); + + // construct a path trie out of all split globs + let mut trie = Trie::default(); + for glob in &globs { + trie.insert(glob.base.components(), &glob.pattern); + } + + // run LCP-style aggregation of patterns in the trie into groups + let mut groups = Vec::new(); + trie.collect_groups(PathBuf::new(), &mut groups); + + // finally, convert resulting patterns to strings + groups + .into_iter() + .map(|(base, patterns)| { + ( + base, + patterns + .iter() + // NOTE: this unwrap is ok because input patterns are valid utf-8 + .map(|p| p.to_str().unwrap().to_owned()) + .collect(), + ) + }) + .collect() +} + +#[cfg(test)] +mod tests { + use super::{GlobParts, cluster_globs, split_glob}; + + fn windowsify(path: &str) -> String { + if cfg!(windows) { + path.replace('/', "\\") + } else { + path.to_owned() + } + } + + #[test] + fn test_split_glob() { + #[track_caller] + fn check(input: &str, base: &str, pattern: &str) { + let result = split_glob(input); + let expected = GlobParts { + base: base.into(), + pattern: pattern.into(), + }; + assert_eq!(result, expected, "{input:?} != {base:?} + {pattern:?}"); + } + + check("", "", ""); + check("a", "", "a"); + check("a/b", "a", "b"); + check("a/b/", "a", "b"); + check("a/.//b/", "a", "b"); + check("./a/b/c", "a/b", "c"); + check("c/d/*", "c/d", "*"); + check("c/d/*/../*", "c/d", "*/../*"); + check("a/?b/c", "a", "?b/c"); + check("/a/b/*", "/a/b", "*"); + check("../x/*", "../x", "*"); + check("a/{b,c}/d", "a", "{b,c}/d"); + check("a/[bc]/d", "a", "[bc]/d"); + check("*", "", "*"); + check("*/*", "", "*/*"); + check("..", "..", ""); + check("/", "/", ""); + } + + #[test] + fn test_cluster_globs() { + #[track_caller] + fn check(input: &[&str], expected: &[(&str, &[&str])]) { + let input = input.iter().map(|s| windowsify(s)).collect::>(); + + let mut result_sorted = cluster_globs(&input); + for (_, patterns) in &mut result_sorted { + patterns.sort_unstable(); + } + result_sorted.sort_unstable(); + + let mut expected_sorted = Vec::new(); + for (base, patterns) in expected { + let mut patterns_sorted = Vec::new(); + for pattern in *patterns { + patterns_sorted.push(windowsify(pattern)); + } + patterns_sorted.sort_unstable(); + expected_sorted.push((windowsify(base).into(), patterns_sorted)); + } + expected_sorted.sort_unstable(); + + assert_eq!( + result_sorted, expected_sorted, + "{input:?} != {expected_sorted:?} (got: {result_sorted:?})" + ); + } + + check(&["a/b/*", "a/c/*"], &[("a/b", &["*"]), ("a/c", &["*"])]); + check(&["./a/b/*", "a/c/*"], &[("a/b", &["*"]), ("a/c", &["*"])]); + check(&["/a/b/*", "/a/c/*"], &[("/a/b", &["*"]), ("/a/c", &["*"])]); + check( + &["../a/b/*", "../a/c/*"], + &[("../a/b", &["*"]), ("../a/c", &["*"])], + ); + check(&["x/*", "y/*"], &[("x", &["*"]), ("y", &["*"])]); + check(&[], &[]); + check( + &["./*", "a/*", "../foo/*.png"], + &[("", &["*", "a/*"]), ("../foo", &["*.png"])], + ); + check( + &[ + "?", + "/foo/?", + "/foo/bar/*", + "../bar/*.png", + "../bar/../baz/*.jpg", + ], + &[ + ("", &["?"]), + ("/foo", &["?", "bar/*"]), + ("../bar", &["*.png"]), + ("../bar/../baz", &["*.jpg"]), + ], + ); + check(&["/abs/path/*"], &[("/abs/path", &["*"])]); + check(&["/abs/*", "rel/*"], &[("/abs", &["*"]), ("rel", &["*"])]); + check(&["a/{b,c}/*", "a/d?/*"], &[("a", &["{b,c}/*", "d?/*"])]); + check( + &[ + "../shared/a/[abc].png", + "../shared/a/b/*", + "../shared/b/c/?x/d", + "docs/important/*.{doc,xls}", + "docs/important/very/*", + ], + &[ + ("../shared/a", &["[abc].png", "b/*"]), + ("../shared/b/c", &["?x/d"]), + ("docs/important", &["*.{doc,xls}", "very/*"]), + ], + ); + check(&["file.txt"], &[("", &["file.txt"])]); + check(&["/"], &[("/", &[""])]); + check(&[".."], &[("..", &[""])]); + check( + &["file1.txt", "file2.txt"], + &[("", &["file1.txt", "file2.txt"])], + ); + check( + &["a/file1.txt", "a/file2.txt"], + &[("a", &["file1.txt", "file2.txt"])], + ); + check( + &["*", "a/b/*", "a/../c/*.jpg", "a/../c/*.png", "/a/*", "/b/*"], + &[ + ("", &["*", "a/b/*"]), + ("a/../c", &["*.jpg", "*.png"]), + ("/a", &["*"]), + ("/b", &["*"]), + ], + ); + + if cfg!(windows) { + check( + &[ + r"\\foo\bar\shared/a/[abc].png", + r"\\foo\bar\shared/a/b/*", + r"\\foo\bar/shared/b/c/?x/d", + r"D:\docs\important/*.{doc,xls}", + r"D:\docs/important/very/*", + ], + &[ + (r"\\foo\bar\shared\a", &["[abc].png", r"b\*"]), + (r"\\foo\bar\shared\b\c", &[r"?x\d"]), + (r"D:\docs\important", &["*.{doc,xls}", r"very\*"]), + ], + ); + } + } +} diff --git a/crates/uv-cache-info/src/lib.rs b/crates/uv-cache-info/src/lib.rs index 286411f68..092d40652 100644 --- a/crates/uv-cache-info/src/lib.rs +++ b/crates/uv-cache-info/src/lib.rs @@ -3,4 +3,5 @@ pub use crate::timestamp::*; mod cache_info; mod git_info; +mod glob; mod timestamp; From 088a436efe34ec517d5762545a753ab9424278d8 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Fri, 11 Jul 2025 11:45:45 -0500 Subject: [PATCH 190/349] Move `run_to_completion` utility to `crate::child` instead of `crate::commands::run` (#14566) This was really confusing as everything else in the `commands` module is a command --- crates/uv/src/{commands/run.rs => child.rs} | 0 crates/uv/src/commands/mod.rs | 1 - crates/uv/src/commands/project/run.rs | 2 +- crates/uv/src/commands/tool/run.rs | 2 +- crates/uv/src/lib.rs | 1 + 5 files changed, 3 insertions(+), 3 deletions(-) rename crates/uv/src/{commands/run.rs => child.rs} (100%) diff --git a/crates/uv/src/commands/run.rs b/crates/uv/src/child.rs similarity index 100% rename from crates/uv/src/commands/run.rs rename to crates/uv/src/child.rs diff --git a/crates/uv/src/commands/mod.rs b/crates/uv/src/commands/mod.rs index 0203d4dd5..d1e647363 100644 --- a/crates/uv/src/commands/mod.rs +++ b/crates/uv/src/commands/mod.rs @@ -72,7 +72,6 @@ mod project; mod publish; mod python; pub(crate) mod reporters; -mod run; #[cfg(feature = "self-update")] mod self_update; mod tool; diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index 3eece5432..63850f563 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -40,6 +40,7 @@ use uv_static::EnvVars; use uv_warnings::warn_user; use uv_workspace::{DiscoveryOptions, VirtualProject, Workspace, WorkspaceCache, WorkspaceError}; +use crate::child::run_to_completion; use crate::commands::pip::loggers::{ DefaultInstallLogger, DefaultResolveLogger, SummaryInstallLogger, SummaryResolveLogger, }; @@ -55,7 +56,6 @@ use crate::commands::project::{ validate_project_requires_python, }; use crate::commands::reporters::PythonDownloadReporter; -use crate::commands::run::run_to_completion; use crate::commands::{ExitStatus, diagnostics, project}; use crate::printer::Printer; use crate::settings::{NetworkSettings, ResolverInstallerSettings}; diff --git a/crates/uv/src/commands/tool/run.rs b/crates/uv/src/commands/tool/run.rs index c8297243d..f6b79774c 100644 --- a/crates/uv/src/commands/tool/run.rs +++ b/crates/uv/src/commands/tool/run.rs @@ -42,6 +42,7 @@ use uv_warnings::warn_user; use uv_warnings::warn_user_once; use uv_workspace::WorkspaceCache; +use crate::child::run_to_completion; use crate::commands::ExitStatus; use crate::commands::pip::loggers::{ DefaultInstallLogger, DefaultResolveLogger, SummaryInstallLogger, SummaryResolveLogger, @@ -51,7 +52,6 @@ use crate::commands::project::{ EnvironmentSpecification, PlatformState, ProjectError, resolve_names, }; use crate::commands::reporters::PythonDownloadReporter; -use crate::commands::run::run_to_completion; use crate::commands::tool::common::{matching_packages, refine_interpreter}; use crate::commands::tool::{Target, ToolRequest}; use crate::commands::{diagnostics, project::environment::CachedEnvironment}; diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 261dd8d7c..84d889599 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -52,6 +52,7 @@ use crate::settings::{ PublishSettings, }; +pub(crate) mod child; pub(crate) mod commands; pub(crate) mod logging; pub(crate) mod printer; From a9e21f7f6b26e4ad27718a35efb53d7cda490e69 Mon Sep 17 00:00:00 2001 From: dmitry-bychkov Date: Fri, 11 Jul 2025 20:05:15 +0300 Subject: [PATCH 191/349] Update CONTRIBUTING.md with instructions to format markdown files (#14246) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Current documentation requires contributors to have Node.js/npm installed locally to format Markdown files. This might be problematic for users who don't work with JavaScript ecosystem or users who want to avoid toolchain setup. This change adds docker-based alternative: ``` docker run --rm -v .:/src/ -w /src/ node:alpine npx prettier --prose-wrap always --write "**/*.md" ``` Which mounts current working directory into /src/ inside of a container and also sets working directory (-w) to /src/ so prettier loads .editorconfig. ## Test Plan Both commands should produce the same output
    Native Prettier ```console ➜ uv git:(docs/contributing-md-formatting) npx prettier --prose-wrap always --write "**/*.md" .github/PULL_REQUEST_TEMPLATE.md 28ms (unchanged) BENCHMARKS.md 30ms (unchanged) changelogs/0.1.x.md 264ms (unchanged) changelogs/0.2.x.md 223ms (unchanged) changelogs/0.3.x.md 29ms (unchanged) changelogs/0.4.x.md 126ms (unchanged) changelogs/0.5.x.md 153ms (unchanged) changelogs/0.6.x.md 77ms (unchanged) CONTRIBUTING.md 9ms (unchanged) crates/README.md 4ms (unchanged) crates/uv-build/README.md 1ms (unchanged) crates/uv-client/README.md 1ms (unchanged) crates/uv-globfilter/README.md 3ms (unchanged) crates/uv-pep440/Readme.md 6ms (unchanged) crates/uv-pep508/Readme.md 3ms (unchanged) crates/uv-python/python/packaging/README.md 1ms (unchanged) crates/uv-trampoline/README.md 14ms (unchanged) crates/uv-virtualenv/README.md 1ms (unchanged) docs/concepts/authentication.md 10ms (unchanged) docs/concepts/build-backend.md 11ms (unchanged) docs/concepts/cache.md 17ms (unchanged) docs/concepts/configuration-files.md 9ms (unchanged) docs/concepts/index.md 2ms (unchanged) docs/concepts/indexes.md 22ms (unchanged) docs/concepts/projects/build.md 4ms (unchanged) docs/concepts/projects/config.md 25ms (unchanged) docs/concepts/projects/dependencies.md 29ms (unchanged) docs/concepts/projects/index.md 2ms (unchanged) docs/concepts/projects/init.md 10ms (unchanged) docs/concepts/projects/layout.md 10ms (unchanged) docs/concepts/projects/run.md 4ms (unchanged) docs/concepts/projects/sync.md 11ms (unchanged) docs/concepts/projects/workspaces.md 12ms (unchanged) docs/concepts/python-versions.md 26ms (unchanged) docs/concepts/resolution.md 40ms (unchanged) docs/concepts/tools.md 19ms (unchanged) docs/getting-started/features.md 8ms (unchanged) docs/getting-started/first-steps.md 2ms (unchanged) docs/getting-started/help.md 8ms (unchanged) docs/getting-started/index.md 2ms (unchanged) docs/getting-started/installation.md 8ms (unchanged) docs/guides/index.md 2ms (unchanged) docs/guides/install-python.md 31ms (unchanged) docs/guides/integration/alternative-indexes.md 21ms (unchanged) docs/guides/integration/aws-lambda.md 49ms (unchanged) docs/guides/integration/dependency-bots.md 16ms (unchanged) docs/guides/integration/docker.md 37ms (unchanged) docs/guides/integration/fastapi.md 8ms (unchanged) docs/guides/integration/github.md 36ms (unchanged) docs/guides/integration/index.md 4ms (unchanged) docs/guides/integration/jupyter.md 17ms (unchanged) docs/guides/integration/marimo.md 11ms (unchanged) docs/guides/integration/pre-commit.md 27ms (unchanged) docs/guides/integration/pytorch.md 12ms (unchanged) docs/guides/package.md 5ms (unchanged) docs/guides/projects.md 12ms (unchanged) docs/guides/scripts.md 19ms (unchanged) docs/guides/tools.md 8ms (unchanged) docs/index.md 7ms (unchanged) docs/pip/compatibility.md 44ms (unchanged) docs/pip/compile.md 13ms (unchanged) docs/pip/dependencies.md 3ms (unchanged) docs/pip/environments.md 10ms (unchanged) docs/pip/index.md 2ms (unchanged) docs/pip/inspection.md 1ms (unchanged) docs/pip/packages.md 3ms (unchanged) docs/reference/benchmarks.md 3ms (unchanged) docs/reference/index.md 3ms (unchanged) docs/reference/installer.md 2ms (unchanged) docs/reference/policies/index.md 2ms (unchanged) docs/reference/policies/license.md 2ms (unchanged) docs/reference/policies/platforms.md 4ms (unchanged) docs/reference/policies/versioning.md 2ms (unchanged) docs/reference/resolver-internals.md 19ms (unchanged) docs/reference/troubleshooting/build-failures.md 13ms (unchanged) docs/reference/troubleshooting/index.md 1ms (unchanged) docs/reference/troubleshooting/reproducible-examples.md 7ms (unchanged) PIP_COMPATIBILITY.md 1ms (unchanged) README.md 10ms (unchanged) scripts/benchmark/README.md 1ms (unchanged) scripts/packages/built-by-uv/README.md 1ms (unchanged) scripts/packages/dependent_locals/first_local/README.md 0ms (unchanged) scripts/packages/dependent_locals/second_local/README.md 0ms (unchanged) scripts/packages/hatchling_editable/README.md 0ms (unchanged) scripts/packages/README.md 1ms (unchanged) scripts/packages/root_editable/README.md 0ms (unchanged) scripts/workspaces/albatross-virtual-workspace/packages/Unrelated.md 1ms (unchanged) SECURITY.md 2ms (unchanged) STYLE.md 9ms (unchanged) ➜ uv git:(docs/contributing-md-formatting) git status On branch docs/contributing-md-formatting nothing to commit, working tree clean ➜ uv git:(docs/contributing-md-formatting) ```
    Docker based ```console ➜ uv git:(docs/contributing-md-formatting) sudo docker run --rm -v .:/src/ -w /src/ node:alpine npx prettier --prose-wrap always --write "**/*.md" npm warn exec The following package was not found and will be installed: prettier@3.6.0 .github/PULL_REQUEST_TEMPLATE.md 54ms (unchanged) BENCHMARKS.md 41ms (unchanged) changelogs/0.1.x.md 297ms (unchanged) changelogs/0.2.x.md 306ms (unchanged) changelogs/0.3.x.md 50ms (unchanged) changelogs/0.4.x.md 137ms (unchanged) changelogs/0.5.x.md 217ms (unchanged) changelogs/0.6.x.md 114ms (unchanged) CONTRIBUTING.md 12ms (unchanged) crates/README.md 8ms (unchanged) crates/uv-build/README.md 2ms (unchanged) crates/uv-client/README.md 2ms (unchanged) crates/uv-globfilter/README.md 6ms (unchanged) crates/uv-pep440/Readme.md 8ms (unchanged) crates/uv-pep508/Readme.md 5ms (unchanged) crates/uv-python/python/packaging/README.md 2ms (unchanged) crates/uv-trampoline/README.md 17ms (unchanged) crates/uv-virtualenv/README.md 2ms (unchanged) docs/concepts/authentication.md 20ms (unchanged) docs/concepts/build-backend.md 20ms (unchanged) docs/concepts/cache.md 35ms (unchanged) docs/concepts/configuration-files.md 11ms (unchanged) docs/concepts/index.md 3ms (unchanged) docs/concepts/indexes.md 24ms (unchanged) docs/concepts/projects/build.md 5ms (unchanged) docs/concepts/projects/config.md 25ms (unchanged) docs/concepts/projects/dependencies.md 38ms (unchanged) docs/concepts/projects/index.md 3ms (unchanged) docs/concepts/projects/init.md 15ms (unchanged) docs/concepts/projects/layout.md 11ms (unchanged) docs/concepts/projects/run.md 7ms (unchanged) docs/concepts/projects/sync.md 15ms (unchanged) docs/concepts/projects/workspaces.md 15ms (unchanged) docs/concepts/python-versions.md 30ms (unchanged) docs/concepts/resolution.md 52ms (unchanged) docs/concepts/tools.md 20ms (unchanged) docs/getting-started/features.md 10ms (unchanged) docs/getting-started/first-steps.md 2ms (unchanged) docs/getting-started/help.md 5ms (unchanged) docs/getting-started/index.md 3ms (unchanged) docs/getting-started/installation.md 8ms (unchanged) docs/guides/index.md 2ms (unchanged) docs/guides/install-python.md 49ms (unchanged) docs/guides/integration/alternative-indexes.md 29ms (unchanged) docs/guides/integration/aws-lambda.md 102ms (unchanged) docs/guides/integration/dependency-bots.md 20ms (unchanged) docs/guides/integration/docker.md 38ms (unchanged) docs/guides/integration/fastapi.md 7ms (unchanged) docs/guides/integration/github.md 46ms (unchanged) docs/guides/integration/index.md 3ms (unchanged) docs/guides/integration/jupyter.md 16ms (unchanged) docs/guides/integration/marimo.md 6ms (unchanged) docs/guides/integration/pre-commit.md 14ms (unchanged) docs/guides/integration/pytorch.md 18ms (unchanged) docs/guides/package.md 9ms (unchanged) docs/guides/projects.md 11ms (unchanged) docs/guides/scripts.md 13ms (unchanged) docs/guides/tools.md 13ms (unchanged) docs/index.md 11ms (unchanged) docs/pip/compatibility.md 40ms (unchanged) docs/pip/compile.md 12ms (unchanged) docs/pip/dependencies.md 4ms (unchanged) docs/pip/environments.md 10ms (unchanged) docs/pip/index.md 4ms (unchanged) docs/pip/inspection.md 2ms (unchanged) docs/pip/packages.md 5ms (unchanged) docs/reference/benchmarks.md 2ms (unchanged) docs/reference/index.md 3ms (unchanged) docs/reference/installer.md 3ms (unchanged) docs/reference/policies/index.md 1ms (unchanged) docs/reference/policies/license.md 3ms (unchanged) docs/reference/policies/platforms.md 5ms (unchanged) docs/reference/policies/versioning.md 4ms (unchanged) docs/reference/resolver-internals.md 29ms (unchanged) docs/reference/troubleshooting/build-failures.md 19ms (unchanged) docs/reference/troubleshooting/index.md 2ms (unchanged) docs/reference/troubleshooting/reproducible-examples.md 9ms (unchanged) PIP_COMPATIBILITY.md 1ms (unchanged) README.md 15ms (unchanged) scripts/benchmark/README.md 1ms (unchanged) scripts/packages/built-by-uv/README.md 1ms (unchanged) scripts/packages/dependent_locals/first_local/README.md 0ms (unchanged) scripts/packages/dependent_locals/second_local/README.md 0ms (unchanged) scripts/packages/hatchling_editable/README.md 1ms (unchanged) scripts/packages/README.md 1ms (unchanged) scripts/packages/root_editable/README.md 0ms (unchanged) scripts/workspaces/albatross-virtual-workspace/packages/Unrelated.md 2ms (unchanged) SECURITY.md 3ms (unchanged) STYLE.md 16ms (unchanged) npm notice npm notice New minor version of npm available! 11.3.0 -> 11.4.2 npm notice Changelog: https://github.com/npm/cli/releases/tag/v11.4.2 npm notice To update run: npm install -g npm@11.4.2 npm notice ➜ uv git:(docs/contributing-md-formatting) git status On branch docs/contributing-md-formatting nothing to commit, working tree clean ➜ uv git:(docs/contributing-md-formatting) ```
    Co-authored-by: Dmitry Bychkov --- CONTRIBUTING.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 14b5197fe..f7be958a4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -165,6 +165,13 @@ After making changes to the documentation, format the markdown files with: npx prettier --prose-wrap always --write "**/*.md" ``` +Note that the command above requires Node.js and npm to be installed on your system. As an +alternative, you can run this command using Docker: + +```console +$ docker run --rm -v .:/src/ -w /src/ node:alpine npx prettier --prose-wrap always --write "**/*.md" +``` + ## Releases Releases can only be performed by Astral team members. From 081e2010df63c561bdf56f5d6e34b102dd035d94 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Fri, 11 Jul 2025 12:13:35 -0500 Subject: [PATCH 192/349] Isolate `install_git_public_rate_limited...` test from `UV_HTTP_RETRIES` (#14567) Blocking https://github.com/astral-sh/uv/pull/14565 This also makes the test 5x faster, from 5s to 1s. --- crates/uv/tests/it/edit.rs | 4 +++- crates/uv/tests/it/pip_install.rs | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/uv/tests/it/edit.rs b/crates/uv/tests/it/edit.rs index c1a74541f..18170cff9 100644 --- a/crates/uv/tests/it/edit.rs +++ b/crates/uv/tests/it/edit.rs @@ -561,7 +561,9 @@ async fn add_git_private_rate_limited_by_github_rest_api_429_response() -> Resul uv_snapshot!(context.filters(), context .add() .arg(format!("uv-private-pypackage @ git+https://{token}@github.com/astral-test/uv-private-pypackage")) - .env("UV_GITHUB_FAST_PATH_URL", server.uri()), @r" + .env(EnvVars::UV_GITHUB_FAST_PATH_URL, server.uri()) + .env(EnvVars::UV_TEST_NO_HTTP_RETRY_DELAY, "true") + .env_remove(EnvVars::UV_HTTP_RETRIES), @r" success: true exit_code: 0 ----- stdout ----- diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index f142beefa..bc27228c7 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -2168,7 +2168,9 @@ async fn install_git_public_rate_limited_by_github_rest_api_429_response() { uv_snapshot!(context.filters(), context .pip_install() .arg("uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage") - .env("UV_GITHUB_FAST_PATH_URL", server.uri()), @r" + .env(EnvVars::UV_GITHUB_FAST_PATH_URL, server.uri()) + .env(EnvVars::UV_TEST_NO_HTTP_RETRY_DELAY, "true") + .env_remove(EnvVars::UV_HTTP_RETRIES), @r" success: true exit_code: 0 ----- stdout ----- From ee35fe34ab90adb99505581daeb63edd5c3f827f Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Fri, 11 Jul 2025 13:59:47 -0500 Subject: [PATCH 193/349] Increase the number of retries during test runs in CI (#14565) --- .github/workflows/ci.yml | 8 ++++++++ crates/uv/tests/it/edit.rs | 4 +++- crates/uv/tests/it/network.rs | 33 +++++++++++++++++++++++++-------- 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b0d8e18a3..ba7a4b4d1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -223,6 +223,9 @@ jobs: tool: cargo-nextest - name: "Cargo test" + env: + # Retry more than default to reduce flakes in CI + UV_HTTP_RETRIES: 5 run: | cargo nextest run \ --features python-patch \ @@ -256,6 +259,9 @@ jobs: tool: cargo-nextest - name: "Cargo test" + env: + # Retry more than default to reduce flakes in CI + UV_HTTP_RETRIES: 5 run: | cargo nextest run \ --no-default-features \ @@ -300,6 +306,8 @@ jobs: - name: "Cargo test" working-directory: ${{ env.UV_WORKSPACE }} env: + # Retry more than default to reduce flakes in CI + UV_HTTP_RETRIES: 5 # Avoid permission errors during concurrent tests # See https://github.com/astral-sh/uv/issues/6940 UV_LINK_MODE: copy diff --git a/crates/uv/tests/it/edit.rs b/crates/uv/tests/it/edit.rs index 18170cff9..ddaed434f 100644 --- a/crates/uv/tests/it/edit.rs +++ b/crates/uv/tests/it/edit.rs @@ -11877,7 +11877,9 @@ async fn add_unexpected_error_code() -> Result<()> { "# })?; - uv_snapshot!(context.filters(), context.add().arg("anyio").arg("--index").arg(server.uri()), @r" + uv_snapshot!(context.filters(), context.add().arg("anyio").arg("--index").arg(server.uri()) + .env_remove(EnvVars::UV_HTTP_RETRIES) + .env(EnvVars::UV_TEST_NO_HTTP_RETRY_DELAY, "true"), @r" success: false exit_code: 2 ----- stdout ----- diff --git a/crates/uv/tests/it/network.rs b/crates/uv/tests/it/network.rs index 1a5805970..a9376e07e 100644 --- a/crates/uv/tests/it/network.rs +++ b/crates/uv/tests/it/network.rs @@ -3,6 +3,7 @@ use std::{env, io}; use assert_fs::fixture::{ChildPath, FileWriteStr, PathChild}; use http::StatusCode; use serde_json::json; +use uv_static::EnvVars; use wiremock::matchers::method; use wiremock::{Mock, MockServer, ResponseTemplate}; @@ -48,7 +49,9 @@ async fn simple_http_500() { .pip_install() .arg("tqdm") .arg("--index-url") - .arg(&mock_server_uri), @r" + .arg(&mock_server_uri) + .env_remove(EnvVars::UV_HTTP_RETRIES) + .env(EnvVars::UV_TEST_NO_HTTP_RETRY_DELAY, "true"), @r" success: false exit_code: 2 ----- stdout ----- @@ -72,7 +75,9 @@ async fn simple_io_err() { .pip_install() .arg("tqdm") .arg("--index-url") - .arg(&mock_server_uri), @r" + .arg(&mock_server_uri) + .env_remove(EnvVars::UV_HTTP_RETRIES) + .env(EnvVars::UV_TEST_NO_HTTP_RETRY_DELAY, "true"), @r" success: false exit_code: 2 ----- stdout ----- @@ -99,7 +104,9 @@ async fn find_links_http_500() { .arg("tqdm") .arg("--no-index") .arg("--find-links") - .arg(&mock_server_uri), @r" + .arg(&mock_server_uri) + .env_remove(EnvVars::UV_HTTP_RETRIES) + .env(EnvVars::UV_TEST_NO_HTTP_RETRY_DELAY, "true"), @r" success: false exit_code: 2 ----- stdout ----- @@ -125,7 +132,9 @@ async fn find_links_io_error() { .arg("tqdm") .arg("--no-index") .arg("--find-links") - .arg(&mock_server_uri), @r" + .arg(&mock_server_uri) + .env_remove(EnvVars::UV_HTTP_RETRIES) + .env(EnvVars::UV_TEST_NO_HTTP_RETRY_DELAY, "true"), @r" success: false exit_code: 2 ----- stdout ----- @@ -154,7 +163,9 @@ async fn direct_url_http_500() { let filters = vec![(mock_server_uri.as_str(), "[SERVER]")]; uv_snapshot!(filters, context .pip_install() - .arg(format!("tqdm @ {tqdm_url}")), @r" + .arg(format!("tqdm @ {tqdm_url}")) + .env_remove(EnvVars::UV_HTTP_RETRIES) + .env(EnvVars::UV_TEST_NO_HTTP_RETRY_DELAY, "true"), @r" success: false exit_code: 1 ----- stdout ----- @@ -180,7 +191,9 @@ async fn direct_url_io_error() { let filters = vec![(mock_server_uri.as_str(), "[SERVER]")]; uv_snapshot!(filters, context .pip_install() - .arg(format!("tqdm @ {tqdm_url}")), @r" + .arg(format!("tqdm @ {tqdm_url}")) + .env_remove(EnvVars::UV_HTTP_RETRIES) + .env(EnvVars::UV_TEST_NO_HTTP_RETRY_DELAY, "true"), @r" success: false exit_code: 1 ----- stdout ----- @@ -239,7 +252,9 @@ async fn python_install_http_500() { .python_install() .arg("cpython-3.10.0-darwin-aarch64-none") .arg("--python-downloads-json-url") - .arg(python_downloads_json.path()), @r" + .arg(python_downloads_json.path()) + .env_remove(EnvVars::UV_HTTP_RETRIES) + .env(EnvVars::UV_TEST_NO_HTTP_RETRY_DELAY, "true"), @r" success: false exit_code: 1 ----- stdout ----- @@ -269,7 +284,9 @@ async fn python_install_io_error() { .python_install() .arg("cpython-3.10.0-darwin-aarch64-none") .arg("--python-downloads-json-url") - .arg(python_downloads_json.path()), @r" + .arg(python_downloads_json.path()) + .env_remove(EnvVars::UV_HTTP_RETRIES) + .env(EnvVars::UV_TEST_NO_HTTP_RETRY_DELAY, "true"), @r" success: false exit_code: 1 ----- stdout ----- From 7ea030a1a854680001205037ae0959c1750365a2 Mon Sep 17 00:00:00 2001 From: Geoffrey Thomas Date: Sat, 12 Jul 2025 12:46:40 -0400 Subject: [PATCH 194/349] Bump Python releases to pick up python-build-standalone 20250712 (#14578) This is primarily a regression fix for missing SQLite extensions (astral-sh/python-build-standalone#694). --- crates/uv-python/download-metadata.json | 936 ++++++++++++------------ 1 file changed, 468 insertions(+), 468 deletions(-) diff --git a/crates/uv-python/download-metadata.json b/crates/uv-python/download-metadata.json index 4e2d98846..8c7ffec4c 100644 --- a/crates/uv-python/download-metadata.json +++ b/crates/uv-python/download-metadata.json @@ -11,8 +11,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "7a69c986243f4e7ed70c1a97d4a524253d3fb4f042ae68eb688f9fafe5dbb714", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "94b80254a7e50dd2d82d323a0bffdc59772b2f04b0f0c044bc4d56d696249eb2", "variant": null }, "cpython-3.14.0b4-darwin-x86_64-none": { @@ -27,8 +27,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "8c100fe3bfef08b046051c4183c9ca4542317729c466982783fabea996fcb97f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "2155f60b2a8a1448b2c4852a27887be2e9fe8e910bac1a75b342e44884a191b5", "variant": null }, "cpython-3.14.0b4-linux-aarch64-gnu": { @@ -43,8 +43,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "930e8ecf6c89de145cf49171d98e089af7007752e8e7652c1ea73460fec0d07c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "f76fb1a88e722f9cae8b82b9851b736968582527d8a1212ab3b918b2012ce0a6", "variant": null }, "cpython-3.14.0b4-linux-armv7-gnueabi": { @@ -59,8 +59,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "5b489148c56a0a9772568706cf6c716e14b1d93e52f54d76f71f14783f659d13", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "c358e87ac84d228e191a22d2447c60e1cb15e6cbb753c397b0e9b9da9c557ce0", "variant": null }, "cpython-3.14.0b4-linux-armv7-gnueabihf": { @@ -75,8 +75,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "2b4474ebc495b64374339acf58d22793f8f55ce1a40e31d61a988af7cf2c8085", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "a426e05b3d8a20dfbda84162ef75ed3590e7137436623b93d136c084d0688690", "variant": null }, "cpython-3.14.0b4-linux-powerpc64le-gnu": { @@ -91,8 +91,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "abc24237c270f248b5b2990091209a60c23d5bef8476796cf5b0c16c34a24e54", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "b835aac7264b64652007f5210369d5fe1b8d1629befbb8d00e40a891cd039f67", "variant": null }, "cpython-3.14.0b4-linux-riscv64-gnu": { @@ -107,8 +107,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "fd25c2de82d3ea004831c543591195f3790c93d5df7f5f1a39b0e5f9e1716039", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "0ad96a96ae32f5979f2bd9e6992ecf122819ceb06711439c66b9f8a3dc1eaba4", "variant": null }, "cpython-3.14.0b4-linux-s390x-gnu": { @@ -123,8 +123,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "35f93fd3336dcfd2612fb2945937221f81af9a65369efb81afa1d89784029e61", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "18763ccce35baeb1960e043f9bd4be3a36a511acc6844b91381532ee5b7c6da8", "variant": null }, "cpython-3.14.0b4-linux-x86_64-gnu": { @@ -139,8 +139,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "a76999ca5b8c6e219750b016870fc85cc395dd992de1d702576d1c831585aa95", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "3d07868b329c7c9b7ae5a52af35c27d0b20b5a7f6f574a3bedb5836b4bb337d7", "variant": null }, "cpython-3.14.0b4-linux-x86_64-musl": { @@ -155,8 +155,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "a8f12323bd6c10f1ecadbe424e64c2429434e59e69314966a422c9a7eb5f13a0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "82ee7827c1f75a7b5150f731ddf1dc312c7958c741a6746967fb8a5656c85b91", "variant": null }, "cpython-3.14.0b4-linux-x86_64_v2-gnu": { @@ -171,8 +171,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "64649a18cee348ba72b42ec46aa548dca3d79ed37a2abeea17f5b5fea4ad67b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "c96dd14927c89392bd0ff3264e4b7bdfeea76979f544ee30260151c913046396", "variant": null }, "cpython-3.14.0b4-linux-x86_64_v2-musl": { @@ -187,8 +187,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "352b97d9c5634787cdfe11b00a4ac83e0a254f70dc2887780fa93b52a8cdbec8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "ae82acb77c69c506a799bd7022fe9a22508814fe76d0d7e53c1f2f60b5fc77d6", "variant": null }, "cpython-3.14.0b4-linux-x86_64_v3-gnu": { @@ -203,8 +203,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "d780f46da4c2ae2400cb08c6e5900d976d46572c1fb2dc6a9494a4c309f913f2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "9fdb71600bbdcae5dd47426972d1d0af03a2f7d98ac44fbb63284203738fda2c", "variant": null }, "cpython-3.14.0b4-linux-x86_64_v3-musl": { @@ -219,8 +219,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "4ef7c85e6a6788f1838a80a23463ee36fdfd50c909c784bc6ed7011725220288", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "f864428b9b6b5938efeb93526d52ec685377672ad292e4b2eee62cb6107933e1", "variant": null }, "cpython-3.14.0b4-linux-x86_64_v4-gnu": { @@ -235,8 +235,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "cd91301114d7ebfcfccbb3377a09c8d8537dc460de629ec6e64d3880aeb7ab0c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "0d3f7f0c8b881bcdff08d14a0999c736f13e309e663edd0739a2db327c43e4c2", "variant": null }, "cpython-3.14.0b4-linux-x86_64_v4-musl": { @@ -251,8 +251,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "ff8cba3869c879717c6aae2931398b1c30ab761008483a49cc5d93899a2eeb8c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "11443f91bbda5f3d440908f20bfafd549dad5357e705f1e85273ebb6db0206f3", "variant": null }, "cpython-3.14.0b4-windows-aarch64-none": { @@ -267,8 +267,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "c21eb7a109ec8b980735aee5ca5c3b7522479919d12078f046a05114de428ff0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "61bef0ff22c3117795c55d5e8e2c87956a94fbb4725e03231f360b7c68ba5358", "variant": null }, "cpython-3.14.0b4-windows-i686-none": { @@ -283,8 +283,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "29ebdc7899a947e29aba6376477d059871698b712cf0dfb75b8e96af2e8b23cb", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "bcf229f25c12f81169b1f1d207a719fc2908f4e6ba5b61404787710d3b1e2120", "variant": null }, "cpython-3.14.0b4-windows-x86_64-none": { @@ -299,8 +299,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "072b97a1850f11bc350c1abfa5c08024ce4fe008022d634e23d4647e47cc005f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "8255b31a40867eb52ff1a2e476f56c697a717e6193d313413c788b0fbdd28a3c", "variant": null }, "cpython-3.14.0b4+freethreaded-darwin-aarch64-none": { @@ -315,8 +315,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "f4a28e1d77003d6cd955f2a436a244ec03bb64f142a9afc79246634d3dec5da3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "ce28498dcf2c5c4d3c964e6e44ff44e5b1b72a4234f807e2ff121393ed40442e", "variant": "freethreaded" }, "cpython-3.14.0b4+freethreaded-darwin-x86_64-none": { @@ -331,8 +331,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "f1ea70b041fa5862124980b7fe34362987243a7ecc34fde881357503e47f32ab", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-x86_64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "a7d63512a17522d7c76c7bafa27b49a35f4f5f74b5140be209ca17c0cad15737", "variant": "freethreaded" }, "cpython-3.14.0b4+freethreaded-linux-aarch64-gnu": { @@ -347,8 +347,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-aarch64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "2a92a108a3fbd5c439408fe9f3b62bf569ef06dbc2b5b657de301f14a537231a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-aarch64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "0250288ab21cfd14caa826056de7203baa19ed7e85198c19e6dcdd8b2124ae0e", "variant": "freethreaded" }, "cpython-3.14.0b4+freethreaded-linux-armv7-gnueabi": { @@ -363,8 +363,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-armv7-unknown-linux-gnueabi-freethreaded%2Blto-full.tar.zst", - "sha256": "f1d52c12f6908f6dc0658bf9d5cf1068272b4f9026aa33b59ded9f17e1d51f9f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-armv7-unknown-linux-gnueabi-freethreaded%2Blto-full.tar.zst", + "sha256": "c0bd17a6409c21fb10b075449511c09940b53438bf785cd20db1f2e5d15ade30", "variant": "freethreaded" }, "cpython-3.14.0b4+freethreaded-linux-armv7-gnueabihf": { @@ -379,8 +379,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-armv7-unknown-linux-gnueabihf-freethreaded%2Blto-full.tar.zst", - "sha256": "418741c7de3c53323d9ae8a42a450f0f612fa5fbea1bedeea57dee0647c82a8d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-armv7-unknown-linux-gnueabihf-freethreaded%2Blto-full.tar.zst", + "sha256": "d747055b6b5878dcf6b9d425b0a7ea3fa7b33fe241b31681e28f56d5ed86ed5d", "variant": "freethreaded" }, "cpython-3.14.0b4+freethreaded-linux-powerpc64le-gnu": { @@ -395,8 +395,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-ppc64le-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "5823a07c957162d6d675488d5306ac3f35a3f458e946cd74da6d1ac69bc97ce3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-ppc64le-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "756376b22bf237646f7bb519bee69b1704d369a6ca5941b5ff83d5b2d022612b", "variant": "freethreaded" }, "cpython-3.14.0b4+freethreaded-linux-riscv64-gnu": { @@ -411,8 +411,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-riscv64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "f48843e0f1c13ddeaaf9180bc105475873d924638969bc9256a2ac170faeb933", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-riscv64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "25dbe52c44b42914d9343d456dc17fbcbf234ab1f0fd0be00cae27c6e336546b", "variant": "freethreaded" }, "cpython-3.14.0b4+freethreaded-linux-s390x-gnu": { @@ -427,8 +427,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-s390x-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "a1e6f843d533c88e290d1e757d4c7953c4f4ccfb5380fef5405aceab938c6f57", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-s390x-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "7ebb845ee94ae870e13146de0052251d48d584363c1b374f84fbdeb8e7936350", "variant": "freethreaded" }, "cpython-3.14.0b4+freethreaded-linux-x86_64-gnu": { @@ -443,8 +443,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "7f5ab66a563f48f169bdb1d216eed8c4126698583d21fa191ab4d995ca8b5506", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-x86_64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "0df5305c3b95f53f7f2db762be2badf752477c359146155f8b9658b71aff2128", "variant": "freethreaded" }, "cpython-3.14.0b4+freethreaded-linux-x86_64-musl": { @@ -459,8 +459,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "180249191d6e84b5dd61f6f7ba7215582b1296ef4d8bd048439cd981363cd2b2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-x86_64-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "c6beef48f6a2ca49da0b2798e5dc9c45233a8f0b6fa778616ba7cfdcd66f85a6", "variant": "freethreaded" }, "cpython-3.14.0b4+freethreaded-linux-x86_64_v2-gnu": { @@ -475,8 +475,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v2-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "bc9c0f25680f1f3c3104aef3144f1cd8c72d31e4cbf45a7c6f89ddb5c1b0e952", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-x86_64_v2-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "31587432be64d6913317919c239ef84ae4c78a7b11f95e8d48b81dc820021be3", "variant": "freethreaded" }, "cpython-3.14.0b4+freethreaded-linux-x86_64_v2-musl": { @@ -491,8 +491,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v2-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "b30a2004c89d79256926bb4d87bec6100b669d967d336cb9df1aa5ae9a9106cf", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-x86_64_v2-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "302a23af192207337db2c2268a3fed98f13845ad5324f1ff97baa68807098513", "variant": "freethreaded" }, "cpython-3.14.0b4+freethreaded-linux-x86_64_v3-gnu": { @@ -507,8 +507,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v3-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "6941b1d02adb12cd875c2320e0d30380b7837c705333336b8d295440d93d3668", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-x86_64_v3-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "39747d608a5400b0fa37fbddef606678f8552fdf907f43b1d8a475436c413aa9", "variant": "freethreaded" }, "cpython-3.14.0b4+freethreaded-linux-x86_64_v3-musl": { @@ -523,8 +523,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v3-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "b64f69cb58ac51e962080d6fa848d90dc24739bc94089a7975b3459b23ad5df3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-x86_64_v3-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "9870447eb095027df97a1e412eff378fb78872a527dc6adeffc901fff8a40d70", "variant": "freethreaded" }, "cpython-3.14.0b4+freethreaded-linux-x86_64_v4-gnu": { @@ -539,8 +539,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v4-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "b294b586bdcbc0b038e77999d4371c6fe3d90228b2b9aa632262ad3f5210487b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-x86_64_v4-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "78adac3ab0696380ebdbceb96924d0f033e20b033e3a1633aa54df0295407292", "variant": "freethreaded" }, "cpython-3.14.0b4+freethreaded-linux-x86_64_v4-musl": { @@ -555,8 +555,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v4-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "61ed61ed5052a7ca9d919194526486d7f973fd69bb97e70e95c917a984f723c7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-x86_64_v4-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "59f92039b72eca4cfb4639699bc97bbb0de6b866a7894bac9cf132374cf5aa1a", "variant": "freethreaded" }, "cpython-3.14.0b4+freethreaded-windows-aarch64-none": { @@ -571,8 +571,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-aarch64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", - "sha256": "d7396bafafc82b7e817f0d16208d0f37a88a97c0a71d91e477cbadc5b9d55f6d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-aarch64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "37fac713d3b25731f134c9c6b1c9021ffb2aacda630010ffa15497446655179f", "variant": "freethreaded" }, "cpython-3.14.0b4+freethreaded-windows-i686-none": { @@ -587,8 +587,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-i686-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", - "sha256": "7066fc54db97331fb25f52783f188d65f8868ad578f9e25cb9b1ae1f2c6dacc5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-i686-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "5a7d61b1863960dab6f78027b5edc543ee41d0a45f7851413951389b842385c8", "variant": "freethreaded" }, "cpython-3.14.0b4+freethreaded-windows-x86_64-none": { @@ -603,8 +603,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", - "sha256": "5de7968ba0e344562fcff0f9f7c9454966279f1e274b6e701edee253b4a6b565", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-x86_64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "e503ec18fd8b1d0fcb94ded5a67be4a88334d5b101dc485b0281577ae84a6acc", "variant": "freethreaded" }, "cpython-3.14.0b4+debug-linux-aarch64-gnu": { @@ -619,8 +619,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "9ac97f7531f9d74ccd1f7de8b558029094831a0be965fe9569ecc7547aeec445", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "6bf05e71ef3cf092d0f40d992ea192016327468992e5e0b7bde8ac48d6b9c145", "variant": "debug" }, "cpython-3.14.0b4+debug-linux-armv7-gnueabi": { @@ -635,8 +635,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "fcb0d09a7774b69ca7df3a954fedc32bd1935838c91918f1d08b9a19914f30ec", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "9b73df95176c383e4af6027b78da060c69892914bfc195107084b21281f09bfd", "variant": "debug" }, "cpython-3.14.0b4+debug-linux-armv7-gnueabihf": { @@ -651,8 +651,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "664a70a1f73eb0ca1299bf8b26ec0b696ea1a09a26b5a1956688c3e4004b0ce2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "2d325c459c761b4bca5e2005aeccc889ef62ee4b0811d9252e22817f3037825e", "variant": "debug" }, "cpython-3.14.0b4+debug-linux-powerpc64le-gnu": { @@ -667,8 +667,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "71ac17708fd382292c5dbc77b11646b9ee52230381c2f7067bc5f22a2e2fd9cf", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "1c49311aae1ade3afd9d39091897d2b1307aeadfdde87e5099e07b0fdc32bc2f", "variant": "debug" }, "cpython-3.14.0b4+debug-linux-riscv64-gnu": { @@ -683,8 +683,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "2916572ff670885b38860861fceb395711831ac2a36e0830fe0ee029a91cec56", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "ad52ff04ef3fc78430b8b0623a0442088dc4e8c6835fce6957e251676942ebbf", "variant": "debug" }, "cpython-3.14.0b4+debug-linux-s390x-gnu": { @@ -699,8 +699,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "4086605066914c6fb1944932e59585c328c3a688379d2c061df8e963e65e04dd", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "6865d4830ef7beaa99dd817df0c49bb0d380b9a0c822be6f8ca090f9a568df81", "variant": "debug" }, "cpython-3.14.0b4+debug-linux-x86_64-gnu": { @@ -715,8 +715,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "c91fa37d96f46a4f58ac6d3b2d9e0178288e2fb21a05131c874abfbfae404f71", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "db9c32e119c58d9f25745599efaa383be06323ca8d8524a6c50b62367b058b93", "variant": "debug" }, "cpython-3.14.0b4+debug-linux-x86_64-musl": { @@ -731,8 +731,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "ab08748b50a7df1e6231fab1bf59a7e0b26cfb44ff2c811a9f249fe141332d21", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "39dece02d5b286e7d9ffbbacdd730db0d64b881bb2b2edd3b721be23c4e89609", "variant": "debug" }, "cpython-3.14.0b4+debug-linux-x86_64_v2-gnu": { @@ -747,8 +747,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "64dd678f10b3bb86bd047cf585651d323c80e34da840ca8ed49507f3959acc90", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "90453b5f3d982604a950e5f362b192889f82524257d2fa8bf979b270e8bdb370", "variant": "debug" }, "cpython-3.14.0b4+debug-linux-x86_64_v2-musl": { @@ -763,8 +763,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "3e057342e72555a4934e05037423f2b68f42d62a6f10b36d48150ca5110d603e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "d070ef11038828a1326c230c45782c70f02a6b89504af76cc95f0778db20caac", "variant": "debug" }, "cpython-3.14.0b4+debug-linux-x86_64_v3-gnu": { @@ -779,8 +779,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "265b07a17fedc8ca32a8ebd6763946c21bb472346ac65efb89d1e045e4772abd", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "baf92ab8fa281f72a8e8b4a1975a931876866b69aebed1eb94dafeaa219f788d", "variant": "debug" }, "cpython-3.14.0b4+debug-linux-x86_64_v3-musl": { @@ -795,8 +795,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "5860fc768bf7c7d2051ee80109f0fd5a4d89f045ca26562f88e5f93978979abe", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "3a92a638ef08b058eebf806ecb0134aa9467c554512fd2082e6ecd1a6c517fdd", "variant": "debug" }, "cpython-3.14.0b4+debug-linux-x86_64_v4-gnu": { @@ -811,8 +811,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "ae0cf5352a594ce1dfd287fb49684490128a7f89b3dfbcd43f1b8d84083c8ead", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "7144cb9ac62b0084b8421b83e90aab0ed6e704cc5f63ba1c16f8216971d11857", "variant": "debug" }, "cpython-3.14.0b4+debug-linux-x86_64_v4-musl": { @@ -827,8 +827,8 @@ "minor": 14, "patch": 0, "prerelease": "b4", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.14.0b4%2B20250708-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "5e2b1a537aa9cc6e1c77e6050f31aacd866c50b16b603b54c485b8f8cfeebb4a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.14.0b4%2B20250712-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "bef1d2f0e3f32667366655e8333ef1f92ab07cd7b988da110f3970a5d671e3a3", "variant": "debug" }, "cpython-3.14.0b3-darwin-aarch64-none": { @@ -6395,8 +6395,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "71c9af8648001c4a09943305a890339a4cfff0bd260aa5a9d8c8e82e7ef32583", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "08d840adc7dd1724bd7c25141a0207f8343808749fa67e608d8007b46429c196", "variant": null }, "cpython-3.13.5-darwin-x86_64-none": { @@ -6411,8 +6411,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "65b171888e34d0a904ee0a6adef1a5366bdedcd9fca990ec06717a68eef2c4ff", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "5277dc381e94abde80989841f3015df2aba33894893c4a31d63400887bdefd2d", "variant": null }, "cpython-3.13.5-linux-aarch64-gnu": { @@ -6427,8 +6427,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "e0d2322a92b9bb8e39442cbcfa6ee9590fd035de2a6199d4e6903dcbc0b6542a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "82d8a025b43c9127d47490a7070aa5d8bfede2d1deb5161c0f4c2355396f9e5d", "variant": null }, "cpython-3.13.5-linux-armv7-gnueabi": { @@ -6443,8 +6443,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "59442502a4eebff23a49503a9cbe92a6b813a756bf36a299ced55fb705d5fe73", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "6aa50bf3245364091a7e5ca6b88166f960c2268586c33e295069645815f16195", "variant": null }, "cpython-3.13.5-linux-armv7-gnueabihf": { @@ -6459,8 +6459,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "c3de5a89b71ef3dc8ee53777a9fda3f2d7f381abc0b4a6f6f890de55d3620293", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "5f776b18951b9a0507e64e890796113a16b18adb93a01d4f84c922e2564dab43", "variant": null }, "cpython-3.13.5-linux-powerpc64le-gnu": { @@ -6475,8 +6475,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "c17e73fe07de36a506ffc400173739d2802f30bdc5f5b6443891bbcee926edac", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "b74b79e5a65c84ed732071fd7b445a51b86c03ef18643b87c0fe5c96242e629b", "variant": null }, "cpython-3.13.5-linux-riscv64-gnu": { @@ -6491,8 +6491,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "1b5da1585dca39a15452c891ff16f468ce984f76500c262f08c4aeae75e79c3c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "652416183693219b1f0f1f2a8d2a595f75f8c94e8c7b8b25ecd312ec1fdbb36e", "variant": null }, "cpython-3.13.5-linux-s390x-gnu": { @@ -6507,8 +6507,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "d47e645034432fce6d107835c07d5fe38fd53232a66e0a9d63ead48b42da3539", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "29a7140db0cbd1426f450cd419a8b5892a4a72d7ef74c1760940dd656f8eaded", "variant": null }, "cpython-3.13.5-linux-x86_64-gnu": { @@ -6523,8 +6523,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "2f57c58edc385fe9958d2c6e41ecd389cfed3f882515a1813f1d2ba4c964f399", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "e42827755c227d3ea31b0c887230db1cd411e8bddf84f16341a989de2d352c51", "variant": null }, "cpython-3.13.5-linux-x86_64-musl": { @@ -6539,8 +6539,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "13cf16ef2008adf36a812add953317a4359945468dbcaece38b2b71466d05502", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "a652ff101318b7bd7a06181df679e2e76d592ebe70dbc4ca5db97b572889d93f", "variant": null }, "cpython-3.13.5-linux-x86_64_v2-gnu": { @@ -6555,8 +6555,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "416d3a7bd64c3ee047b37d91ce1a58ec308733292c0268bfd860984c21eb7377", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "dd945e6178236e2eee27b9de8e6d0b2ef9c6f905185a177676d608e42d81bebb", "variant": null }, "cpython-3.13.5-linux-x86_64_v2-musl": { @@ -6571,8 +6571,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "c32aee456cb150a8c105c213dc4afa8a409fba1aced890a4f58001ae70074922", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "af86120b3c3c48afdd512a798c1df2e01e7404875d5b54fc7bbde23f8b004265", "variant": null }, "cpython-3.13.5-linux-x86_64_v3-gnu": { @@ -6587,8 +6587,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "f2d3a4aa566ce5a505a82357c766ccfc60f6bb4e255fab8725da2fbc28a199d3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "c13783eae63223bced84ec976be9ad87d5b2ab3d9ba80c4f678520a4763410ba", "variant": null }, "cpython-3.13.5-linux-x86_64_v3-musl": { @@ -6603,8 +6603,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "f8d1c8f82a6cd694ca453e1c5e96e7415232be288a832b17bd5a4e9b7a5c09fe", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "5e7433fd471a8d2a5dfa9b062b3c1af108eef5958e74d123de963c5d018b3086", "variant": null }, "cpython-3.13.5-linux-x86_64_v4-gnu": { @@ -6619,8 +6619,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "a46b315e40f93ce673fb5ff9193c1f9dee550fe6f494fe1bba41885ef19ee094", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "14a4301952bf11ddf023e27ff5810963bf5a165946009f72c18bdd53f22450c0", "variant": null }, "cpython-3.13.5-linux-x86_64_v4-musl": { @@ -6635,8 +6635,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "4efeb9cd7c96f3b157478bb3037597b56334f14aad519eddc64da29849cc8031", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "583b793e600a9d55b941092de2f4f7426acaac7e7430ed9a36586f7a1754a8ea", "variant": null }, "cpython-3.13.5-windows-aarch64-none": { @@ -6651,8 +6651,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "22b73edc3afc256b58bb41b5a660aa835500781ef5b187de0c941748b1f38e3a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "0e95119f5d018ec18bcf9ee57c91e13c9ffda2a5da5fa14f578498f8ec6e4ac0", "variant": null }, "cpython-3.13.5-windows-i686-none": { @@ -6667,8 +6667,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "fffdf2a1a16b9a24ef8489008a4a08927b202d7b79401913bbe1363e4180ad3a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "a877e912a7fc298e2b8ee349ed86bee00ac551232faebf258b790e334208f9d2", "variant": null }, "cpython-3.13.5-windows-x86_64-none": { @@ -6683,8 +6683,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "0871127fcf73c79479f36b2f34177565f6e97b87b4dd9cdafe4d6c37b54c153a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "bf9d014f24aa15f2ae37814e748773e395cbec111e368a91cdbcb4372bdff7c5", "variant": null }, "cpython-3.13.5+freethreaded-darwin-aarch64-none": { @@ -6699,8 +6699,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "b7764ec1b41a7018c67c83ce3c98f47b0eeac9c4039f3cd50b5bcde4e86bde96", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "61862be1c897fff1d5ec772be045d1af44846ffd4a6186247cc11e5e9ae3d247", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-darwin-x86_64-none": { @@ -6715,8 +6715,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "f15f0700b64fb3475c4dcc2a41540b47857da0c777544c10eb510f71f552e8ec", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "a51777a7a3d4b4860dd761dbcce85a8e9589031293a2f91f4a6a3679c3d0f5a8", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-aarch64-gnu": { @@ -6731,8 +6731,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-aarch64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "ced03b7ba62d2864df87ae86ecc50512fbfed66897602ae6f7aacbfb8d7eab38", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-aarch64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "e907a33d468de5f3936e73a0e6281a40307207acf62d59a34a1ef5a703816810", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-armv7-gnueabi": { @@ -6747,8 +6747,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-armv7-unknown-linux-gnueabi-freethreaded%2Blto-full.tar.zst", - "sha256": "0eafdd313352b0cda5cbfa872610cae8f47cfcba72da5a4267c7a1ef4dab8ccd", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-armv7-unknown-linux-gnueabi-freethreaded%2Blto-full.tar.zst", + "sha256": "fa495608f0bb7debc53a5d7e9bd10a328e7f087bba5b14203512902ead9e6142", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-armv7-gnueabihf": { @@ -6763,8 +6763,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-armv7-unknown-linux-gnueabihf-freethreaded%2Blto-full.tar.zst", - "sha256": "1a7c93ed247a564836416cbb008837059fb4e66468d1770a9b2ba2d12a415450", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-armv7-unknown-linux-gnueabihf-freethreaded%2Blto-full.tar.zst", + "sha256": "5316526a325b72a7e6a75f5c0ba8f2f4d1cbab8c8f0516f76055f7a178666f21", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-powerpc64le-gnu": { @@ -6779,8 +6779,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-ppc64le-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "9c943e130a9893c9f6f375c02b34c0b7e62d186d283fc7950d0ee20d7e2f6821", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-ppc64le-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "23770a0b9e176b8ca1bbbecd86029d4c9961fa8b88d0b0d584b14f0ad7a5dccc", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-riscv64-gnu": { @@ -6795,8 +6795,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-riscv64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "8075ed7b5f8c8a7c7c65563d2a1d5c20622a46416fb2e5b8d746592527472ea7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-riscv64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "0f111d4619843451a0edd13e145fc3b1ea44aecf8d7a92184dcd4a9ed0a063c4", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-s390x-gnu": { @@ -6811,8 +6811,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-s390x-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "a8dbcbe79f7603d82a3640dfd05f9dbff07264f14a6a9a616d277f19d113222c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-s390x-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "0a6df4acd93d29b0d94aa92fa46482f10bbcfe1b1e608e26909f608691c7f512", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64-gnu": { @@ -6827,8 +6827,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "e21a8d49749ffd40a439349f62fc59cb9e6424a22b40da0242bb8af6e964ba04", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "2c49314909be249c90071a54168f80d4cbf27ecbec7d464f8743d84427c5b7b1", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64-musl": { @@ -6843,8 +6843,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "625ae3e251cf7f310078f3f77bfdae8bbe3f1fe2c64f0d8c2c60939cb71b99d4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "e27a15c987d616763619413b2d7122d1f4ba66a66c564c2ab4a22fb1f95c826d", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v2-gnu": { @@ -6859,8 +6859,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v2-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "7b9bc02fc1eb08ba78145946644fe81bc6353e2e28e74890ff93378daffa9547", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v2-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "6882afc2e308561b8c1a23187c0439116434aae8573fd6e6dbdce60e3af79db5", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v2-musl": { @@ -6875,8 +6875,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v2-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "4e163edf7e6a6a104f19213f3ad1b767f4d33a950ca8ea51f7b9ce04ba5a4c16", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v2-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "a8ef0d7a50a2616b2a1f8a5d7a3b52fa69085e6a75a6f7d3f318f7c132abfe16", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v3-gnu": { @@ -6891,8 +6891,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v3-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "f1390326557df5562639bccaaaad4edcebf4e710696a2948b2aa00db2abdde5a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v3-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "ab2e44c83245d18226f1fce26b09218de866048ecb515b50b8174ba75c182b4e", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v3-musl": { @@ -6907,8 +6907,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v3-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "d5751f3b8af6d06e06a0ce5ea18307c1b6c38508b3879442c504eca3047d4ae2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v3-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "bad372bd5e38ff42064907b95273736137485ffdc6ff1d90b2e49f8df2829abb", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v4-gnu": { @@ -6923,8 +6923,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v4-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "88d8e7dfed818877158ede9b22342d9ce0fd3f49116954ca0eae7540e675d235", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v4-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "d12f4ecb61ae7ced3723173aa0a5ddaea395e098bfede57497426c65b5776b82", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v4-musl": { @@ -6939,8 +6939,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v4-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "2a6de48306f788910b33c54e1640d3b9fe29ccb3c44dcdc0b0ba6d6a89213d9e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v4-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "734233279cbab1f882f6e6b7d1a403695379aaba7473ba865b9741b860833076", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-windows-aarch64-none": { @@ -6955,8 +6955,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-aarch64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", - "sha256": "accb608c75ba9d6487fa3c611e1b8038873675cb058423a23fa7e30fc849cf69", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-aarch64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "51d116a7f93654d602d7e503e3c7132ae4f10e5a8e8fbe7e2ceb9e550f11051a", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-windows-i686-none": { @@ -6971,8 +6971,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-i686-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", - "sha256": "5cba33c38d25519b4c55a5b0015865771e604a2d331c7d335f52753b09d5b667", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-i686-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "d4461149a95fd6d9c97d01afb42561c4b687d08526c84e8ff9658d26514450eb", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-windows-x86_64-none": { @@ -6987,8 +6987,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", - "sha256": "75acd65c9a44afae432abfd83db648256ac89122f31e21a59310b0c373b147f1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "eb704f14608176fc8d8d8d08ca5b7e7de14c982b12cd447727bf79b1d2b72ac7", "variant": "freethreaded" }, "cpython-3.13.5+debug-linux-aarch64-gnu": { @@ -7003,8 +7003,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "bd73726128747a991d39bbc2c1a1792d97c6d2f4c7b6ed4b2db9254dd16d4ea6", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "defdf6ddc233f8e97cc26afaa341651791c6085a59e02a1ab14cf8a981cdc7bf", "variant": "debug" }, "cpython-3.13.5+debug-linux-armv7-gnueabi": { @@ -7019,8 +7019,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "bbc1e704e4a2466cd52785e52f075e1b10ef5628879620b9461c6af2072e7036", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "69308c195ebc63543efa8f09fabb4a6fa2fc575019bd1afbc36c66858d2122c4", "variant": "debug" }, "cpython-3.13.5+debug-linux-armv7-gnueabihf": { @@ -7035,8 +7035,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "60389c2db232050357f24d7858ff019bb9cb37295465196275ec999e1d85f7db", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "ad3c911764e60a94c073c57361dc44ed1e04885652cabb1d1f3a1d11d466650d", "variant": "debug" }, "cpython-3.13.5+debug-linux-powerpc64le-gnu": { @@ -7051,8 +7051,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "e93c5832c3c6e39a2131d69de2e700bddab3a4f8bce74039e69276cec645f3a8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "bd91893c42edc3b23ee45df6fff77250dab8f94646bbdf2087c0a209231f210d", "variant": "debug" }, "cpython-3.13.5+debug-linux-riscv64-gnu": { @@ -7067,8 +7067,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "6fb1da6dd6ccc40eea19062cb494f7cf0207c1e99a0a8cf9cae8fdc9cc30a4b6", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "7f3e649685358af0d78c8d7dcc4d357d5674e24aeaecbcc309ce83d5694821ce", "variant": "debug" }, "cpython-3.13.5+debug-linux-s390x-gnu": { @@ -7083,8 +7083,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "a62a131ed07e9ef322ded45fb5257aa58502b10cb6e2a18298145838a041637b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "fc013b0375c357286bf6886c0160c9a7fca774869c8a5896114ac1bf338f0b2e", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64-gnu": { @@ -7099,8 +7099,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "a054dca4b204562ae34cd38f7b31ff53f035acd012310f9f7c8817eac9852db2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "3502c7c36500fa1a84096f0e9c04dc036f3dbbae117d6b86d05b0a71a65e53cb", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64-musl": { @@ -7115,8 +7115,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "5da37b4623286ed7283277ec6288d0be88fcd3d208e98c075a140385734f0056", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "b42647c29dca10e55ceeaa10b6425f4ff851721376b4b9de82ce10c21da2b5f2", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v2-gnu": { @@ -7131,8 +7131,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "83faa4f0a92287a55887ef402bb138ca7aa46848afb7c9a30ebc337f8cb4b86c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "5dee021b1e82ddeacae72fdee5ba6d2727faf1b39b8d4b9361a7961e5321c347", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v2-musl": { @@ -7147,8 +7147,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "8caaba837f778d2da1b041f15f0f46a3c117a531a55d6e79f5aaca836ecfb84f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "623e2fedb44f5c8c123371a9e82771792d1a64ea11cb963259947679c1bb7027", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v3-gnu": { @@ -7163,8 +7163,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "d4d2e746af77d16428d8168d11f8bf5b90424667949af7895413cdc18ebcaee8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "f24df9f31d052c4e9cabec7a897d78ceccf9fb90a6edaa6f4f128e49d5f27162", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v3-musl": { @@ -7179,8 +7179,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "f76628dc2447a1fc55f463623c81f9a19002b5f968afe77b57136fdc41833993", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "2821ef432b962ab4968e339f8d55a790eb64e266ccba674837589d58fb40f0d0", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v4-gnu": { @@ -7195,8 +7195,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "4620c454e6ae9ad0093785b54790ddb68c2d3f2d868aa79a5aa678b98e1138a3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "8f9f953c202e0f6b5f7e7abff2b34beaff7a627d1f7ff8cdfe4d29f4fc12f067", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v4-musl": { @@ -7211,8 +7211,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.13.5%2B20250708-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "e1f4f398dadd9cd83e351ea08a068bc3ea24f870ccddbeb3b65ce65a3bc5c106", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "5c0740e8df7d69b4e2ead4f11db97e3d884e77377d84cbf6fba58077043388fb", "variant": "debug" }, "cpython-3.13.4-darwin-aarch64-none": { @@ -11435,8 +11435,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "3c948bee581f42c4a3b072a5e1ff261e0eb1636c00d5474c28a13fa627c95578", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "0a5748a455ebd0ef0419bffa0b239c1596ea021937fa4c9eb3b8893cf7b46d48", "variant": null }, "cpython-3.12.11-darwin-x86_64-none": { @@ -11451,8 +11451,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "c81794121d513b7eab710a210202e78393400460251a6878c85b927977098b38", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "1154b0be69bdd8c144272cee596181f096577d535bff1548f8df49e0d7d9c721", "variant": null }, "cpython-3.12.11-linux-aarch64-gnu": { @@ -11467,8 +11467,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "7ac6956ce9119a44531e9cbe3fe4d0beadcf244e02be81a863b95aa69041314f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "73a22b9fa275682f326393df8f8afe82c302330e760bf9b4667378a3a98613ba", "variant": null }, "cpython-3.12.11-linux-armv7-gnueabi": { @@ -11483,8 +11483,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "4cc102db1b315425d2feda63407ee0e737902d94eaecf52e3ec8ea6f6d7cee4d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "6a60953cc821d673bf67724d05a430576d0921a60cfceeca11af5a758bd3ae71", "variant": null }, "cpython-3.12.11-linux-armv7-gnueabihf": { @@ -11499,8 +11499,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "c62d2c512b4e35dfb40d29246ed02cf0049e645bf333eca0a9e703da51f64597", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "1f8b03c8bf51f36f659961364f9d78a093af84305bbe416f95b5ecb64a11314d", "variant": null }, "cpython-3.12.11-linux-powerpc64le-gnu": { @@ -11515,8 +11515,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "1025a919ad5170f76c58fb73f4b2b3a5e2ed910d1f802390f032b4da91152f23", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "10164c4c0e7f9a29024677226bc5f7c0b8b2b6ac5109a0d51a0fb7963f4bec48", "variant": null }, "cpython-3.12.11-linux-riscv64-gnu": { @@ -11531,8 +11531,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "0178724fd0ce4712092c2afb66094e12d1f7e07744cf9d0c462aad516a82b984", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "f47a3ad7d96ba16b8b38f68f69296e0dca1e910b8ff9b89dd9e9309fab9aa379", "variant": null }, "cpython-3.12.11-linux-s390x-gnu": { @@ -11547,8 +11547,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "cb480b2fd0fefcdf71e07ab6a321e878bbc6d2c855356575db29fcbb48d5eae1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "0714bccd13e1bfd7cce812255f4ba960b9ac5eb0a8b876daef7f8796dbd79c7a", "variant": null }, "cpython-3.12.11-linux-x86_64-gnu": { @@ -11563,8 +11563,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "50f2684ecd4dfdff732d091f0e3d383261a9d524a850784cd01a1c0839ece3e7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "e42c16fe50fda85dad3f5042b6d507476ea8e88c0f039018fef0680038d87c17", "variant": null }, "cpython-3.12.11-linux-x86_64-musl": { @@ -11579,8 +11579,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "f7ef6763b79a50da594fd1e03a6ee39017db6002c552539dbe0edffefc453804", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "3676e47a82e674878b986a6ba05d5e2829cb8061bfda3c72258c232ad2a5c9f1", "variant": null }, "cpython-3.12.11-linux-x86_64_v2-gnu": { @@ -11595,8 +11595,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "32320209ab9b187b142a81bc4063c8aab9aa05ddb9833ca921c17eefdd2f1509", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "ddf0c26a2df22156672e7476fda10845056d13d4b5223de6ba054d25bfcd9d3c", "variant": null }, "cpython-3.12.11-linux-x86_64_v2-musl": { @@ -11611,8 +11611,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "14abfef4e25db478db20dd15627576f47ff012a0eb3f7de3f9d1101ea409d02c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "2be8e228b2a698b66f9d96819bcc6f31ac5bdc773f6ec6dbd917ab351d665da2", "variant": null }, "cpython-3.12.11-linux-x86_64_v3-gnu": { @@ -11627,8 +11627,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "4f71291857a656cf4b780d7c5bd2667ecde14f9ec093e026cf28d2c8727d69ad", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "820174fbb713495a1beecd087cc651d2d4f1d10b1bb2e308c61aecec006fea0a", "variant": null }, "cpython-3.12.11-linux-x86_64_v3-musl": { @@ -11643,8 +11643,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "b3530c771104b7765241b87b2ac749f6fce1886b4d2b677a1fc46aaca9378019", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "5cfc247d6ee2303c98fecddfbdf6ddd2e0d44c59a033cb47a3eb6ab4bd236933", "variant": null }, "cpython-3.12.11-linux-x86_64_v4-gnu": { @@ -11659,8 +11659,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "0de444c0e4ac45f2f4863889e57f2dbbe79f01593afcc21f63b4ddb5832edd61", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "01519be2a0930f86a43ac93f25fb0f44b3dbf8077ecd23c98c5b3011150ef16a", "variant": null }, "cpython-3.12.11-linux-x86_64_v4-musl": { @@ -11675,8 +11675,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "3d761bb79ef0946ee76b659c9bcf034dc8a67e1d414bef51ecb498c595a2b262", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "93a9714ef88ece8575707e1841369b753f9d320c42832efffda8df8dfcbd9ca7", "variant": null }, "cpython-3.12.11-windows-aarch64-none": { @@ -11691,8 +11691,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "c2b541cd75cd12d7b1d52ebee724cc1b1f4d7367901d06b2f3f4a2e3ded4145e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "512ae77ca0afe3a81d990c975548f052b9cde78187190eb5457b3b9cdad37a9c", "variant": null }, "cpython-3.12.11-windows-i686-none": { @@ -11707,8 +11707,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "cbf9c2bd5f182f6fc6da969729d0d4a5683d5f392f3a9bed3d7240cbe7385c11", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "c815e6eadc40013227269d4999d5aef856c4967e175beedadef60e429275be57", "variant": null }, "cpython-3.12.11-windows-x86_64-none": { @@ -11723,8 +11723,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "79e5d97e543975309fe3a22e27f2d83d7b08cff462d699bfa721854971773ec6", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "49911a479230f9a0ad33fc6742229128249f695502360dab3f5fd9096585e9a5", "variant": null }, "cpython-3.12.11+debug-linux-aarch64-gnu": { @@ -11739,8 +11739,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "a8d1e10b91253cf528c9233c314e6958de7d9380c5e949a2ce1b1b4dc8538ebd", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "aed96d0c279ff78619991fadf2ef85539d9ca208f2204ea252d3197b82092e37", "variant": "debug" }, "cpython-3.12.11+debug-linux-armv7-gnueabi": { @@ -11755,8 +11755,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "46a11e0955ea444a0fe3fabbe9b1f36be4a72c804b8265d90f84f26a3de3199e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "360e6b2b9bf34d8fb086c43f3b0ce95e7918a458b491c6d85bf2624ab7e75ae3", "variant": "debug" }, "cpython-3.12.11+debug-linux-armv7-gnueabihf": { @@ -11771,8 +11771,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "ccc5fbb01a83f1a264e90d8f92324c64d3dc2b2bdc4568340bb58dc62b061cce", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "fffb9b6c2e81b03aa8a1d8932a351da172cd6069bbdc192f020c8862d262eab5", "variant": "debug" }, "cpython-3.12.11+debug-linux-powerpc64le-gnu": { @@ -11787,8 +11787,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "0f334bbaa774e7b98f264e04456dfb6130519294ac0c25593cebb41c92571e34", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "a8bed95f73ccd6451cad69163ef7097bfc17eda984d2932a93e2dda639f06ff2", "variant": "debug" }, "cpython-3.12.11+debug-linux-riscv64-gnu": { @@ -11803,8 +11803,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "f048e364a7895b535c9e68f987cf17e3ee5f3bd3b7189b95cc7db30cd8a7b9b5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "395d73e73ff0d0085ddb83f15d51375c655756e28b0e44c0266eb49f8d2b2f27", "variant": "debug" }, "cpython-3.12.11+debug-linux-s390x-gnu": { @@ -11819,8 +11819,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "c48068f9f02f16314265567acb56e411e9936abc9b18c9d67811f5faade66031", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "097dc82abc3805b8e1721e67869fd4ae6419fb9089d7289aec4dd61b9c834db4", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64-gnu": { @@ -11835,8 +11835,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "ef2fe47be6b147bc376ce8f2949cc3d193c9c1d2e362fa9dcbabf0e7c60f8a19", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "d11f20d2adaa582ac3e3ab6f56a3c1f4e468e1aa4712d6fe76dd2776fdb28330", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64-musl": { @@ -11851,8 +11851,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "a88306d6b3a09b85f93514d43b2c8bd35dff417cf861bd2a1ead4d87c5666f8a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "a4cfaa4c7915c35ecf4a15a3f25cdda68b1e2de06280cfe98680b4eed3e11ac1", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v2-gnu": { @@ -11867,8 +11867,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "b8637c81f61f41d49bf95699cc4c295579d671912f81b5446c3ba2496dac2627", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "e040fa65666bd109534c8ed4c70d198954a28e87dffbab1b138a55c8c98c4db5", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v2-musl": { @@ -11883,8 +11883,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "5dab0c1eb4ce013826a462247629263eae7726b635d868408152444cbf83a778", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "89504b7f5fba85aa2644be63aa9377e69e56f6c6f4c57a96e0a6050e95e2b8d8", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v3-gnu": { @@ -11899,8 +11899,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "eef2733d40a9511a2af9d83808ad640993c5d8b6fb436bc240cd9bac6be4ffc5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "5eb9cb98d4528045f1e03373373ddb783fbbf6646e3d0e683fb563e5f1d198e6", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v3-musl": { @@ -11915,8 +11915,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "9f7fbd3712e13f91414e7a498a58160d8745fa02b9d2898db8f6f3c589920b6d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "0d463ebb5c0886e019c54e07963965ee53c52d01e42b3ca8a994e8599c2d7242", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v4-gnu": { @@ -11931,8 +11931,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "acf0037e25e80cbc3e8a1ff1e3b83da10ed2b00d8ff7df0ff1d207d896e2225f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "94924bb8ca1f03bf06c87554be2ea50ff8db47f2a3b02c5ff3b27d5a502d5fe4", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v4-musl": { @@ -11947,8 +11947,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.12.11%2B20250708-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "fefe36ed014e3a6baf0eb122161b42262c1a00ae403de18fb03353cf80d46c1f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "47d315cae2b1cd67155cd072410e4a6c0f428e78f09bb5da9ff7eb08480c05c4", "variant": "debug" }, "cpython-3.12.10-darwin-aarch64-none": { @@ -15995,8 +15995,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "f35b94b5aaefaff34b59f4aab09a5eec02c93e3b61a46c6694f4e93fb2aea86c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "cb07230fc0946bab64762b2a97cca278c32c0fa4b1cf5c5c3eb848f08757498a", "variant": null }, "cpython-3.11.13-darwin-x86_64-none": { @@ -16011,8 +16011,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "c2a6b3053af4354d74b70d25ccf744bea7c545ee00da38a93e8b392ec9f062f1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "1eec204b5dffad8a430c2380fd14895fad2b47406f6d69e07f00b954ffdb8064", "variant": null }, "cpython-3.11.13-linux-aarch64-gnu": { @@ -16027,8 +16027,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "a05521f2fa75e60920cb1172722920262c73d7ead3045a2a5b4844d287a1dfdd", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "c5155a27d8e8df696eff8c39b1b37e5330f12a764fdf79b5f52ea2deb98a73a0", "variant": null }, "cpython-3.11.13-linux-armv7-gnueabi": { @@ -16043,8 +16043,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "a4bb388a080d1dc4a7d381d2bc7f74d00311d5fc6ef66d457178b5c62d7e0ac1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "680ecfd9fc09d62dbe68cfb201e567086e3df9a27d061d9bcde78fad4f7f4d94", "variant": null }, "cpython-3.11.13-linux-armv7-gnueabihf": { @@ -16059,8 +16059,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "80444ffb9f33d39a9462e2efa04ba7edbef6af2e957457a71a0710344972f0ba", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "af2508bfab6c90a28d7e271e9c1cede875769556f3537fc7b0e3b6dd1f1c92b7", "variant": null }, "cpython-3.11.13-linux-powerpc64le-gnu": { @@ -16075,8 +16075,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "40e5fcea272e4a8253cf2bc392fbad36ca4260de75a12ef3c95711eb86f57a0c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "c83b749e3908140dec9ffadbf6b3f98bacaf4ca2230ead6adbd8a0923eebf362", "variant": null }, "cpython-3.11.13-linux-riscv64-gnu": { @@ -16091,8 +16091,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "eae2bbaf28b1f5886408e6cae4c5d393f3065dbd3293231b93bd0122f5f0543d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "7f0dfc489925e04ba015f170f4f30309330fae711d28bc4ed11ff13b9c3d9443", "variant": null }, "cpython-3.11.13-linux-s390x-gnu": { @@ -16107,8 +16107,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "702fd03db386a6711afbf14778a5b2aca6d4c3e47ff26e85a4d85991023ee0db", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "603e7bad4e81cee7d4c1c9ca3cb5573036fb1d226a9a9634ca0763120740d8ff", "variant": null }, "cpython-3.11.13-linux-x86_64-gnu": { @@ -16123,8 +16123,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "f730f5d09fc41e2573b0092ef143dd8976a8f6593ad31b833ea1d0adbc5562dd", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "e50197b0784baaf2d47c8c8773daa4600b2809330829565e9f31e6cfbc657eae", "variant": null }, "cpython-3.11.13-linux-x86_64-musl": { @@ -16139,8 +16139,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "337e164de474fefe5a2bf63c5d836093eae3532be80ed54b8d1abfd6dcb1b742", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "a233b0492531f187ac33ecfd466debf21537a8b3ae90d799758808d74af09162", "variant": null }, "cpython-3.11.13-linux-x86_64_v2-gnu": { @@ -16155,8 +16155,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "3903459242e57e9979ca6e581c06f3e4c573cf1d3e2d3eb62ce2cba8e3d83fd9", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "5f970ce2eecd824c367132c4fd8d066a0af3d079e46acf972e672588a578b246", "variant": null }, "cpython-3.11.13-linux-x86_64_v2-musl": { @@ -16171,8 +16171,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "624494b5583fcec1f75464797686ffeb4727cf0ccdc54cf9c73f0b45888d5274", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "a2df9657ecbecce2a50f8bb27cb8755d54c478195d49558de1c9c56f5de84033", "variant": null }, "cpython-3.11.13-linux-x86_64_v3-gnu": { @@ -16187,8 +16187,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "d5898a58943ed9f770a94125e7af85fbfd50b87e19135628708e8dbc6c8bd0b4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "c30fd4073a10ac6ee0b8719d106bb6195ca73b7f85340aac6e33069869ae4ee8", "variant": null }, "cpython-3.11.13-linux-x86_64_v3-musl": { @@ -16203,8 +16203,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "8bc18b17a9f8d36271dca160d402c18a42552b0e50708bf3732d0e2b1985235d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "cd15f24848c848b058a41dd0b05c4e5beca692d2c60c962fcb912fffc690afef", "variant": null }, "cpython-3.11.13-linux-x86_64_v4-gnu": { @@ -16219,8 +16219,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "257e29dc405d10062184da4078e1d46a787e19a04cba2a1c1831c21e52d0a557", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "8c390cae0b2d163f18117cae43bcbe430e58146d97e0c39b4afe72842e55f5fc", "variant": null }, "cpython-3.11.13-linux-x86_64_v4-musl": { @@ -16235,8 +16235,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "4b7dec009dbdfb4821aebdb5ca082ac7765ecdb67980dc86adebd57febaf1aec", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "f2ac3addbdf3c08ccf2320bdbed20213b45acd3399d44a990046f09dd883824e", "variant": null }, "cpython-3.11.13-windows-aarch64-none": { @@ -16251,8 +16251,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "d45d2a6009dc50a76e4630c39ea36ba85e51555b7a17e1683d1bcf01c3bf7e1a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "84058f18798534e76f6b9d15b96c41116aad0055e01c6e3ab2ab02db24826b9a", "variant": null }, "cpython-3.11.13-windows-i686-none": { @@ -16267,8 +16267,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "892f215501ae1cfe36e210224f4de106e5825f34f41ad8d458ef73f3012be61f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "8044a253950315481784b9f4764e1025b0d4a7a2760b7a82df849f4667113f80", "variant": null }, "cpython-3.11.13-windows-x86_64-none": { @@ -16283,8 +16283,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "d19baf214caf1ad3d1b34c6931dcd6d915abedd419ba4aecb0cacb7e1ec7884a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "43a574437fb7e11c439e13d84dd094fa25c741d32f9245c5ffc0e5f9523aafa9", "variant": null }, "cpython-3.11.13+debug-linux-aarch64-gnu": { @@ -16299,8 +16299,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "bf9e9c0295634d5ead7d3756651898d6af8d1bfdd8cc410769f9354d3e0871e4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "b6ca253ced82c9575935a32d327d29dcffa9cb15963b9331c621ac91aa151933", "variant": "debug" }, "cpython-3.11.13+debug-linux-armv7-gnueabi": { @@ -16315,8 +16315,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "c0a5f208bbb1d51dfc3e98919f7856ae3a5643d2e6a6b5edfcbfa7ea41bb822e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "3e02d8ff6b63bb83a9b4cbf428d75c90d06f79df211fa176d291f3864c1e77df", "variant": "debug" }, "cpython-3.11.13+debug-linux-armv7-gnueabihf": { @@ -16331,8 +16331,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "3d091a03c7d5fb47ac6050bffff371ce3904978ca3dc3c49f2bfacdc6b434a1d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "c7f9429f877d9e78a1b7e71c83b2beea38a727f239899ed325b3648e4e4cc1bf", "variant": "debug" }, "cpython-3.11.13+debug-linux-powerpc64le-gnu": { @@ -16347,8 +16347,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "5c2be36a8aa027761b6c5da5bc4bb7ef92c6a8fa70a166f45fcc6f1c8b78330c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "1f47dd100661489bf86befae148ce290009b91a7b62994f087136916ba4cfe4f", "variant": "debug" }, "cpython-3.11.13+debug-linux-riscv64-gnu": { @@ -16363,8 +16363,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "609cd34b0f86f576eec2e55a917d07e4d322e2c58309d6ae2243470207ed369b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "47c5cae609e683e59bf6aff225c06216305b939374476a4cf796d65888a00436", "variant": "debug" }, "cpython-3.11.13+debug-linux-s390x-gnu": { @@ -16379,8 +16379,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "dd849e7e5308066f03d1f2be307cdfd95d5c815aec9dc743bf53c98731005cd5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "7c16d22e0eeddfec0275f413ccca73c62ba55736230e889e5e78213e456bae1c", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64-gnu": { @@ -16395,8 +16395,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "6ed2ab536fce32ba93ddf3ea572c92aee3a5c12575f9096defbab858011a9810", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "22b0309a7232568c054790a23979f490143c2a65f5b4638b52ebfa2e02ad7b20", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64-musl": { @@ -16411,8 +16411,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "a4df9df180fa29800467eef491b3d22019aec3eca8160f9babd27b24cf6ebf39", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "6a3c83db95e39a68ace7515787be03e77993f023bb0c908eaed4cf79480f24d4", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v2-gnu": { @@ -16427,8 +16427,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "b27f28286c97e589521c496fe327e940c5ab99a406d652fe470008c2a525a159", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "0d7a5be35f70db94f151656a912fd66e0c001c515969007906b3f97c3fe46364", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v2-musl": { @@ -16443,8 +16443,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "9ffcf6f5b69805c47fb39c43810030cf1ff0fefab4b858734da75130f2184f7e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "7c4ae94fe3f488027f1a97f304ef4dbe2d83f4b97381b5d6dd5552ce01065027", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v3-gnu": { @@ -16459,8 +16459,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "c546e8dc6d21eb9e3fc8a849b67fe5564ebd69456c800e1e9ba685a6450e1db3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "5fec7d7868079bd9107c190a3187d3bffe8e3a0214d09f8ce7fbe02788f6030d", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v3-musl": { @@ -16475,8 +16475,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "190734e9714c4041a160d50240a1e5489fd416091bb2f4f0ae1e17e46a67f641", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "ac5f52aca1051354e336448634b8e544476198d1f8db73f0bcd6dff64267cf9e", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v4-gnu": { @@ -16491,8 +16491,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "629c39a382faed464041836b9299a2f3159e3cc5d07844f5cb5be8d579898166", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "467cee90b4081db0ddfef98e213bf9b69355068c2899853c7cf38bea44661fd5", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v4-musl": { @@ -16507,8 +16507,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.11.13%2B20250708-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "7909d1992f8bc7346b081f46a0d4c37e7ccabd041a947d89c17caa1cc497007b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "1ac6812cca22b1d3c70b932d5f6f6da0bc693a532e78132661f856bafcd40e2b", "variant": "debug" }, "cpython-3.11.12-darwin-aarch64-none": { @@ -20299,8 +20299,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "5076f23af532e6225b85106393a092c1e43c67605f5038a2687efe2608e999b0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "73939b9c93d50163cd0f1af8b3ce751c941a3a8d6eba9c08edcc9235dc5888c7", "variant": null }, "cpython-3.10.18-darwin-x86_64-none": { @@ -20315,8 +20315,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "8e9436c3aec957de1e79fd670b7c7801ad59f174a178a7e92964e4642ade8eda", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "1ba1523d81d042a516068b98ded99d3490d3f4bb6c214fc468b62dadde88e5ac", "variant": null }, "cpython-3.10.18-linux-aarch64-gnu": { @@ -20331,8 +20331,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "9e7581dc4e6e75135650551040d1ad9529bb1b7b2b6c2dbf9b80483507284a50", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "54c490a7f22ac03171334e5265081ca90d75ca0525b154b001f0ee96ad961c18", "variant": null }, "cpython-3.10.18-linux-armv7-gnueabi": { @@ -20347,8 +20347,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "973db52fb00257045a4d3ea13c59c50588bc6f708b0a0230a2adb2154f710009", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "56ca1369651cb56221053676d206aa675ee91ddad5de71cb8de7e357f213ff59", "variant": null }, "cpython-3.10.18-linux-armv7-gnueabihf": { @@ -20363,8 +20363,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "87368650aa19e173da8b365231f75f1584f2d9e8b95d763b9c47f7fc053a644a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "eacff45758c90b3cdd4456a31b1217d665e122df8b5a0b8b238efcc59b8d8867", "variant": null }, "cpython-3.10.18-linux-powerpc64le-gnu": { @@ -20379,8 +20379,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "cc3079246949bcef9be0118f58e6713fc8af2ba49927db015bc6f4d8fca6ab26", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "6e4180591050ec321a76ac278f9eab9c80017136293ce965229f3cbea3a1a855", "variant": null }, "cpython-3.10.18-linux-riscv64-gnu": { @@ -20395,8 +20395,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "73c6d8cf8eb865595ef232f5bb7d7a55cb0c861e2ee72a6b23e61409010bf6ee", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "ef176d45d3199989df3563e8a578fb00084190fa139ecc752debdee7d9acc77d", "variant": null }, "cpython-3.10.18-linux-s390x-gnu": { @@ -20411,8 +20411,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "668f8d911eec50bdd36996f3c0c098255fd90360e83d73efc383c136a93cbd30", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "f744cbebf0cc0236fd234aa99ae799105ed2edb0a01cf3fe9991d6dd85bd157c", "variant": null }, "cpython-3.10.18-linux-x86_64-gnu": { @@ -20427,8 +20427,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "c6e79f2c78b893339c4fbb4f337647f5e14d491ca2c05ecec8f78187bfd9480c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "ba282bc7e494c38c7f5483437fd1108e1d55f0b24effb3eb5b28e03966667d7c", "variant": null }, "cpython-3.10.18-linux-x86_64-musl": { @@ -20443,8 +20443,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "cb6f4ea6cb5eef904d5a8fb4bcfee77bc34bca4946f8a12bab70c103f503f676", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "0502186e5ccc85134a2c7d11913198eb5319477da1702deb5d4b89c3f692b166", "variant": null }, "cpython-3.10.18-linux-x86_64_v2-gnu": { @@ -20459,8 +20459,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "dbc05eadb1cdf504718688bb29367ab16fc0868c3b873031ea49b85e919a3bee", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "ddd7ff4a13131c29011dd508d2f398c95977dc5c055be891835a3aa12df7acfa", "variant": null }, "cpython-3.10.18-linux-x86_64_v2-musl": { @@ -20475,8 +20475,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "5c7ac0653d42d1ab391fec12c1f1f1d940c7ebe20013979d91d4651c3fcb62b9", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "feb3d0c6ddfa959948321d6ac3de32d5cde32fe50135862c65165c9415cafedf", "variant": null }, "cpython-3.10.18-linux-x86_64_v3-gnu": { @@ -20491,8 +20491,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "1199924aba81e7475479b9e709e91f5cbb5cf3dc269cc0c30c27cf25cbfe8f01", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "69c634bf5c979ca3d6fac7e5a34613915e55fc6671bfb0dee7470f3960a649ee", "variant": null }, "cpython-3.10.18-linux-x86_64_v3-musl": { @@ -20507,8 +20507,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "66a78c15f1f2cd0cfd0196edf323bdffe77481e6904751e125d4db23db78bad0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "dbe2e101bb60277ef0f9354b7f0b1aaa85b07dec3a12ca72ae133baa080deeca", "variant": null }, "cpython-3.10.18-linux-x86_64_v4-gnu": { @@ -20523,8 +20523,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "7c0aaa49f3a5b15689ae43d6cd4f418732ee95070aaa96dabf968bb3ac45b29e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "a6b2530a580061eb9d08168ac5e8808b8df1d2e7b8dd683c424b59cc9124a3a2", "variant": null }, "cpython-3.10.18-linux-x86_64_v4-musl": { @@ -20539,8 +20539,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "b14649f4bdb22cf8b2c3656034687b9854f0ad0489018a65a1d44e886a000e96", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "3a2abc86a8e740d4e7dddcd697781630d9d9e6ce538095b43a4789a531f8239b", "variant": null }, "cpython-3.10.18-windows-i686-none": { @@ -20555,8 +20555,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "e633c5093644502c477ba2391bde9bf23fb5d695aaa7de0e727b363592d81edf", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "1326fb50a7f39ff80b338a95c47acbeda30f484ee28ff168c3e395320345ee01", "variant": null }, "cpython-3.10.18-windows-x86_64-none": { @@ -20571,8 +20571,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "9b168333744e676d221d0e47b73328e38a78a080bbeff009db72d0eae201a3a7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "0dec10054eefa76d4e47e8f53d9993e51a6d76252d9f8e5162b1b9805e6ffc20", "variant": null }, "cpython-3.10.18+debug-linux-aarch64-gnu": { @@ -20587,8 +20587,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "2997824229577882eb7f0000118c93d0fb12f97bee10bd7c41ed46b7123c6d5d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "ed4d68544efef0d7c158c4464d8e3b4407a02e2ea014e76dfa65fddfd49384af", "variant": "debug" }, "cpython-3.10.18+debug-linux-armv7-gnueabi": { @@ -20603,8 +20603,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "5650962a60d540d9a71b6af917f78386ae69f4368f9b3537828b8368400aee8f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "39fdc60b2645262ef658ebbf5edfaffd655524855d3aa35bfb05a149a271e4f5", "variant": "debug" }, "cpython-3.10.18+debug-linux-armv7-gnueabihf": { @@ -20619,8 +20619,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "891540ab2a6e2534115787c95e06111176c2630dc261bad2169251924ec41fc6", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "cf0c02ab4b46c9b6a0854e5bd9da9b322d8d91ae5803190b798ff15cb25ab153", "variant": "debug" }, "cpython-3.10.18+debug-linux-powerpc64le-gnu": { @@ -20635,8 +20635,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "7266278b47151f48b7b57790cda43aeb12bb1a776711fbb552a60ace2d9e68fc", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "e9f346d7fa001e85cea92cf027b924c2095d54f7db297287b2df550f04e6c304", "variant": "debug" }, "cpython-3.10.18+debug-linux-riscv64-gnu": { @@ -20651,8 +20651,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "ba07bece860b8f98da3740860f4e91de18d0e05a30f1970203f0d5f98489210c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "c11eba8055c7bb643f55694fb1828d8d13e4ade2cb3ec60d8d9bb38fbf7500d8", "variant": "debug" }, "cpython-3.10.18+debug-linux-s390x-gnu": { @@ -20667,8 +20667,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "217a35c1c9ef9bfef37970587245ce06c3e63f92322b083e0baa7da2a82587cf", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "c7b407062dc86e011c2e3d8f5f0e1db8d8eac3124e4d0b597f561d7f7b2a8723", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64-gnu": { @@ -20683,8 +20683,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "1d485c1882d0ecefe858ef8db3864fb6b91a938941f3d7350c06f3b6a03734db", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "1ba2a0159629d92207966cbf2038774afd0f78cc59e94efb8a86e88a32563bdd", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64-musl": { @@ -20699,8 +20699,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "cdbead37d85fff493e6eb3e6adf3d6935a721315b4711666db56d157e796396b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "ebee02e3380e50e394962697dc4d4c845f60ac356da88f671be563ef0dafaa9b", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v2-gnu": { @@ -20715,8 +20715,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "5ae93dac6ae65c7f13c355ce1fe28b78a0a9b272c428bb27f5dbf2a357275bc2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "4de984931af2c4a2b18139ff123843671c5037900524065c2fef26ff3d1a5771", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v2-musl": { @@ -20731,8 +20731,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "a588754cd0e959123c5beedd1d50cc849f8c3bed4908174a6f55730951a10241", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "fd97d5565e0fb98ad78db65f107789e287f84c53f4d9f3ccb37fdd5f3849288b", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v3-gnu": { @@ -20747,8 +20747,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "65976255591b39e428ae750050e398521a32bcdefb96053dd2cf9007165411da", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "ea450da681ab3fdef0da5181d90ebff7331ce1f7f827bb3b56657badc4127fad", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v3-musl": { @@ -20763,8 +20763,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "fc8ba366396b3e6b5aca7e3ba449ad094350a533f31a0c99c6ed1ac0d41ef7d2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "ff9fe8b880460ce9529db369e2becca20a7e6a042df2deba2277e35c5cdcd35a", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v4-gnu": { @@ -20779,8 +20779,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "2bf6024c48b82b667dc3bab77d9ff143ac3983e75be94c32cdc22b9cd7e50d15", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "c1a1d9661cf1d45096478fefd1e70ff6d0cbc419194cf094414d24fa336f5116", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v4-musl": { @@ -20795,8 +20795,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.10.18%2B20250708-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "41696205b706ea5b0ef89eefd695bfe87f44dae57f9318711892b1ceb144cff7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "2bf809a85ffc45a37b32d5107f1a3ee8a6d12f07bb5fd3ad26ba16501418a8a7", "variant": "debug" }, "cpython-3.10.17-darwin-aarch64-none": { @@ -25739,8 +25739,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "aff1156fa5be26caf1ac2d4029936eb9379dc4351bb1d32d2120b10f2ba61747", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "3ab0d1885fee62dadc1123f0b23814e51b6abe5dcf6182a0c9af6cfc69764741", "variant": null }, "cpython-3.9.23-darwin-x86_64-none": { @@ -25755,8 +25755,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "9de5325065b159e3e7daa53c133126df6b3eeed2316176d84e7761b01d16ba7f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "0fbb8bcc5d203b83ba1e63f9b8b1debe9162c22dd0f7481543f310b298255d6a", "variant": null }, "cpython-3.9.23-linux-aarch64-gnu": { @@ -25771,8 +25771,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "51fe6b026253b9f9c83205d1907572d7618ea47216e40a351d30eaa55f879c3e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "da2e4a73d7318241031d87da2acb7da99070f94d715b8c9f8c973a5d586b20a6", "variant": null }, "cpython-3.9.23-linux-armv7-gnueabi": { @@ -25787,8 +25787,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "1faeec85e15cd17acb90683bc42cc8bccdb5250816501863d3407713deb6215e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "41599a37d0f6fa48b44183d15a7c98a299839b83fa28774ff3f01d28500da9a6", "variant": null }, "cpython-3.9.23-linux-armv7-gnueabihf": { @@ -25803,8 +25803,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "08261e7a2328c989409a7f0f4574bfca84adfab7e5db6556209642ebba55de5e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "2263daa7d9cda3e53449091dc86aa7931409721031bad1a1a160b214777c5cd6", "variant": null }, "cpython-3.9.23-linux-powerpc64le-gnu": { @@ -25819,8 +25819,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "2ab4c6c616b23b2220829420028f90d0aa4f767ae60fcdf5d2edff08644bb5af", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "fc068ac5cf5e4effc74e2b63e34c2618e5a838737a19ca8f7f17cc2f10e44f26", "variant": null }, "cpython-3.9.23-linux-riscv64-gnu": { @@ -25835,8 +25835,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "bea6c21421b016ca03e786f0fb91a03cc9d3f39aa8069785632efe3666e90df5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "5475f1106abed1b1163fa7964f8f8e834cbdafc26ddb9ab79cc5c10fb8110457", "variant": null }, "cpython-3.9.23-linux-s390x-gnu": { @@ -25851,8 +25851,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "6715a5b8af51e76929c1f7a81c9085053243d2b4025bac29f8ec18301766d795", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "2d571c79b0722488b4980badb163ebd83e48b02b5a125239c67239df8dd37476", "variant": null }, "cpython-3.9.23-linux-x86_64-gnu": { @@ -25867,8 +25867,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "ad39b79d0168f0f7cc5dbe14d99ff8d1068077f15cc2b03456fe3364630157e8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "7932256affbd8fe7e055fb54715dae47e4557919bfe84bb8f33260a7a792633a", "variant": null }, "cpython-3.9.23-linux-x86_64-musl": { @@ -25883,8 +25883,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "977af02740232123c385e7f8e70eb8acdcf8ffd4126526f9d3d8cb1bd20fd669", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "64c4bb8c76b50f264a6900f3391156efd0c39ad75447f1b561aa0b150069e361", "variant": null }, "cpython-3.9.23-linux-x86_64_v2-gnu": { @@ -25899,8 +25899,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "ffbb92f9213591ab7b253c89d34218c3adab25327668b89bc6120038cc2b0a37", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "c2bdab1548c60ed0bda4c69bea6dd17569c1d681065ed5ec5395175ed165f47a", "variant": null }, "cpython-3.9.23-linux-x86_64_v2-musl": { @@ -25915,8 +25915,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "e53121074856e6ef4e8f3a865c2848d4287431a1d0ceef21fd389cc39649f917", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "61b59f2c19575acd088e1d63ca95e810e8e2b1af20f37d7acebf90f864c22ca4", "variant": null }, "cpython-3.9.23-linux-x86_64_v3-gnu": { @@ -25931,8 +25931,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "1856f202d42555e8e8709db0291bbfac5a896724734314746ef20c014cca8552", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "f791037703a7370783c853bb406034532599ff561dfbf5bc67d44323d131b3c3", "variant": null }, "cpython-3.9.23-linux-x86_64_v3-musl": { @@ -25947,8 +25947,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "94f94fa20477b5088a147936c565c2b0a5a18e353d954ad6bbd5048e933d9a67", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "88c3ad43158942c232039752e4d269cd89e282795e4c7f863f76f3e307b852f4", "variant": null }, "cpython-3.9.23-linux-x86_64_v4-gnu": { @@ -25963,8 +25963,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "55209fe80fac7837837c5b4d310e71e1de822ca413465bf7589fabae5dd9ba7a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "0a71dcb46a9ff949f7672f65090d210ee79d80846f10629e3f234eb7f5fe58e8", "variant": null }, "cpython-3.9.23-linux-x86_64_v4-musl": { @@ -25979,8 +25979,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "41e1237774abf02a8c3b33c365d959ba8529f6a845d93789e3fe7ba4203fb8c2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "cd574a9a36a729aa964e1c52bb3084a36350d905c4d16427d85dd3f80e1b3dcd", "variant": null }, "cpython-3.9.23-windows-i686-none": { @@ -25995,8 +25995,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "f8d558d6d260cc970f02e04f5b6555acd5148b1b2bef25d2c945ab2b8dfd3ce2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "f5b6a6185ed80463160cbd95e520d8d741873736d816ac314d3e08d61f4df222", "variant": null }, "cpython-3.9.23-windows-x86_64-none": { @@ -26011,8 +26011,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "3a150e1126b1b7645a95ba06992d886cd03dab524d7c2660bd94bcf51f499fa1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "a8f80f8da7901fba2b271cdc5351a79b3d12fd95ee50cc4fe78410dc693eb150", "variant": null }, "cpython-3.9.23+debug-linux-aarch64-gnu": { @@ -26027,8 +26027,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "51cfb2db5abdd1e10d2998289fbf3235352a61b4b6a3ef8ac4fbf4252ae09c78", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "c00ba3d83356c187e39c9d6b1541733299a675663690dc1b49c62a152d2db191", "variant": "debug" }, "cpython-3.9.23+debug-linux-armv7-gnueabi": { @@ -26043,8 +26043,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "369a0f68be191dbb45a3ca173c9589d77f973be3552f08225d03f5e013795d25", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "eb4875c6220036fd1b40af4d885823057122d61fc60f0b2c364065259adad0cc", "variant": "debug" }, "cpython-3.9.23+debug-linux-armv7-gnueabihf": { @@ -26059,8 +26059,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "0821af742c0187823ae3194c53b7590e7bf0524a14b94580300391e0b13bdd8a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "eca68cac8c0880f08de5c1bcae91ff0bd7fe64e5788a433fc182a5e037af671c", "variant": "debug" }, "cpython-3.9.23+debug-linux-powerpc64le-gnu": { @@ -26075,8 +26075,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "45525a2d123981cb56f5fe4cd87e9bbe18c3fffe6b778313e8ef76f864315513", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "5ffc8d84b6098cfa5e2e3aaedcc3e130809d5caa1958d5155995ed3df15d8cc7", "variant": "debug" }, "cpython-3.9.23+debug-linux-riscv64-gnu": { @@ -26091,8 +26091,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "9280d5f805d1f1ff992657af852a343f90cdaf7ef40287b55f48a73e409a4fe3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "d7f38d5539d7a0b15ce6071ba3290ce1a4ac2da3bd490d023b4d7b36c6c33c89", "variant": "debug" }, "cpython-3.9.23+debug-linux-s390x-gnu": { @@ -26107,8 +26107,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "340c153709d2d428d0604802983bd017079ea95f48ccbb8877e08c87b8c93f4f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "14250195a8c4c42fa9b22e7ca70ac5be3fe5e0ca81239c0672043eddeb6bb96e", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64-gnu": { @@ -26123,8 +26123,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "e63909ea5cf383db126d5af9c3ba09fc68868104cf8db265723ad1220a5fafae", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "846ad94f04ca8413762e6cfaee752156bbaa75f3ec030bcc235453f708e3577c", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64-musl": { @@ -26139,8 +26139,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "1f58c434a2772e136506e517e412cc450359807a32742064d9ef3ec18ae1ef3e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "4ef30683e0dd6a08a6ef591ab37a218baa42a7352f5c3951131538ab0ef83865", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v2-gnu": { @@ -26155,8 +26155,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "6702268ce25da3f547ed1f48ee20144d0cdc1db967a467f25d097f43cb52a25e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "8964daf898c112bc5caa9499e8d1ba4c0d82911b4c3e07044c7f5abf489b97c6", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v2-musl": { @@ -26171,8 +26171,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "606eeb49821a06fb874527494f6493606e5f837cf56dba8235e75149ec53297b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "868f2f3e994992a1b68eb051fa2678a2e57bbbe1fcfc9f48461b0d2d87c5b6a8", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v3-gnu": { @@ -26187,8 +26187,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "11dcf8d92a18e609f32750ceb758a65855505a79907302142c8b70785c5c9a03", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "1616c6f535b6edf4160ee97b9beca8146f9cd77a4de8c240a0a3f095a09795e9", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v3-musl": { @@ -26203,8 +26203,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "d246a1a69cee5ec4bf467fb1ea42f6218925d3047afd3817b34fc3f8ad199200", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "1f9d7987734042d04badc60686f5503eb373ea8b7b7f3ade6a58a37f7d808265", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v4-gnu": { @@ -26219,8 +26219,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "05b81fde271d35e97d5e411a2d9e232baa424a55c8ea6e09a15e1606c08833f4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "4b8f925b20b6b74c1eb48fa869ee79cde20745fb93c83776e5c71924448e7e53", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v4-musl": { @@ -26235,8 +26235,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250708/cpython-3.9.23%2B20250708-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "33e7411e88033865e8a4e9c995112cb3867f284102624b3ce1dbcdb4f4c03ea3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "ecab1905698e5dd4a11c46a1dc6be49cf0e37f70b81191adbb7dad6e453906cb", "variant": "debug" }, "cpython-3.9.22-darwin-aarch64-none": { From 4175e3eb4d7e484004d3eba6f0ecaded5810e03a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 13 Jul 2025 08:20:51 -0500 Subject: [PATCH 195/349] Sync latest Python releases (#14581) 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 b9f58dd92..8357ee7fb 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/20250708/cpython-unix/targets.yml"; +const TARGETS_YML_URL: &str = "https://raw.githubusercontent.com/astral-sh/python-build-standalone/refs/tags/20250712/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 54170aba5..646501b07 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 a57241c0d7b7412a3f2f7c9fadbb0bf55c50daea Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 13 Jul 2025 21:10:28 -0400 Subject: [PATCH 196/349] Update pre-commit hook astral-sh/ruff-pre-commit to v0.12.3 (#14592) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1c8965c0f..5476c9dc8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -42,7 +42,7 @@ repos: types_or: [yaml, json5] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.2 + rev: v0.12.3 hooks: - id: ruff-format - id: ruff From e9509fde84ea84ea8d588c5a47eff1810e6b2f43 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 15:42:56 +0200 Subject: [PATCH 197/349] Update Rust crate clap to v4.5.41 (#14593) 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 | |---|---|---|---| | [clap](https://redirect.github.com/clap-rs/clap) | workspace.dependencies | patch | `4.5.40` -> `4.5.41` | --- > [!WARNING] > Some dependencies could not be looked up. Check the Dependency Dashboard for more information. --- ### Release Notes
    clap-rs/clap (clap) ### [`v4.5.41`](https://redirect.github.com/clap-rs/clap/blob/HEAD/CHANGELOG.md#4541---2025-07-09) [Compare Source](https://redirect.github.com/clap-rs/clap/compare/v4.5.40...v4.5.41) ##### Features - Add `Styles::context` and `Styles::context_value` to customize the styling of `[default: value]` like notes in the `--help`
    --- ### Configuration 📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC), 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 | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bc42e30af..4079390e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -619,9 +619,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.40" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" +checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" dependencies = [ "clap_builder", "clap_derive", @@ -629,9 +629,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.40" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" +checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" dependencies = [ "anstream", "anstyle", @@ -672,9 +672,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.40" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" +checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" dependencies = [ "heck", "proc-macro2", @@ -761,7 +761,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] @@ -1138,7 +1138,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1966,7 +1966,7 @@ checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" dependencies = [ "hermit-abi 0.4.0", "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2026,7 +2026,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2885,7 +2885,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3317,7 +3317,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3330,7 +3330,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.2", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3913,7 +3913,7 @@ dependencies = [ "getrandom 0.3.1", "once_cell", "rustix 1.0.7", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -6293,7 +6293,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] From 4c40dd341e68dfd69073cb4e7cc36cf1dcdb04c7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 15:45:56 +0200 Subject: [PATCH 198/349] Update Rust crate hyper-util to v0.1.15 (#14595) 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 | |---|---|---|---| | [hyper-util](https://hyper.rs) ([source](https://redirect.github.com/hyperium/hyper-util)) | dev-dependencies | patch | `0.1.14` -> `0.1.15` | --- > [!WARNING] > Some dependencies could not be looked up. Check the Dependency Dashboard for more information. --- ### Release Notes
    hyperium/hyper-util (hyper-util) ### [`v0.1.15`](https://redirect.github.com/hyperium/hyper-util/blob/HEAD/CHANGELOG.md#0115-2025-07-07) [Compare Source](https://redirect.github.com/hyperium/hyper-util/compare/v0.1.14...v0.1.15) - Add header casing options to `auto::Builder`. - Fix `proxy::Socksv5` to check for enough bytes before parsing ipv6 responses. - Fix including `client-proxy` in the `full` feature set.
    --- ### Configuration 📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC), 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 4079390e9..f2ef0b4a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1703,9 +1703,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" +checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" dependencies = [ "base64 0.22.1", "bytes", From ef7ab76206e96bbc531a5ee9c065f6a2462cb651 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 15:48:47 +0200 Subject: [PATCH 199/349] Update Rust crate codspeed-criterion-compat to v3.0.3 (#14594) 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 | |---|---|---|---| | [codspeed-criterion-compat](https://codspeed.io) ([source](https://redirect.github.com/CodSpeedHQ/codspeed-rust)) | dependencies | patch | `3.0.2` -> `3.0.3` | --- > [!WARNING] > Some dependencies could not be looked up. Check the Dependency Dashboard for more information. --- ### Release Notes
    CodSpeedHQ/codspeed-rust (codspeed-criterion-compat) ### [`v3.0.3`](https://redirect.github.com/CodSpeedHQ/codspeed-rust/releases/tag/v3.0.3) [Compare Source](https://redirect.github.com/CodSpeedHQ/codspeed-rust/compare/v3.0.2...v3.0.3) #### What's Changed - tests: cargo-bench should work with the compat layers by [@​art049](https://redirect.github.com/art049) in [https://github.com/CodSpeedHQ/codspeed-rust/pull/110](https://redirect.github.com/CodSpeedHQ/codspeed-rust/pull/110) - fix: handle rustflags from .cargo/config.toml by [@​GuillaumeLagrange](https://redirect.github.com/GuillaumeLagrange) in [https://github.com/CodSpeedHQ/codspeed-rust/pull/109](https://redirect.github.com/CodSpeedHQ/codspeed-rust/pull/109) **Full Changelog**: https://github.com/CodSpeedHQ/codspeed-rust/compare/v3.0.2...v3.0.3
    --- ### Configuration 📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC), 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 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f2ef0b4a3..53bd78f42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -690,9 +690,9 @@ checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "codspeed" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "922018102595f6668cdd09c03f4bff2d951ce2318c6dca4fe11bdcb24b65b2bf" +checksum = "a7524e02ff6173bc143d9abc01b518711b77addb60de871bbe5686843f88fb48" dependencies = [ "anyhow", "bincode", @@ -708,9 +708,9 @@ dependencies = [ [[package]] name = "codspeed-criterion-compat" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d8ad82d2383cb74995f58993cbdd2914aed57b2f91f46580310dd81dc3d05a" +checksum = "2f71662331c4f854131a42b95055f3f8cbca53640348985f699635b1f96d8c26" dependencies = [ "codspeed", "codspeed-criterion-compat-walltime", @@ -719,9 +719,9 @@ dependencies = [ [[package]] name = "codspeed-criterion-compat-walltime" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61badaa6c452d192a29f8387147888f0ab358553597c3fe9bf8a162ef7c2fa64" +checksum = "e3c9bd9e895e0aa263d139a8b5f58a4ea4abb86d5982ec7f58d3c7b8465c1e01" dependencies = [ "anes", "cast", From d179c496ddbbc2c8434608dc60da1bbd9f18c30b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 15:49:11 +0200 Subject: [PATCH 200/349] Update Rust crate spdx to v0.10.9 (#14596) 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 | |---|---|---|---| | [spdx](https://redirect.github.com/EmbarkStudios/spdx) | workspace.dependencies | patch | `0.10.8` -> `0.10.9` | --- > [!WARNING] > Some dependencies could not be looked up. Check the Dependency Dashboard for more information. --- ### Release Notes
    EmbarkStudios/spdx (spdx) ### [`v0.10.9`](https://redirect.github.com/EmbarkStudios/spdx/blob/HEAD/CHANGELOG.md#0109---2025-07-12) [Compare Source](https://redirect.github.com/EmbarkStudios/spdx/compare/0.10.8...0.10.9) ##### Changed - [PR#74](https://redirect.github.com/EmbarkStudios/spdx/pull/76) update SPDX license list to 3.27.0.
    --- ### Configuration 📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC), 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 53bd78f42..5dbdfaf65 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3729,9 +3729,9 @@ dependencies = [ [[package]] name = "spdx" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58b69356da67e2fc1f542c71ea7e654a361a79c938e4424392ecf4fa065d2193" +checksum = "c3e17e880bafaeb362a7b751ec46bdc5b61445a188f80e0606e68167cd540fa3" dependencies = [ "smallvec", ] From 9efd053d27882cdb763057f368b5f85b7448af67 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Mon, 14 Jul 2025 08:56:39 -0500 Subject: [PATCH 201/349] Add test case for `uv tool` Python re-resolves (#14605) A test case for https://github.com/astral-sh/uv/pull/10401 and https://github.com/astral-sh/uv/pull/14606 --- crates/uv/tests/it/tool_run.rs | 70 ++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/crates/uv/tests/it/tool_run.rs b/crates/uv/tests/it/tool_run.rs index fb6287454..8bcf5c3d1 100644 --- a/crates/uv/tests/it/tool_run.rs +++ b/crates/uv/tests/it/tool_run.rs @@ -2975,3 +2975,73 @@ fn tool_run_windows_runnable_types() -> anyhow::Result<()> { Ok(()) } + +#[test] +fn tool_run_reresolve_python() -> anyhow::Result<()> { + let context = TestContext::new_with_versions(&["3.11", "3.12"]).with_filtered_counts(); + let tool_dir = context.temp_dir.child("tools"); + let bin_dir = context.temp_dir.child("bin"); + let foo_dir = context.temp_dir.child("foo"); + let foo_pyproject_toml = foo_dir.child("pyproject.toml"); + + foo_pyproject_toml.write_str(indoc! { r#" + [project] + name = "foo" + version = "1.0.0" + requires-python = ">=3.12" + dependencies = [] + + [project.scripts] + foo = "foo:run" + "# + })?; + let foo_project_src = foo_dir.child("src"); + let foo_module = foo_project_src.child("foo"); + let foo_init = foo_module.child("__init__.py"); + foo_init.write_str(indoc! { r#" + import sys + + def run(): + print(".".join(str(key) for key in sys.version_info[:2])) + "# + })?; + + // Although 3.11 is first on the path, we'll re-resolve with 3.12 because the `requires-python` + // is not compatible with 3.11. + uv_snapshot!(context.filters(), context.tool_run() + .arg("--from") + .arg("./foo") + .arg("foo") + .env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str()) + .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r" + success: true + exit_code: 0 + ----- stdout ----- + 3.12 + + ----- stderr ----- + Resolved [N] packages in [TIME] + Prepared [N] packages in [TIME] + Installed [N] packages in [TIME] + + foo==1.0.0 (from file://[TEMP_DIR]/foo) + "); + + uv_snapshot!(context.filters(), context.tool_run() + .arg("--from") + .arg("./foo") + .arg("--python") + .arg("3.11") + .arg("foo") + .env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str()) + .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r" + success: true + exit_code: 0 + ----- stdout ----- + 3.12 + + ----- stderr ----- + Resolved [N] packages in [TIME] + "); + + Ok(()) +} From 3b050b554519f2bbe94a5987aaa87d097dfca5b5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 09:58:55 -0400 Subject: [PATCH 202/349] Update Rust crate tokio to v1.46.1 (#14599) --- Cargo.lock | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5dbdfaf65..8232c6905 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1942,6 +1942,17 @@ dependencies = [ "similar", ] +[[package]] +name = "io-uring" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "libc", +] + [[package]] name = "ipnet" version = "2.11.0" @@ -4134,17 +4145,19 @@ source = "git+https://github.com/astral-sh/tl.git?rev=6e25b2ee2513d75385101a8ff9 [[package]] name = "tokio" -version = "1.45.1" +version = "1.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", + "slab", "socket2", "tokio-macros", "windows-sys 0.52.0", From 0af025eafbf24215898628d7682f8c787356868d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 08:59:09 -0500 Subject: [PATCH 203/349] Update CodSpeedHQ/action action to v3.7.0 (#14597) 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 | |---|---|---|---| | [CodSpeedHQ/action](https://redirect.github.com/CodSpeedHQ/action) | action | minor | `v3.5.0` -> `v3.7.0` | --- > [!WARNING] > Some dependencies could not be looked up. Check the Dependency Dashboard for more information. --- ### Release Notes
    CodSpeedHQ/action (CodSpeedHQ/action) ### [`v3.7.0`](https://redirect.github.com/CodSpeedHQ/action/releases/tag/v3.7.0) [Compare Source](https://redirect.github.com/CodSpeedHQ/action/compare/v3.6.1...v3.7.0) #### What's Changed ##### 🚀 Features - Add pre- and post-benchmark scripts by [@​not-matthias](https://redirect.github.com/not-matthias) - Add cli args for perf by [@​not-matthias](https://redirect.github.com/not-matthias) in [#​94](https://redirect.github.com/CodSpeedHQ/runner/pull/94) ##### 🐛 Bug Fixes - Forward environment to systemd-run cmd by [@​not-matthias](https://redirect.github.com/not-matthias) - Only panic in upload for non-existing integration by [@​not-matthias](https://redirect.github.com/not-matthias) - Multi-line commands in valgrind by [@​not-matthias](https://redirect.github.com/not-matthias) - Symlink libpython doesn't work for statically linked python by [@​not-matthias](https://redirect.github.com/not-matthias) in [#​89](https://redirect.github.com/CodSpeedHQ/runner/pull/89) - Run perf with sudo; support systemd-run for non-perf walltime by [@​not-matthias](https://redirect.github.com/not-matthias) - Use correct path for unwind info by [@​not-matthias](https://redirect.github.com/not-matthias) ##### ⚙️ Internals - Add executor tests by [@​not-matthias](https://redirect.github.com/not-matthias) in [#​95](https://redirect.github.com/CodSpeedHQ/runner/pull/95) - Add log to detect invalid origin url by [@​not-matthias](https://redirect.github.com/not-matthias) - Upgrade to edition 2024 by [@​not-matthias](https://redirect.github.com/not-matthias) - Add debug logs for proc maps by [@​not-matthias](https://redirect.github.com/not-matthias) in [#​88](https://redirect.github.com/CodSpeedHQ/runner/pull/88) - Enhance version resolution with 'latest' support and flexible formats by [@​art049](https://redirect.github.com/art049) in [https://github.com/CodSpeedHQ/action/pull/132](https://redirect.github.com/CodSpeedHQ/action/pull/132) **Full Changelog**: https://github.com/CodSpeedHQ/action/compare/v3.6.1...v3.7.0 **Full Runner Changelog**: https://github.com/CodSpeedHQ/runner/blob/main/CHANGELOG.md ### [`v3.6.1`](https://redirect.github.com/CodSpeedHQ/action/releases/tag/v3.6.1) [Compare Source](https://redirect.github.com/CodSpeedHQ/action/compare/v3.5.0...v3.6.1) ##### What's Changed ##### 🚀 Features - Allow setting upload url via env var for convenience by [@​GuillaumeLagrange](https://redirect.github.com/GuillaumeLagrange) in [#​85](https://redirect.github.com/CodSpeedHQ/runner/pull/85) - Send unknown cpu\_brand when it is not recognized by [@​adriencaccia](https://redirect.github.com/adriencaccia) - Allow only running the benchmarks, and only uploading the results by [@​GuillaumeLagrange](https://redirect.github.com/GuillaumeLagrange) in [#​81](https://redirect.github.com/CodSpeedHQ/runner/pull/81) - Install perf on setup by [@​not-matthias](https://redirect.github.com/not-matthias) - Add perf integration for python by [@​not-matthias](https://redirect.github.com/not-matthias) - Add perf integration for rust by [@​not-matthias](https://redirect.github.com/not-matthias) - Add fifo ipc by [@​not-matthias](https://redirect.github.com/not-matthias) - Use custom time formatting to be in line with the rest of CodSpeed by [@​GuillaumeLagrange](https://redirect.github.com/GuillaumeLagrange) in [#​77](https://redirect.github.com/CodSpeedHQ/runner/pull/77) - Output information about benches after a local run by [@​GuillaumeLagrange](https://redirect.github.com/GuillaumeLagrange) in [#​76](https://redirect.github.com/CodSpeedHQ/runner/pull/76) - Allow specifying oauth token through CLI by [@​GuillaumeLagrange](https://redirect.github.com/GuillaumeLagrange) in [#​75](https://redirect.github.com/CodSpeedHQ/runner/pull/75) - Add option to output structured json by [@​GuillaumeLagrange](https://redirect.github.com/GuillaumeLagrange) in [#​74](https://redirect.github.com/CodSpeedHQ/runner/pull/74) - Add flags to specify repository from CLI by [@​GuillaumeLagrange](https://redirect.github.com/GuillaumeLagrange) - Improve error handling for valgrind by [@​not-matthias](https://redirect.github.com/not-matthias) in [#​67](https://redirect.github.com/CodSpeedHQ/runner/pull/67) - Handle local run failure by [@​adriencaccia](https://redirect.github.com/adriencaccia) in [#​71](https://redirect.github.com/CodSpeedHQ/runner/pull/71) - Run benchmark with systemd (for optional cpu isolation) by [@​not-matthias](https://redirect.github.com/not-matthias) in [#​86](https://redirect.github.com/CodSpeedHQ/runner/pull/86) ##### 🐛 Bug Fixes - Persist logs when running with skip\_upload by [@​GuillaumeLagrange](https://redirect.github.com/GuillaumeLagrange) in [#​84](https://redirect.github.com/CodSpeedHQ/runner/pull/84) - Valgrind crash for unresolved libpython by [@​not-matthias](https://redirect.github.com/not-matthias) in [#​82](https://redirect.github.com/CodSpeedHQ/runner/pull/82) - Support trailing slash in origin url by [@​not-matthias](https://redirect.github.com/not-matthias) in [#​83](https://redirect.github.com/CodSpeedHQ/runner/pull/83) - Use bash to ensure correct behavior across systems by [@​not-matthias](https://redirect.github.com/not-matthias) - Fix test randomly failing due to other test run in parallel by [@​GuillaumeLagrange](https://redirect.github.com/GuillaumeLagrange) - Check child status code after valgrind by [@​not-matthias](https://redirect.github.com/not-matthias) in [#​72](https://redirect.github.com/CodSpeedHQ/runner/pull/72) - Only show perf output at debug or trace level by [@​not-matthias](https://redirect.github.com/not-matthias) in [#​87](https://redirect.github.com/CodSpeedHQ/runner/pull/87) ##### ⚙️ Internals - Dont use regex in perf map harvest by [@​not-matthias](https://redirect.github.com/not-matthias) - Switch to astral-sh/cargo-dist by [@​adriencaccia](https://redirect.github.com/adriencaccia) in [#​80](https://redirect.github.com/CodSpeedHQ/runner/pull/80) **Full Changelog**: https://github.com/CodSpeedHQ/action/compare/v3.5.0...v3.6.1 **Full Runner Changelog**: https://github.com/CodSpeedHQ/runner/blob/main/CHANGELOG.md
    --- ### Configuration 📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC), 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 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ba7a4b4d1..0ccc9ea4e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2532,7 +2532,7 @@ jobs: run: cargo codspeed build --profile profiling --features codspeed -p uv-bench - name: "Run benchmarks" - uses: CodSpeedHQ/action@0010eb0ca6e89b80c88e8edaaa07cfe5f3e6664d # v3.5.0 + uses: CodSpeedHQ/action@c28fe9fbe7d57a3da1b7834ae3761c1d8217612d # v3.7.0 with: run: cargo codspeed run token: ${{ secrets.CODSPEED_TOKEN }} @@ -2569,7 +2569,7 @@ jobs: run: cargo codspeed build --profile profiling --features codspeed -p uv-bench - name: "Run benchmarks" - uses: CodSpeedHQ/action@0010eb0ca6e89b80c88e8edaaa07cfe5f3e6664d # v3.5.0 + uses: CodSpeedHQ/action@c28fe9fbe7d57a3da1b7834ae3761c1d8217612d # v3.7.0 with: run: cargo codspeed run token: ${{ secrets.CODSPEED_TOKEN }} From 4890f3ef2bbde7a2b36641dc1d8f92e7695f68ec Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Mon, 14 Jul 2025 09:07:30 -0500 Subject: [PATCH 204/349] Do not re-resolve with a new Python version in `uv tool` if it is incompatible with `--python` (#14606) Closes https://github.com/astral-sh/uv/issues/14604 --- crates/uv/src/commands/tool/common.rs | 22 +++++++++++----------- crates/uv/tests/it/tool_run.rs | 20 ++++++++++++++++++++ 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/crates/uv/src/commands/tool/common.rs b/crates/uv/src/commands/tool/common.rs index ffc1b5645..b24a64e25 100644 --- a/crates/uv/src/commands/tool/common.rs +++ b/crates/uv/src/commands/tool/common.rs @@ -98,14 +98,6 @@ pub(crate) async fn refine_interpreter( return Ok(None); } - // If the user passed a `--python` request, and the refined interpreter is incompatible, we - // can't use it. - if let Some(python_request) = python_request { - if !python_request.satisfied(interpreter, cache) { - return Ok(None); - } - } - // We want an interpreter that's as close to the required version as possible. If we choose the // "latest" Python, we risk choosing a version that lacks wheels for the tool's requirements // (assuming those requirements don't publish source distributions). @@ -135,15 +127,15 @@ pub(crate) async fn refine_interpreter( Bound::Unbounded => unreachable!("`requires-python` should never be unbounded"), }; - let python_request = PythonRequest::Version(VersionRequest::Range( + let requires_python_request = PythonRequest::Version(VersionRequest::Range( VersionSpecifiers::from_iter([lower_bound, upper_bound]), PythonVariant::default(), )); - debug!("Refining interpreter with: {python_request}"); + debug!("Refining interpreter with: {requires_python_request}"); let interpreter = PythonInstallation::find_or_download( - Some(&python_request), + Some(&requires_python_request), EnvironmentPreference::OnlySystem, python_preference, python_downloads, @@ -158,6 +150,14 @@ pub(crate) async fn refine_interpreter( .await? .into_interpreter(); + // If the user passed a `--python` request, and the refined interpreter is incompatible, we + // can't use it. + if let Some(python_request) = python_request { + if !python_request.satisfied(&interpreter, cache) { + return Ok(None); + } + } + Ok(Some(interpreter)) } diff --git a/crates/uv/tests/it/tool_run.rs b/crates/uv/tests/it/tool_run.rs index 8bcf5c3d1..90d906fb5 100644 --- a/crates/uv/tests/it/tool_run.rs +++ b/crates/uv/tests/it/tool_run.rs @@ -3026,6 +3026,7 @@ fn tool_run_reresolve_python() -> anyhow::Result<()> { + foo==1.0.0 (from file://[TEMP_DIR]/foo) "); + // When an incompatible Python version is explicitly requested, we should not re-resolve uv_snapshot!(context.filters(), context.tool_run() .arg("--from") .arg("./foo") @@ -3034,6 +3035,25 @@ fn tool_run_reresolve_python() -> anyhow::Result<()> { .arg("foo") .env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str()) .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + × No solution found when resolving tool dependencies: + ╰─▶ Because the current Python version (3.11.[X]) does not satisfy Python>=3.12 and foo==1.0.0 depends on Python>=3.12, we can conclude that foo==1.0.0 cannot be used. + And because only foo==1.0.0 is available and you require foo, we can conclude that your requirements are unsatisfiable. + "); + + // Unless the discovered interpreter is compatible with the request + uv_snapshot!(context.filters(), context.tool_run() + .arg("--from") + .arg("./foo") + .arg("--python") + .arg(">=3.11") + .arg("foo") + .env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str()) + .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r" success: true exit_code: 0 ----- stdout ----- From 852aba4f90988b7bd437573b721062228d504b49 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 16:09:06 +0200 Subject: [PATCH 205/349] Update Rust crate indicatif to 0.18.0 (#14598) 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 | |---|---|---|---| | [indicatif](https://redirect.github.com/console-rs/indicatif) | workspace.dependencies | minor | `0.17.8` -> `0.18.0` | --- > [!WARNING] > Some dependencies could not be looked up. Check the Dependency Dashboard for more information. --- ### Release Notes
    console-rs/indicatif (indicatif) ### [`v0.18.0`](https://redirect.github.com/console-rs/indicatif/releases/tag/0.18.0) [Compare Source](https://redirect.github.com/console-rs/indicatif/compare/0.17.12...0.18.0) Unfortunately [0.17.12](https://redirect.github.com/console-rs/indicatif/releases/0.17.12) had to be yanked because the console upgrade was a semver-incompatible change. Rerelease as 0.18.0 instead. #### What's Changed - Bump version to 0.18.0 by [@​djc](https://redirect.github.com/djc) in [https://github.com/console-rs/indicatif/pull/715](https://redirect.github.com/console-rs/indicatif/pull/715) ### [`v0.17.12`](https://redirect.github.com/console-rs/indicatif/releases/tag/0.17.12) [Compare Source](https://redirect.github.com/console-rs/indicatif/compare/0.17.11...0.17.12) #### What's Changed - Add ProgressBar::force\_draw by [@​jaheba](https://redirect.github.com/jaheba) in [https://github.com/console-rs/indicatif/pull/689](https://redirect.github.com/console-rs/indicatif/pull/689) - Use width to truncate `HumanFloatCount` values by [@​ReagentX](https://redirect.github.com/ReagentX) in [https://github.com/console-rs/indicatif/pull/696](https://redirect.github.com/console-rs/indicatif/pull/696) - `ProgressStyle` enable/disable colors based on draw target by [@​tonywu6](https://redirect.github.com/tonywu6) in [https://github.com/console-rs/indicatif/pull/699](https://redirect.github.com/console-rs/indicatif/pull/699) - Switch dep number\_prefix to unit\_prefix by [@​kimono-koans](https://redirect.github.com/kimono-koans) in [https://github.com/console-rs/indicatif/pull/709](https://redirect.github.com/console-rs/indicatif/pull/709) - draw\_target: inline the format arg to silence clippy by [@​chris-laplante](https://redirect.github.com/chris-laplante) in [https://github.com/console-rs/indicatif/pull/711](https://redirect.github.com/console-rs/indicatif/pull/711) - Upgrade to console 0.16 by [@​djc](https://redirect.github.com/djc) in [https://github.com/console-rs/indicatif/pull/712](https://redirect.github.com/console-rs/indicatif/pull/712)
    --- ### Configuration 📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC), 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 | 59 ++++++++++++++++++++++++++++++++++++------------------ Cargo.toml | 2 +- 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8232c6905..7f6b601ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -788,10 +788,22 @@ dependencies = [ "encode_unicode", "libc", "once_cell", - "unicode-width 0.2.1", "windows-sys 0.59.0", ] +[[package]] +name = "console" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e09ced7ebbccb63b4c65413d821f2e00ce54c5ca4514ddc6b3c892fdbcbc69d" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width 0.2.1", + "windows-sys 0.60.2", +] + [[package]] name = "core-foundation" version = "0.10.0" @@ -1910,14 +1922,14 @@ dependencies = [ [[package]] name = "indicatif" -version = "0.17.11" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" +checksum = "70a646d946d06bedbbc4cac4c218acf4bbf2d87757a784857025f4d447e4e1cd" dependencies = [ - "console", - "number_prefix", + "console 0.16.0", "portable-atomic", "unicode-width 0.2.1", + "unit-prefix", "web-time", ] @@ -1933,7 +1945,7 @@ version = "1.43.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "154934ea70c58054b556dd430b99a98c2a7ff5309ac9891597e339b5c28f4371" dependencies = [ - "console", + "console 0.15.11", "once_cell", "pest", "pest_derive", @@ -2465,12 +2477,6 @@ dependencies = [ "libc", ] -[[package]] -name = "number_prefix" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" - [[package]] name = "object" version = "0.36.7" @@ -4523,6 +4529,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" +[[package]] +name = "unit-prefix" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "323402cff2dd658f39ca17c789b502021b3f18707c91cdf22e3838e1b4023817" + [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -4631,7 +4643,7 @@ dependencies = [ "base64 0.22.1", "byteorder", "clap", - "console", + "console 0.15.11", "ctrlc", "dotenvy", "dunce", @@ -5038,7 +5050,7 @@ dependencies = [ name = "uv-console" version = "0.0.1" dependencies = [ - "console", + "console 0.15.11", ] [[package]] @@ -5675,7 +5687,7 @@ version = "0.1.0" dependencies = [ "anyhow", "configparser", - "console", + "console 0.15.11", "fs-err 3.1.1", "futures", "rustc-hash", @@ -6332,7 +6344,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f919aee0a93304be7f62e8e5027811bbba96bcb1de84d6618be56e43f8a32a1" dependencies = [ "windows-core 0.59.0", - "windows-targets 0.53.0", + "windows-targets 0.53.2", ] [[package]] @@ -6379,7 +6391,7 @@ dependencies = [ "windows-interface 0.59.1", "windows-result 0.3.4", "windows-strings 0.3.1", - "windows-targets 0.53.0", + "windows-targets 0.53.2", ] [[package]] @@ -6550,6 +6562,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -6583,9 +6604,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.0" +version = "0.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" dependencies = [ "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", diff --git a/Cargo.toml b/Cargo.toml index ecdc11701..3405cff53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -116,7 +116,7 @@ home = { version = "0.5.9" } html-escape = { version = "0.2.13" } http = { version = "1.1.0" } indexmap = { version = "2.5.0" } -indicatif = { version = "0.17.8" } +indicatif = { version = "0.18.0" } indoc = { version = "2.0.5" } itertools = { version = "0.14.0" } jiff = { version = "0.2.0", features = ["serde"] } From df44199ceb4c856142bb67e060f464ba5c06d72e Mon Sep 17 00:00:00 2001 From: Geoffrey Thomas Date: Mon, 14 Jul 2025 10:42:35 -0400 Subject: [PATCH 206/349] Add an exception handler on Windows (#14582) We've seen a few cases of uv.exe exiting with an exception code as its exit status and no user-visible output (#14563 in the field, and #13812 in CI). It seems that recent versions of Windows no longer show dialog boxes on access violations (what UNIX calls segfaults) or similar errors. Something is probably sent to Windows Error Reporting, and we can maybe sign up to get the crashes from Microsoft, but the user experience of seeing uv exit with no output is poor, both for end users and during development. While it's possible to opt out of this behavior or set up a debugger, this isn't the default configuration. (See https://superuser.com/q/1246626 for some pointers.) In order to get some output on a crash, we need to install our own default handler for unhandled exceptions (or call all our code inside a Structured Exception Handling __try/__catch block, which is complicated on Rust). This is the moral equivalent of a segfault handler on Windows; the kernel creates a new stack frame and passes arguments to it with some processor state. This commit adds a relatively simple exception handler that leans on Rust's own backtrace implementation and also displays some minimal information from the exception itself. This should be enough info to communicate that something went wrong and let us collect enough information to attempt to debug. There are also a handful of (non-Rust) open-source libraries for this like Breakpad and Crashpad (both from Google) and crashrpt. The approach here, of using SetUnhandledExceptionFilter, seems to be the standard one taken by other such libraries. Crashpad also seems to try to use a newer mechanism for an out-of-tree DLL to report the crash: https://issues.chromium.org/issues/42310037 If we have serious problems with memory corruption, it might be worth adopting some third-party library that has already implemented this approach. (In general, the docs of other crash reporting libraries are worth skimming to understand how these things ought to work.) Co-authored-by: samypr100 <3933065+samypr100@users.noreply.github.com> --- Cargo.lock | 1 + Cargo.toml | 2 +- crates/uv/Cargo.toml | 1 + crates/uv/src/lib.rs | 5 ++ crates/uv/src/windows_exception.rs | 130 +++++++++++++++++++++++++++++ 5 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 crates/uv/src/windows_exception.rs diff --git a/Cargo.lock b/Cargo.lock index 7f6b601ca..f2bebefc9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4734,6 +4734,7 @@ dependencies = [ "walkdir", "which", "whoami", + "windows 0.59.0", "wiremock", "zip", ] diff --git a/Cargo.toml b/Cargo.toml index 3405cff53..752955223 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -184,7 +184,7 @@ url = { version = "2.5.2", features = ["serde"] } version-ranges = { git = "https://github.com/astral-sh/pubgrub", rev = "06ec5a5f59ffaeb6cf5079c6cb184467da06c9db" } walkdir = { version = "2.5.0" } which = { version = "8.0.0", features = ["regex"] } -windows = { version = "0.59.0", features = ["Win32_Storage_FileSystem"] } +windows = { version = "0.59.0", features = ["Win32_System_Kernel", "Win32_System_Diagnostics_Debug", "Win32_Storage_FileSystem"] } windows-core = { version = "0.59.0" } windows-registry = { version = "0.5.0" } windows-result = { version = "0.3.0" } diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml index 7fa28ed67..904cc8fc3 100644 --- a/crates/uv/Cargo.toml +++ b/crates/uv/Cargo.toml @@ -108,6 +108,7 @@ zip = { workspace = true } [target.'cfg(target_os = "windows")'.dependencies] self-replace = { workspace = true } +windows = { workspace = true } [dev-dependencies] assert_cmd = { version = "2.0.16" } diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 84d889599..2a163d32c 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -57,6 +57,8 @@ pub(crate) mod commands; pub(crate) mod logging; pub(crate) mod printer; pub(crate) mod settings; +#[cfg(windows)] +mod windows_exception; #[instrument(skip_all)] async fn run(mut cli: Cli) -> Result { @@ -2189,6 +2191,9 @@ where I: IntoIterator, T: Into + Clone, { + #[cfg(windows)] + windows_exception::setup(); + // Set the `UV` variable to the current executable so it is implicitly propagated to all child // processes, e.g., in `uv run`. if let Ok(current_exe) = std::env::current_exe() { diff --git a/crates/uv/src/windows_exception.rs b/crates/uv/src/windows_exception.rs new file mode 100644 index 000000000..e96075f96 --- /dev/null +++ b/crates/uv/src/windows_exception.rs @@ -0,0 +1,130 @@ +//! Helper for setting up Windows exception handling. +//! +//! Recent versions of Windows seem to no longer show dialog boxes on access violations +//! (segfaults) or similar errors. The user experience is that the command exits with +//! the exception code as its exit status and no visible output. In order to see these +//! errors both in the field and in CI, we need to install our own exception handler. +//! +//! This is a relatively simple exception handler that leans on Rust's own backtrace +//! implementation and also displays some minimal information from the exception itself. + +#![allow(unsafe_code)] +#![allow(clippy::print_stderr)] + +use windows::Win32::{ + Foundation, + System::Diagnostics::Debug::{ + CONTEXT, EXCEPTION_CONTINUE_SEARCH, EXCEPTION_POINTERS, SetUnhandledExceptionFilter, + }, +}; + +fn display_exception_info(name: &str, info: &[usize; 15]) { + match info[0] { + 0 => eprintln!("{name} reading {:#x}", info[1]), + 1 => eprintln!("{name} writing {:#x}", info[1]), + 8 => eprintln!("{name} executing {:#x}", info[1]), + _ => eprintln!("{name} from operation {} at {:#x}", info[0], info[1]), + } +} + +#[cfg(target_arch = "x86")] +fn dump_regs(c: &CONTEXT) { + eprintln!( + "eax={:08x} ebx={:08x} ecx={:08x} edx={:08x} esi={:08x} edi={:08x}", + c.Eax, c.Ebx, c.Ecx, c.Edx, c.Esi, c.Edi + ); + eprintln!( + "eip={:08x} ebp={:08x} esp={:08x} eflags={:08x}", + c.Eip, c.Ebp, c.Esp, c.EFlags + ); +} + +#[cfg(target_arch = "x86_64")] +fn dump_regs(c: &CONTEXT) { + eprintln!("rax={:016x} rbx={:016x} rcx={:016x}", c.Rax, c.Rbx, c.Rcx); + eprintln!("rdx={:016x} rsx={:016x} rdi={:016x}", c.Rdx, c.Rsi, c.Rdi); + eprintln!("rsp={:016x} rbp={:016x} r8={:016x}", c.Rsp, c.Rbp, c.R8); + eprintln!(" r9={:016x} r10={:016x} r11={:016x}", c.R9, c.R10, c.R11); + eprintln!("r12={:016x} r13={:016x} r14={:016x}", c.R12, c.R13, c.R14); + eprintln!( + "r15={:016x} rip={:016x} eflags={:016x}", + c.R15, c.Rip, c.EFlags + ); +} + +#[cfg(target_arch = "aarch64")] +fn dump_regs(c: &CONTEXT) { + // SAFETY: The two variants of this anonymous union are equivalent, + // one's an array and one has named registers. + let r = unsafe { c.Anonymous.Anonymous }; + eprintln!("cpsr={:016x} sp={:016x} pc={:016x}", c.Cpsr, c.Sp, c.Pc); + eprintln!(" x0={:016x} x1={:016x} x2={:016x}", r.X0, r.X1, r.X2); + eprintln!(" x3={:016x} x4={:016x} x5={:016x}", r.X3, r.X4, r.X5); + eprintln!(" x6={:016x} x7={:016x} x8={:016x}", r.X6, r.X7, r.X8); + eprintln!(" x9={:016x} x10={:016x} x11={:016x}", r.X9, r.X10, r.X11); + eprintln!(" x12={:016x} x13={:016x} x14={:016x}", r.X12, r.X13, r.X14); + eprintln!(" x15={:016x} x16={:016x} x17={:016x}", r.X15, r.X16, r.X17); + eprintln!(" x18={:016x} x19={:016x} x20={:016x}", r.X18, r.X19, r.X20); + eprintln!(" x21={:016x} x22={:016x} x23={:016x}", r.X21, r.X22, r.X23); + eprintln!(" x24={:016x} x25={:016x} x26={:016x}", r.X24, r.X25, r.X26); + eprintln!(" x27={:016x} x28={:016x}", r.X27, r.X28); + eprintln!(" fp={:016x} lr={:016x}", r.Fp, r.Lr); +} + +unsafe extern "system" fn unhandled_exception_filter( + exception_info: *const EXCEPTION_POINTERS, +) -> i32 { + // TODO: Really we should not be using eprintln here because Stderr is not async-signal-safe. + // Probably we should be calling the console APIs directly. + eprintln!("error: unhandled exception in uv, please report a bug:"); + let mut context = None; + // SAFETY: Pointer comes from the OS + if let Some(info) = unsafe { exception_info.as_ref() } { + // SAFETY: Pointer comes from the OS + if let Some(exc) = unsafe { info.ExceptionRecord.as_ref() } { + eprintln!( + "code {:#X} at address {:?}", + exc.ExceptionCode.0, exc.ExceptionAddress + ); + match exc.ExceptionCode { + Foundation::EXCEPTION_ACCESS_VIOLATION => { + display_exception_info("EXCEPTION_ACCESS_VIOLATION", &exc.ExceptionInformation); + } + Foundation::EXCEPTION_IN_PAGE_ERROR => { + display_exception_info("EXCEPTION_IN_PAGE_ERROR", &exc.ExceptionInformation); + } + Foundation::EXCEPTION_ILLEGAL_INSTRUCTION => { + eprintln!("EXCEPTION_ILLEGAL_INSTRUCTION"); + } + Foundation::EXCEPTION_STACK_OVERFLOW => { + eprintln!("EXCEPTION_STACK_OVERFLOW"); + } + _ => {} + } + } else { + eprintln!("(ExceptionRecord is NULL)"); + } + // SAFETY: Pointer comes from the OS + context = unsafe { info.ContextRecord.as_ref() }; + } else { + eprintln!("(ExceptionInfo is NULL)"); + } + let backtrace = std::backtrace::Backtrace::capture(); + if backtrace.status() == std::backtrace::BacktraceStatus::Disabled { + eprintln!("note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace"); + } else { + if let Some(context) = context { + dump_regs(context); + } + eprintln!("stack backtrace:\n{backtrace:#}"); + } + EXCEPTION_CONTINUE_SEARCH +} + +/// Set up our handler for unhandled exceptions. +pub(crate) fn setup() { + // SAFETY: winapi call + unsafe { + SetUnhandledExceptionFilter(Some(Some(unhandled_exception_filter))); + } +} From 34fbc06ad6111f43a259993c51f12ac021cc2238 Mon Sep 17 00:00:00 2001 From: Aria Desires Date: Mon, 14 Jul 2025 10:53:39 -0400 Subject: [PATCH 207/349] Add experimental `uv sync --output-format json` (#13689) This is a continuation of the work in * #12405 I have: * moved to an architecture where the human output is derived from the json structs to centralize more of the printing state/logic * cleaned up some of the names/types * added tests * removed the restriction that this output is --dry-run only I have not yet added package info, which was TBD in their design. --------- Co-authored-by: x0rw Co-authored-by: Zanie Blue Co-authored-by: John Mumm --- crates/uv-cli/src/lib.rs | 13 + crates/uv-fs/src/path.rs | 6 + crates/uv/src/commands/project/mod.rs | 16 + crates/uv/src/commands/project/sync.rs | 615 ++++++++--- crates/uv/src/lib.rs | 1 + crates/uv/src/settings.rs | 7 +- crates/uv/tests/it/common/mod.rs | 2 +- crates/uv/tests/it/sync.rs | 1326 ++++++++++++++++-------- docs/reference/cli.md | 7 +- 9 files changed, 1389 insertions(+), 604 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 056447959..0f3652341 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -46,6 +46,15 @@ pub enum PythonListFormat { Json, } +#[derive(Debug, Default, Clone, Copy, clap::ValueEnum)] +pub enum SyncFormat { + /// Display the result in a human-readable format. + #[default] + Text, + /// Display the result in JSON format. + Json, +} + #[derive(Debug, Default, Clone, clap::ValueEnum)] pub enum ListFormat { /// Display the list of packages in a human-readable table. @@ -3207,6 +3216,10 @@ pub struct SyncArgs { #[arg(long, conflicts_with = "all_extras", value_parser = extra_name_with_clap_error)] pub extra: Option>, + /// Select the output format. + #[arg(long, value_enum, default_value_t = SyncFormat::default())] + pub output_format: SyncFormat, + /// Include all optional dependencies. /// /// When two or more extras are declared as conflicting in `tool.uv.conflicts`, using this flag diff --git a/crates/uv-fs/src/path.rs b/crates/uv-fs/src/path.rs index 40e579f8e..45d1da1c8 100644 --- a/crates/uv-fs/src/path.rs +++ b/crates/uv-fs/src/path.rs @@ -398,6 +398,12 @@ impl From> for PortablePathBuf { } } +impl<'a> From<&'a Path> for PortablePathBuf { + fn from(path: &'a Path) -> Self { + Box::::from(path).into() + } +} + #[cfg(feature = "serde")] impl serde::Serialize for PortablePathBuf { fn serialize(&self, serializer: S) -> Result diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index 1a0274cac..774009f63 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -1408,6 +1408,14 @@ impl ProjectEnvironment { Self::WouldCreate(..) => Err(ProjectError::DroppedEnvironment), } } + + /// Return the path to the actual target, if this was a dry run environment. + pub(crate) fn dry_run_target(&self) -> Option<&Path> { + match self { + Self::WouldReplace(path, _, _) | Self::WouldCreate(path, _, _) => Some(path), + Self::Created(_) | Self::Existing(_) | Self::Replaced(_) => None, + } + } } impl std::ops::Deref for ProjectEnvironment { @@ -1588,6 +1596,14 @@ impl ScriptEnvironment { Self::WouldCreate(..) => Err(ProjectError::DroppedEnvironment), } } + + /// Return the path to the actual target, if this was a dry run environment. + pub(crate) fn dry_run_target(&self) -> Option<&Path> { + match self { + Self::WouldReplace(path, _, _) | Self::WouldCreate(path, _, _) => Some(path), + Self::Created(_) | Self::Existing(_) | Self::Replaced(_) => None, + } + } } impl std::ops::Deref for ScriptEnvironment { diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index a9a161527..94586004f 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -6,9 +6,10 @@ use std::sync::Arc; use anyhow::{Context, Result}; use itertools::Itertools; use owo_colors::OwoColorize; +use serde::Serialize; use tracing::warn; - use uv_cache::Cache; +use uv_cli::SyncFormat; use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{ Concurrency, Constraints, DependencyGroups, DependencyGroupsWithDefaults, DryRun, EditableMode, @@ -19,7 +20,7 @@ use uv_dispatch::BuildDispatch; use uv_distribution_types::{ DirectorySourceDist, Dist, Index, Requirement, Resolution, ResolvedDist, SourceDist, }; -use uv_fs::Simplified; +use uv_fs::{PortablePathBuf, Simplified}; use uv_installer::SitePackages; use uv_normalize::{DefaultExtras, DefaultGroups, PackageName}; use uv_pep508::{MarkerTree, VersionOrUrl}; @@ -77,7 +78,14 @@ pub(crate) async fn sync( cache: &Cache, printer: Printer, preview: PreviewMode, + output_format: SyncFormat, ) -> Result { + if preview.is_enabled() && matches!(output_format, SyncFormat::Json) { + warn_user!( + "The `--output-format json` option is experimental and the schema may change without warning. Pass `--preview` to disable this warning." + ); + } + // Identify the target. let workspace_cache = WorkspaceCache::default(); let target = if let Some(script) = script { @@ -180,103 +188,16 @@ pub(crate) async fn sync( }) .ok(); - // Notify the user of any environment changes. - match &environment { - SyncEnvironment::Project(ProjectEnvironment::Existing(environment)) - if dry_run.enabled() => - { - writeln!( - printer.stderr(), - "{}", - format!( - "Discovered existing environment at: {}", - environment.root().user_display().bold() - ) - .dimmed() - )?; - } - SyncEnvironment::Project(ProjectEnvironment::WouldReplace(root, ..)) - if dry_run.enabled() => - { - writeln!( - printer.stderr(), - "{}", - format!( - "Would replace existing virtual environment at: {}", - root.user_display().bold() - ) - .dimmed() - )?; - } - SyncEnvironment::Project(ProjectEnvironment::WouldCreate(root, ..)) - if dry_run.enabled() => - { - writeln!( - printer.stderr(), - "{}", - format!( - "Would create virtual environment at: {}", - root.user_display().bold() - ) - .dimmed() - )?; - } - SyncEnvironment::Script(ScriptEnvironment::Existing(environment)) => { - if dry_run.enabled() { - writeln!( - printer.stderr(), - "{}", - format!( - "Discovered existing environment at: {}", - environment.root().user_display().bold() - ) - .dimmed() - )?; - } else { - writeln!( - printer.stderr(), - "Using script environment at: {}", - environment.root().user_display().cyan() - )?; - } - } - SyncEnvironment::Script(ScriptEnvironment::Replaced(environment)) if !dry_run.enabled() => { - writeln!( - printer.stderr(), - "Recreating script environment at: {}", - environment.root().user_display().cyan() - )?; - } - SyncEnvironment::Script(ScriptEnvironment::Created(environment)) if !dry_run.enabled() => { - writeln!( - printer.stderr(), - "Creating script environment at: {}", - environment.root().user_display().cyan() - )?; - } - SyncEnvironment::Script(ScriptEnvironment::WouldReplace(root, ..)) if dry_run.enabled() => { - writeln!( - printer.stderr(), - "{}", - format!( - "Would replace existing script environment at: {}", - root.user_display().bold() - ) - .dimmed() - )?; - } - SyncEnvironment::Script(ScriptEnvironment::WouldCreate(root, ..)) if dry_run.enabled() => { - writeln!( - printer.stderr(), - "{}", - format!( - "Would create script environment at: {}", - root.user_display().bold() - ) - .dimmed() - )?; - } - _ => {} + let sync_report = SyncReport { + dry_run: dry_run.enabled(), + environment: EnvironmentReport::from(&environment), + action: SyncAction::from(&environment), + target: TargetName::from(&target), + }; + + // Show the intermediate results if relevant + if let Some(message) = sync_report.format(output_format) { + writeln!(printer.stderr(), "{message}")?; } // Special-case: we're syncing a script that doesn't have an associated lockfile. In that case, @@ -340,7 +261,23 @@ pub(crate) async fn sync( ) .await { - Ok(..) => return Ok(ExitStatus::Success), + Ok(..) => { + // Generate a report for the script without a lockfile + let report = Report { + schema: SchemaReport::default(), + target: TargetName::from(&target), + project: None, + script: Some(ScriptReport::from(script)), + sync: sync_report, + lock: None, + dry_run: dry_run.enabled(), + }; + if let Some(output) = report.format(output_format) { + writeln!(printer.stdout(), "{output}")?; + } + return Ok(ExitStatus::Success); + } + // TODO(zanieb): We should respect `--output-format json` for the error case Err(ProjectError::Operation(err)) => { return diagnostics::OperationDiagnostic::native_tls( network_settings.native_tls, @@ -387,46 +324,7 @@ pub(crate) async fn sync( .execute(lock_target) .await { - Ok(result) => { - if dry_run.enabled() { - match result { - LockResult::Unchanged(..) => { - writeln!( - printer.stderr(), - "{}", - format!( - "Found up-to-date lockfile at: {}", - lock_target.lock_path().user_display().bold() - ) - .dimmed() - )?; - } - LockResult::Changed(None, ..) => { - writeln!( - printer.stderr(), - "{}", - format!( - "Would create lockfile at: {}", - lock_target.lock_path().user_display().bold() - ) - .dimmed() - )?; - } - LockResult::Changed(Some(..), ..) => { - writeln!( - printer.stderr(), - "{}", - format!( - "Would update lockfile at: {}", - lock_target.lock_path().user_display().bold() - ) - .dimmed() - )?; - } - } - } - Outcome::Success(result.into_lock()) - } + Ok(result) => Outcome::Success(result), Err(ProjectError::Operation(err)) => { return diagnostics::OperationDiagnostic::native_tls(network_settings.native_tls) .report(err) @@ -440,6 +338,25 @@ pub(crate) async fn sync( Err(err) => return Err(err.into()), }; + let lock_report = LockReport::from((&lock_target, &mode, &outcome)); + if let Some(message) = lock_report.format(output_format) { + writeln!(printer.stderr(), "{message}")?; + } + + let report = Report { + schema: SchemaReport::default(), + target: TargetName::from(&target), + project: target.project().map(ProjectReport::from), + script: target.script().map(ScriptReport::from), + sync: sync_report, + lock: Some(lock_report), + dry_run: dry_run.enabled(), + }; + + if let Some(output) = report.format(output_format) { + writeln!(printer.stdout(), "{output}")?; + } + // Identify the installation target. let sync_target = identify_installation_target(&target, outcome.lock(), all_packages, package.as_ref()); @@ -490,7 +407,7 @@ pub(crate) async fn sync( #[allow(clippy::large_enum_variant)] enum Outcome { /// The `lock` operation was successful. - Success(Lock), + Success(LockResult), /// The `lock` operation successfully resolved, but failed due to a mismatch (e.g., with `--locked`). LockMismatch(Box), } @@ -499,7 +416,7 @@ impl Outcome { /// Return the [`Lock`] associated with this outcome. fn lock(&self) -> &Lock { match self { - Self::Success(lock) => lock, + Self::Success(lock) => lock.lock(), Self::LockMismatch(lock) => lock, } } @@ -563,6 +480,22 @@ enum SyncTarget { Script(Pep723Script), } +impl SyncTarget { + fn project(&self) -> Option<&VirtualProject> { + match self { + Self::Project(project) => Some(project), + Self::Script(_) => None, + } + } + + fn script(&self) -> Option<&Pep723Script> { + match self { + Self::Project(_) => None, + Self::Script(script) => Some(script), + } + } +} + #[derive(Debug)] enum SyncEnvironment { /// A Python environment for a project. @@ -571,6 +504,15 @@ enum SyncEnvironment { Script(ScriptEnvironment), } +impl SyncEnvironment { + fn dry_run_target(&self) -> Option<&Path> { + match self { + Self::Project(env) => env.dry_run_target(), + Self::Script(env) => env.dry_run_target(), + } + } +} + impl Deref for SyncEnvironment { type Target = PythonEnvironment; @@ -892,3 +834,392 @@ fn store_credentials_from_target(target: InstallTarget<'_>) { } } } + +#[derive(Debug, Serialize)] +#[serde(rename_all = "snake_case")] +struct WorkspaceReport { + /// The workspace directory path. + path: PortablePathBuf, +} + +impl From<&Workspace> for WorkspaceReport { + fn from(workspace: &Workspace) -> Self { + Self { + path: workspace.install_path().as_path().into(), + } + } +} +#[derive(Debug, Serialize)] +#[serde(rename_all = "snake_case")] +struct ProjectReport { + // + path: PortablePathBuf, + workspace: WorkspaceReport, +} + +impl From<&VirtualProject> for ProjectReport { + fn from(project: &VirtualProject) -> Self { + Self { + path: project.root().into(), + workspace: WorkspaceReport::from(project.workspace()), + } + } +} + +impl From<&SyncTarget> for TargetName { + fn from(target: &SyncTarget) -> Self { + match target { + SyncTarget::Project(_) => TargetName::Project, + SyncTarget::Script(_) => TargetName::Script, + } + } +} + +#[derive(Serialize, Debug)] +struct ScriptReport { + /// The path to the script. + path: PortablePathBuf, +} + +impl From<&Pep723Script> for ScriptReport { + fn from(script: &Pep723Script) -> Self { + Self { + path: script.path.as_path().into(), + } + } +} + +#[derive(Serialize, Debug, Default)] +#[serde(rename_all = "snake_case")] +enum SchemaVersion { + /// An unstable, experimental schema. + #[default] + Preview, +} + +#[derive(Serialize, Debug, Default)] +struct SchemaReport { + /// The version of the schema. + version: SchemaVersion, +} + +/// A report of the uv sync operation +#[derive(Debug, Serialize)] +#[serde(rename_all = "snake_case")] +struct Report { + /// The schema of this report. + schema: SchemaReport, + /// The target of the sync operation, either a project or a script. + target: TargetName, + /// The report for a [`TargetName::Project`], if applicable. + #[serde(skip_serializing_if = "Option::is_none")] + project: Option, + /// The report for a [`TargetName::Script`], if applicable. + #[serde(skip_serializing_if = "Option::is_none")] + script: Option, + /// The report for the sync operation. + sync: SyncReport, + /// The report for the lock operation. + lock: Option, + /// Whether this is a dry run. + dry_run: bool, +} + +/// The kind of target +#[derive(Debug, Serialize, Clone, Copy)] +#[serde(rename_all = "snake_case")] +enum TargetName { + Project, + Script, +} + +impl std::fmt::Display for TargetName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TargetName::Project => write!(f, "project"), + TargetName::Script => write!(f, "script"), + } + } +} + +/// Represents the action taken during a sync. +#[derive(Serialize, Debug)] +#[serde(rename_all = "snake_case")] +enum SyncAction { + /// The environment was checked and required no updates. + Check, + /// The environment was updated. + Update, + /// The environment was replaced. + Replace, + /// A new environment was created. + Create, +} + +impl From<&SyncEnvironment> for SyncAction { + fn from(env: &SyncEnvironment) -> Self { + match &env { + SyncEnvironment::Project(ProjectEnvironment::Existing(..)) => SyncAction::Check, + SyncEnvironment::Project(ProjectEnvironment::Created(..)) => SyncAction::Create, + SyncEnvironment::Project(ProjectEnvironment::WouldCreate(..)) => SyncAction::Create, + SyncEnvironment::Project(ProjectEnvironment::WouldReplace(..)) => SyncAction::Replace, + SyncEnvironment::Project(ProjectEnvironment::Replaced(..)) => SyncAction::Update, + SyncEnvironment::Script(ScriptEnvironment::Existing(..)) => SyncAction::Check, + SyncEnvironment::Script(ScriptEnvironment::Created(..)) => SyncAction::Create, + SyncEnvironment::Script(ScriptEnvironment::WouldCreate(..)) => SyncAction::Create, + SyncEnvironment::Script(ScriptEnvironment::WouldReplace(..)) => SyncAction::Replace, + SyncEnvironment::Script(ScriptEnvironment::Replaced(..)) => SyncAction::Update, + } + } +} + +impl SyncAction { + fn message(&self, target: TargetName, dry_run: bool) -> Option<&'static str> { + let message = if dry_run { + match self { + SyncAction::Check => "Would use", + SyncAction::Update => "Would update", + SyncAction::Replace => "Would replace", + SyncAction::Create => "Would create", + } + } else { + // For projects, we omit some of these messages when we're not in dry-run mode + let is_project = matches!(target, TargetName::Project); + match self { + SyncAction::Check | SyncAction::Update | SyncAction::Create if is_project => { + return None; + } + SyncAction::Check => "Using", + SyncAction::Update => "Updating", + SyncAction::Replace => "Replacing", + SyncAction::Create => "Creating", + } + }; + Some(message) + } +} + +/// Represents the action taken during a lock. +#[derive(Serialize, Debug)] +#[serde(rename_all = "snake_case")] +enum LockAction { + /// The lockfile was used without checking. + Use, + /// The lockfile was checked and required no updates. + Check, + /// The lockfile was updated. + Update, + /// A new lockfile was created. + Create, +} + +impl LockAction { + fn message(&self, dry_run: bool) -> Option<&'static str> { + let message = if dry_run { + match self { + LockAction::Use => return None, + LockAction::Check => "Found up-to-date", + LockAction::Update => "Would update", + LockAction::Create => "Would create", + } + } else { + return None; + }; + Some(message) + } +} + +#[derive(Serialize, Debug)] +struct PythonReport { + path: PortablePathBuf, + version: uv_pep508::StringVersion, + implementation: String, +} + +impl From<&uv_python::Interpreter> for PythonReport { + fn from(interpreter: &uv_python::Interpreter) -> Self { + Self { + path: interpreter.sys_executable().into(), + version: interpreter.python_full_version().clone(), + implementation: interpreter.implementation_name().to_string(), + } + } +} + +impl PythonReport { + /// Set the path for this Python report. + #[must_use] + fn with_path(mut self, path: PortablePathBuf) -> Self { + self.path = path; + self + } +} + +#[derive(Serialize, Debug)] +struct EnvironmentReport { + /// The path to the environment. + path: PortablePathBuf, + /// The Python interpreter for the environment. + python: PythonReport, +} + +impl From<&PythonEnvironment> for EnvironmentReport { + fn from(env: &PythonEnvironment) -> Self { + Self { + python: PythonReport::from(env.interpreter()), + path: env.root().into(), + } + } +} + +impl From<&SyncEnvironment> for EnvironmentReport { + fn from(env: &SyncEnvironment) -> Self { + let report = EnvironmentReport::from(&**env); + // Replace the path if necessary; we construct a temporary virtual environment during dry + // run invocations and want to report the path we _would_ use. + if let Some(path) = env.dry_run_target() { + report.with_path(path.into()) + } else { + report + } + } +} + +impl EnvironmentReport { + /// Set the path for this environment report. + #[must_use] + fn with_path(mut self, path: PortablePathBuf) -> Self { + let python_path = &self.python.path; + if let Ok(python_path) = python_path.as_ref().strip_prefix(self.path) { + let new_path = path.as_ref().to_path_buf().join(python_path); + self.python = self.python.with_path(new_path.as_path().into()); + } + self.path = path; + self + } +} + +/// The report for a sync operation. +#[derive(Serialize, Debug)] +struct SyncReport { + /// The environment. + environment: EnvironmentReport, + /// The action performed during the sync, e.g., what was done to the environment. + action: SyncAction, + + // We store these fields so the report can format itself self-contained, but the outer + // [`Report`] is intended to include these in user-facing output + #[serde(skip)] + dry_run: bool, + #[serde(skip)] + target: TargetName, +} + +impl SyncReport { + fn format(&self, output_format: SyncFormat) -> Option { + match output_format { + // This is an intermediate report, when using JSON, it's only rendered at the end + SyncFormat::Json => None, + SyncFormat::Text => self.to_human_readable_string(), + } + } + + fn to_human_readable_string(&self) -> Option { + let Self { + environment, + action, + dry_run, + target, + } = self; + + let action = action.message(*target, *dry_run)?; + + let message = format!( + "{action} {target} environment at: {path}", + path = environment.path.user_display().cyan(), + ); + if *dry_run { + return Some(message.dimmed().to_string()); + } + + Some(message) + } +} + +/// The report for a lock operation. +#[derive(Debug, Serialize)] +struct LockReport { + /// The path to the lockfile + path: PortablePathBuf, + /// Whether the lockfile was preserved, created, or updated. + action: LockAction, + + // We store this field so the report can format itself self-contained, but the outer + // [`Report`] is intended to include this in user-facing output + #[serde(skip)] + dry_run: bool, +} + +impl From<(&LockTarget<'_>, &LockMode<'_>, &Outcome)> for LockReport { + fn from((target, mode, outcome): (&LockTarget, &LockMode, &Outcome)) -> Self { + Self { + path: target.lock_path().deref().into(), + action: match outcome { + Outcome::Success(result) => { + match result { + LockResult::Unchanged(..) => match mode { + // When `--frozen` is used, we don't check the lockfile + LockMode::Frozen => LockAction::Use, + LockMode::DryRun(_) | LockMode::Locked(_) | LockMode::Write(_) => { + LockAction::Check + } + }, + LockResult::Changed(None, ..) => LockAction::Create, + LockResult::Changed(Some(_), ..) => LockAction::Update, + } + } + // TODO(zanieb): We don't have a way to report the outcome of the lock yet + Outcome::LockMismatch(_) => LockAction::Check, + }, + dry_run: matches!(mode, LockMode::DryRun(_)), + } + } +} + +impl LockReport { + fn format(&self, output_format: SyncFormat) -> Option { + match output_format { + SyncFormat::Json => None, + SyncFormat::Text => self.to_human_readable_string(), + } + } + + fn to_human_readable_string(&self) -> Option { + let Self { + path, + action, + dry_run, + } = self; + + let action = action.message(*dry_run)?; + + let message = format!( + "{action} lockfile at: {path}", + path = path.user_display().cyan(), + ); + if *dry_run { + return Some(message.dimmed().to_string()); + } + + Some(message) + } +} + +impl Report { + fn format(&self, output_format: SyncFormat) -> Option { + match output_format { + SyncFormat::Json => serde_json::to_string_pretty(self).ok(), + SyncFormat::Text => None, + } + } +} diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 2a163d32c..0b4d0bb82 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -1818,6 +1818,7 @@ async fn run_project( &cache, printer, globals.preview, + args.output_format, )) .await } diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index f89704d45..8a325d538 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -11,8 +11,8 @@ use uv_cli::{ PipCheckArgs, PipCompileArgs, PipFreezeArgs, PipInstallArgs, PipListArgs, PipShowArgs, PipSyncArgs, PipTreeArgs, PipUninstallArgs, PythonFindArgs, PythonInstallArgs, PythonListArgs, PythonListFormat, PythonPinArgs, PythonUninstallArgs, PythonUpgradeArgs, RemoveArgs, RunArgs, - SyncArgs, ToolDirArgs, ToolInstallArgs, ToolListArgs, ToolRunArgs, ToolUninstallArgs, TreeArgs, - VenvArgs, VersionArgs, VersionBump, VersionFormat, + SyncArgs, SyncFormat, ToolDirArgs, ToolInstallArgs, ToolListArgs, ToolRunArgs, + ToolUninstallArgs, TreeArgs, VenvArgs, VersionArgs, VersionBump, VersionFormat, }; use uv_cli::{ AuthorFrom, BuildArgs, ExportArgs, PublishArgs, PythonDirArgs, ResolverInstallerArgs, @@ -1154,6 +1154,7 @@ pub(crate) struct SyncSettings { pub(crate) install_mirrors: PythonInstallMirrors, pub(crate) refresh: Refresh, pub(crate) settings: ResolverInstallerSettings, + pub(crate) output_format: SyncFormat, } impl SyncSettings { @@ -1194,6 +1195,7 @@ impl SyncSettings { python_platform, check, no_check, + output_format, } = args; let install_mirrors = filesystem .clone() @@ -1213,6 +1215,7 @@ impl SyncSettings { }; Self { + output_format, locked, frozen, dry_run, diff --git a/crates/uv/tests/it/common/mod.rs b/crates/uv/tests/it/common/mod.rs index 90f436f6f..2dc72fa1d 100644 --- a/crates/uv/tests/it/common/mod.rs +++ b/crates/uv/tests/it/common/mod.rs @@ -210,7 +210,7 @@ impl TestContext { pub fn with_filtered_python_names(mut self) -> Self { if cfg!(windows) { self.filters - .push(("python.exe".to_string(), "python".to_string())); + .push((r"python\.exe".to_string(), "python".to_string())); } else { self.filters .push((r"python\d.\d\d".to_string(), "python".to_string())); diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index d4479296a..7063035f9 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -27,7 +27,7 @@ fn sync() -> Result<()> { )?; // Running `uv sync` should generate a lockfile. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -37,7 +37,7 @@ fn sync() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); assert!(context.temp_dir.child("uv.lock").exists()); @@ -60,14 +60,14 @@ fn locked() -> Result<()> { )?; // Running with `--locked` should error, if no lockfile is present. - uv_snapshot!(context.filters(), context.sync().arg("--locked"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--locked"), @r" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- error: Unable to find lockfile at `uv.lock`. To create a lockfile, run `uv lock` or `uv sync`. - "###); + "); // Lock the initial requirements. context.lock().assert().success(); @@ -86,7 +86,7 @@ fn locked() -> Result<()> { )?; // Running with `--locked` should error. - uv_snapshot!(context.filters(), context.sync().arg("--locked"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--locked"), @r" success: false exit_code: 2 ----- stdout ----- @@ -94,7 +94,7 @@ fn locked() -> Result<()> { ----- stderr ----- Resolved 2 packages in [TIME] error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. - "###); + "); let updated = context.read("uv.lock"); @@ -120,14 +120,14 @@ fn frozen() -> Result<()> { )?; // Running with `--frozen` should error, if no lockfile is present. - uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- error: Unable to find lockfile at `uv.lock`. To create a lockfile, run `uv lock` or `uv sync`. - "###); + "); context.lock().assert().success(); @@ -143,7 +143,7 @@ fn frozen() -> Result<()> { )?; // Running with `--frozen` should install the stale lockfile. - uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r" success: true exit_code: 0 ----- stdout ----- @@ -154,7 +154,7 @@ fn frozen() -> Result<()> { + anyio==3.7.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); Ok(()) } @@ -172,7 +172,7 @@ fn empty() -> Result<()> { )?; // Running `uv sync` should generate an empty lockfile. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -181,12 +181,12 @@ fn empty() -> Result<()> { warning: No `requires-python` value found in the workspace. Defaulting to `>=3.12`. Resolved in [TIME] Audited in [TIME] - "###); + "); assert!(context.temp_dir.child("uv.lock").exists()); // Running `uv sync` again should succeed. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -195,7 +195,7 @@ fn empty() -> Result<()> { warning: No `requires-python` value found in the workspace. Defaulting to `>=3.12`. Resolved in [TIME] Audited in [TIME] - "###); + "); Ok(()) } @@ -252,7 +252,7 @@ fn package() -> Result<()> { let init = src.child("__init__.py"); init.touch()?; - uv_snapshot!(context.filters(), context.sync().arg("--package").arg("child"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--package").arg("child"), @r" success: true exit_code: 0 ----- stdout ----- @@ -263,7 +263,239 @@ fn package() -> Result<()> { Installed 2 packages in [TIME] + child==0.1.0 (from file://[TEMP_DIR]/child) + iniconfig==2.0.0 - "###); + "); + + Ok(()) +} + +/// Test json output +#[test] +fn sync_json() -> Result<()> { + let context = TestContext::new("3.12") + .with_filtered_python_names() + .with_filtered_virtualenv_bin(); + + 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 = ["iniconfig"] + "#, + )?; + + uv_snapshot!(context.filters(), context.sync() + .arg("--output-format").arg("json"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + { + "schema": { + "version": "preview" + }, + "target": "project", + "project": { + "path": "[TEMP_DIR]/", + "workspace": { + "path": "[TEMP_DIR]/" + } + }, + "sync": { + "environment": { + "path": "[VENV]/", + "python": { + "path": "[VENV]/[BIN]/python", + "version": "3.12.[X]", + "implementation": "cpython" + } + }, + "action": "check" + }, + "lock": { + "path": "[TEMP_DIR]/uv.lock", + "action": "create" + }, + "dry_run": false + } + + ----- stderr ----- + Resolved 2 packages in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + iniconfig==2.0.0 + "#); + + assert!(context.temp_dir.child("uv.lock").exists()); + + uv_snapshot!(context.filters(), context.sync() + .arg("--frozen") + .arg("--output-format").arg("json"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + { + "schema": { + "version": "preview" + }, + "target": "project", + "project": { + "path": "[TEMP_DIR]/", + "workspace": { + "path": "[TEMP_DIR]/" + } + }, + "sync": { + "environment": { + "path": "[VENV]/", + "python": { + "path": "[VENV]/[BIN]/python", + "version": "3.12.[X]", + "implementation": "cpython" + } + }, + "action": "check" + }, + "lock": { + "path": "[TEMP_DIR]/uv.lock", + "action": "use" + }, + "dry_run": false + } + + ----- stderr ----- + Audited 1 package in [TIME] + "#); + + uv_snapshot!(context.filters(), context.sync() + .arg("--locked") + .arg("--output-format").arg("json"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + { + "schema": { + "version": "preview" + }, + "target": "project", + "project": { + "path": "[TEMP_DIR]/", + "workspace": { + "path": "[TEMP_DIR]/" + } + }, + "sync": { + "environment": { + "path": "[VENV]/", + "python": { + "path": "[VENV]/[BIN]/python", + "version": "3.12.[X]", + "implementation": "cpython" + } + }, + "action": "check" + }, + "lock": { + "path": "[TEMP_DIR]/uv.lock", + "action": "check" + }, + "dry_run": false + } + + ----- stderr ----- + Resolved 2 packages in [TIME] + Audited 1 package in [TIME] + "#); + + // Invalidate the lockfile by changing the requirements. + 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 = ["iniconfig<2"] + "#, + )?; + + uv_snapshot!(context.filters(), context.sync() + .arg("--locked") + .arg("--output-format").arg("json"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + "); + + Ok(()) +} + +/// Test --dry json output +#[test] +fn sync_dry_json() -> Result<()> { + let context = TestContext::new_with_versions(&["3.12"]) + .with_filtered_python_names() + .with_filtered_virtualenv_bin(); + + 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 = ["iniconfig"] + "#, + )?; + + // Running `uv sync` should report intent to create the environment and lockfile + uv_snapshot!(context.filters(), context.sync() + .arg("--output-format").arg("json") + .arg("--dry-run"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + { + "schema": { + "version": "preview" + }, + "target": "project", + "project": { + "path": "[TEMP_DIR]/", + "workspace": { + "path": "[TEMP_DIR]/" + } + }, + "sync": { + "environment": { + "path": "[VENV]/", + "python": { + "path": "[VENV]/[BIN]/python", + "version": "3.12.[X]", + "implementation": "cpython" + } + }, + "action": "create" + }, + "lock": { + "path": "[TEMP_DIR]/uv.lock", + "action": "create" + }, + "dry_run": true + } + + ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + Resolved 2 packages in [TIME] + Would download 1 package + Would install 1 package + + iniconfig==2.0.0 + "#); Ok(()) } @@ -322,7 +554,7 @@ fn mixed_requires_python() -> Result<()> { )?; // Running `uv sync` should succeed, locking for Python 3.12. - uv_snapshot!(context.filters(), context.sync().arg("-p").arg("3.12"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("-p").arg("3.12"), @r" success: true exit_code: 0 ----- stdout ----- @@ -337,7 +569,7 @@ fn mixed_requires_python() -> Result<()> { + bird-feeder==0.1.0 (from file://[TEMP_DIR]/packages/bird-feeder) + idna==3.6 + sniffio==1.3.1 - "###); + "); // Running `uv sync` again should fail. uv_snapshot!(context.filters(), context.sync().arg("-p").arg("3.9"), @r" @@ -660,23 +892,23 @@ fn check() -> Result<()> { )?; // Running `uv sync --check` should fail. - uv_snapshot!(context.filters(), context.sync().arg("--check"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--check"), @r" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- - Discovered existing environment at: .venv + Would use project environment at: .venv Resolved 2 packages in [TIME] Would create lockfile at: uv.lock Would download 1 package Would install 1 package + iniconfig==2.0.0 error: The environment is outdated; run `uv sync` to update the environment - "###); + "); // Sync the environment. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -686,23 +918,23 @@ fn check() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); assert!(context.temp_dir.child("uv.lock").exists()); // Running `uv sync --check` should pass now that the environment is up to date. - uv_snapshot!(context.filters(), context.sync().arg("--check"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--check"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - Discovered existing environment at: .venv + Would use project environment at: .venv Resolved 2 packages in [TIME] Found up-to-date lockfile at: uv.lock Audited 1 package in [TIME] Would make no changes - "###); + "); Ok(()) } @@ -750,7 +982,7 @@ fn sync_legacy_non_project_dev_dependencies() -> Result<()> { .touch()?; // Syncing with `--no-dev` should omit all dependencies except `iniconfig`. - uv_snapshot!(context.filters(), context.sync().arg("--no-dev"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-dev"), @r" success: true exit_code: 0 ----- stdout ----- @@ -761,11 +993,11 @@ fn sync_legacy_non_project_dev_dependencies() -> Result<()> { Installed 2 packages in [TIME] + child==0.1.0 (from file://[TEMP_DIR]/child) + iniconfig==2.0.0 - "###); + "); // Syncing without `--no-dev` should include `anyio`, `requests`, `pysocks`, and their // dependencies, but not `typing-extensions`. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -782,7 +1014,7 @@ fn sync_legacy_non_project_dev_dependencies() -> Result<()> { + requests==2.31.0 + sniffio==1.3.1 + urllib3==2.2.1 - "###); + "); Ok(()) } @@ -830,7 +1062,7 @@ fn sync_legacy_non_project_frozen() -> Result<()> { context.lock().assert().success(); - uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--package").arg("foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--package").arg("foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -839,9 +1071,9 @@ fn sync_legacy_non_project_frozen() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r" success: true exit_code: 0 ----- stdout ----- @@ -850,7 +1082,7 @@ fn sync_legacy_non_project_frozen() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + typing-extensions==4.10.0 - "###); + "); Ok(()) } @@ -903,7 +1135,7 @@ fn sync_legacy_non_project_group() -> Result<()> { .child("__init__.py") .touch()?; - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -914,9 +1146,9 @@ fn sync_legacy_non_project_group() -> Result<()> { Installed 2 packages in [TIME] + child==0.1.0 (from file://[TEMP_DIR]/child) + iniconfig==2.0.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -928,9 +1160,9 @@ fn sync_legacy_non_project_group() -> Result<()> { + anyio==4.3.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("bar"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("bar"), @r" success: true exit_code: 0 ----- stdout ----- @@ -946,9 +1178,9 @@ fn sync_legacy_non_project_group() -> Result<()> { - iniconfig==2.0.0 - sniffio==1.3.1 + typing-extensions==4.10.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--group").arg("baz"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("baz"), @r" success: true exit_code: 0 ----- stdout ----- @@ -958,9 +1190,9 @@ fn sync_legacy_non_project_group() -> Result<()> { Installed 2 packages in [TIME] + child==0.1.0 (from file://[TEMP_DIR]/child) + iniconfig==2.0.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--group").arg("bop"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("bop"), @r" success: false exit_code: 2 ----- stdout ----- @@ -968,7 +1200,7 @@ fn sync_legacy_non_project_group() -> Result<()> { ----- stderr ----- Resolved 6 packages in [TIME] error: Group `bop` is not defined in any project's `dependency-groups` table - "###); + "); Ok(()) } @@ -993,7 +1225,7 @@ fn sync_legacy_non_project_frozen_modification() -> Result<()> { context.lock().assert().success(); - uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--group").arg("async"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--group").arg("async"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1004,7 +1236,7 @@ fn sync_legacy_non_project_frozen_modification() -> Result<()> { + anyio==4.3.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); // Modify the "live" dependency groups. pyproject_toml.write_str( @@ -1018,14 +1250,14 @@ fn sync_legacy_non_project_frozen_modification() -> Result<()> { )?; // This should succeed. - uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--group").arg("async"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--group").arg("async"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Audited 3 packages in [TIME] - "###); + "); Ok(()) } @@ -1074,7 +1306,7 @@ fn sync_build_isolation() -> Result<()> { "###); // Running `uv sync` should succeed. - uv_snapshot!(context.filters(), context.sync().arg("--no-build-isolation"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-build-isolation"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1092,7 +1324,7 @@ fn sync_build_isolation() -> Result<()> { + source-distribution==0.0.1 (from https://files.pythonhosted.org/packages/10/1f/57aa4cce1b1abf6b433106676e15f9fa2c92ed2bd4cf77c3b50a9e9ac773/source_distribution-0.0.1.tar.gz) - trove-classifiers==2024.3.3 - wheel==0.43.0 - "###); + "); assert!(context.temp_dir.child("uv.lock").exists()); @@ -1122,7 +1354,7 @@ fn sync_build_isolation_package() -> Result<()> { )?; // Running `uv sync` should fail for iniconfig. - uv_snapshot!(context.filters(), context.sync().arg("--no-build-isolation-package").arg("source-distribution"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-build-isolation-package").arg("source-distribution"), @r#" success: false exit_code: 1 ----- stdout ----- @@ -1140,7 +1372,7 @@ fn sync_build_isolation_package() -> Result<()> { hint: This usually indicates a problem with the package or the build environment. help: `source-distribution` was included because `project` (v0.1.0) depends on `source-distribution` - "###); + "#); // Install `hatchling` for `source-distribution`. uv_snapshot!(context.filters(), context.pip_install().arg("hatchling"), @r###" @@ -1160,7 +1392,7 @@ fn sync_build_isolation_package() -> Result<()> { "###); // Running `uv sync` should succeed. - uv_snapshot!(context.filters(), context.sync().arg("--no-build-isolation-package").arg("source-distribution"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-build-isolation-package").arg("source-distribution"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1177,7 +1409,7 @@ fn sync_build_isolation_package() -> Result<()> { + project==0.1.0 (from file://[TEMP_DIR]/) + source-distribution==0.0.1 (from https://files.pythonhosted.org/packages/10/1f/57aa4cce1b1abf6b433106676e15f9fa2c92ed2bd4cf77c3b50a9e9ac773/source_distribution-0.0.1.tar.gz) - trove-classifiers==2024.3.3 - "###); + "); assert!(context.temp_dir.child("uv.lock").exists()); @@ -1212,7 +1444,7 @@ fn sync_build_isolation_extra() -> Result<()> { )?; // Running `uv sync` should fail for the `compile` extra. - uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("compile"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("compile"), @r#" success: false exit_code: 1 ----- stdout ----- @@ -1230,10 +1462,10 @@ fn sync_build_isolation_extra() -> Result<()> { hint: This usually indicates a problem with the package or the build environment. help: `source-distribution` was included because `project[compile]` (v0.1.0) depends on `source-distribution` - "###); + "#); // Running `uv sync` with `--all-extras` should also fail. - uv_snapshot!(context.filters(), context.sync().arg("--all-extras"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-extras"), @r#" success: false exit_code: 1 ----- stdout ----- @@ -1251,10 +1483,10 @@ fn sync_build_isolation_extra() -> Result<()> { hint: This usually indicates a problem with the package or the build environment. help: `source-distribution` was included because `project[compile]` (v0.1.0) depends on `source-distribution` - "###); + "#); // Install the build dependencies. - uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("build"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("build"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1269,10 +1501,10 @@ fn sync_build_isolation_extra() -> Result<()> { + pluggy==1.4.0 + project==0.1.0 (from file://[TEMP_DIR]/) + trove-classifiers==2024.3.3 - "###); + "); // Running `uv sync` for the `compile` extra should succeed, and remove the build dependencies. - uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("compile"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("compile"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1288,7 +1520,7 @@ fn sync_build_isolation_extra() -> Result<()> { - pluggy==1.4.0 + source-distribution==0.0.1 (from https://files.pythonhosted.org/packages/10/1f/57aa4cce1b1abf6b433106676e15f9fa2c92ed2bd4cf77c3b50a9e9ac773/source_distribution-0.0.1.tar.gz) - trove-classifiers==2024.3.3 - "###); + "); assert!(context.temp_dir.child("uv.lock").exists()); @@ -1342,7 +1574,7 @@ fn sync_reset_state() -> Result<()> { init.touch()?; // Running `uv sync` should succeed. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -1354,7 +1586,7 @@ fn sync_reset_state() -> Result<()> { + project==0.1.0 (from file://[TEMP_DIR]/) + pydantic-core==2.17.0 + typing-extensions==4.10.0 - "###); + "); assert!(context.temp_dir.child("uv.lock").exists()); @@ -1396,7 +1628,7 @@ fn sync_relative_wheel() -> Result<()> { context.temp_dir.join("wheels/ok-1.0.0-py3-none-any.whl"), )?; - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -1407,7 +1639,7 @@ fn sync_relative_wheel() -> Result<()> { Installed 2 packages in [TIME] + ok==1.0.0 (from file://[TEMP_DIR]/wheels/ok-1.0.0-py3-none-any.whl) + relative-wheel==0.1.0 (from file://[TEMP_DIR]/) - "###); + "); let lock = context.read("uv.lock"); @@ -1449,7 +1681,7 @@ fn sync_relative_wheel() -> Result<()> { ); // Check that we can re-read the lockfile. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -1457,7 +1689,7 @@ fn sync_relative_wheel() -> Result<()> { ----- stderr ----- Resolved 2 packages in [TIME] Audited 2 packages in [TIME] - "###); + "); Ok(()) } @@ -1481,7 +1713,7 @@ fn sync_environment() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: false exit_code: 2 ----- stdout ----- @@ -1489,7 +1721,7 @@ fn sync_environment() -> Result<()> { ----- stderr ----- Resolved 2 packages in [TIME] error: The current Python platform is not compatible with the lockfile's supported environments: `python_full_version < '3.11'` - "###); + "); assert!(context.temp_dir.child("uv.lock").exists()); @@ -1516,7 +1748,7 @@ fn sync_dev() -> Result<()> { context.lock().assert().success(); - uv_snapshot!(context.filters(), context.sync().arg("--only-dev"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--only-dev"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1528,9 +1760,9 @@ fn sync_dev() -> Result<()> { + anyio==4.3.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--no-dev"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-dev"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1544,9 +1776,9 @@ fn sync_dev() -> Result<()> { - idna==3.6 - sniffio==1.3.1 + typing-extensions==4.10.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -1557,10 +1789,10 @@ fn sync_dev() -> Result<()> { + anyio==4.3.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); // Using `--no-default-groups` should remove dev dependencies - uv_snapshot!(context.filters(), context.sync().arg("--no-default-groups"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-default-groups"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1571,7 +1803,7 @@ fn sync_dev() -> Result<()> { - anyio==4.3.0 - idna==3.6 - sniffio==1.3.1 - "###); + "); Ok(()) } @@ -1600,7 +1832,7 @@ fn sync_group() -> Result<()> { context.lock().assert().success(); - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -1611,9 +1843,9 @@ fn sync_group() -> Result<()> { Installed 2 packages in [TIME] + iniconfig==2.0.0 + typing-extensions==4.10.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1625,9 +1857,9 @@ fn sync_group() -> Result<()> { + anyio==4.3.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("bar"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("bar"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1645,9 +1877,9 @@ fn sync_group() -> Result<()> { - sniffio==1.3.1 - typing-extensions==4.10.0 + urllib3==2.2.1 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo").arg("--group").arg("bar"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo").arg("--group").arg("bar"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1659,9 +1891,9 @@ fn sync_group() -> Result<()> { + iniconfig==2.0.0 + sniffio==1.3.1 + typing-extensions==4.10.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1669,9 +1901,9 @@ fn sync_group() -> Result<()> { ----- stderr ----- Resolved 10 packages in [TIME] Audited 9 packages in [TIME] - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--all-groups").arg("--no-group").arg("bar"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-groups").arg("--no-group").arg("bar"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1683,9 +1915,9 @@ fn sync_group() -> Result<()> { - charset-normalizer==3.3.2 - requests==2.31.0 - urllib3==2.2.1 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--all-groups").arg("--no-dev"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-groups").arg("--no-dev"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1699,9 +1931,9 @@ fn sync_group() -> Result<()> { - iniconfig==2.0.0 + requests==2.31.0 + urllib3==2.2.1 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--dev"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--dev"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1718,9 +1950,9 @@ fn sync_group() -> Result<()> { - requests==2.31.0 - sniffio==1.3.1 - urllib3==2.2.1 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--dev").arg("--no-group").arg("dev"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--dev").arg("--no-group").arg("dev"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1729,9 +1961,9 @@ fn sync_group() -> Result<()> { Resolved 10 packages in [TIME] Uninstalled 1 package in [TIME] - iniconfig==2.0.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--group").arg("dev").arg("--no-dev"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("dev").arg("--no-dev"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1739,9 +1971,9 @@ fn sync_group() -> Result<()> { ----- stderr ----- Resolved 10 packages in [TIME] Audited 1 package in [TIME] - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1757,10 +1989,10 @@ fn sync_group() -> Result<()> { + requests==2.31.0 + sniffio==1.3.1 + urllib3==2.2.1 - "###); + "); // Using `--no-default-groups` should exclude all groups - uv_snapshot!(context.filters(), context.sync().arg("--no-default-groups"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-default-groups"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1776,9 +2008,9 @@ fn sync_group() -> Result<()> { - requests==2.31.0 - sniffio==1.3.1 - urllib3==2.2.1 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1794,11 +2026,11 @@ fn sync_group() -> Result<()> { + requests==2.31.0 + sniffio==1.3.1 + urllib3==2.2.1 - "###); + "); // Using `--no-default-groups` with `--group foo` and `--group bar` should include those groups, // excluding the remaining `dev` group. - uv_snapshot!(context.filters(), context.sync().arg("--no-default-groups").arg("--group").arg("foo").arg("--group").arg("bar"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-default-groups").arg("--group").arg("foo").arg("--group").arg("bar"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1807,7 +2039,7 @@ fn sync_group() -> Result<()> { Resolved 10 packages in [TIME] Uninstalled 1 package in [TIME] - iniconfig==2.0.0 - "###); + "); Ok(()) } @@ -1833,7 +2065,7 @@ fn sync_include_group() -> Result<()> { context.lock().assert().success(); - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -1843,9 +2075,9 @@ fn sync_include_group() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + typing-extensions==4.10.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1858,9 +2090,9 @@ fn sync_include_group() -> Result<()> { + idna==3.6 + iniconfig==2.0.0 + sniffio==1.3.1 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("bar"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("bar"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1872,9 +2104,9 @@ fn sync_include_group() -> Result<()> { - idna==3.6 - sniffio==1.3.1 - typing-extensions==4.10.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo").arg("--group").arg("bar"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo").arg("--group").arg("bar"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1886,9 +2118,9 @@ fn sync_include_group() -> Result<()> { + idna==3.6 + sniffio==1.3.1 + typing-extensions==4.10.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1897,9 +2129,9 @@ fn sync_include_group() -> Result<()> { Resolved 6 packages in [TIME] Uninstalled 1 package in [TIME] - typing-extensions==4.10.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1908,9 +2140,9 @@ fn sync_include_group() -> Result<()> { Resolved 6 packages in [TIME] Installed 1 package in [TIME] + typing-extensions==4.10.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--no-default-groups"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-default-groups"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1922,9 +2154,9 @@ fn sync_include_group() -> Result<()> { - idna==3.6 - iniconfig==2.0.0 - sniffio==1.3.1 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1936,9 +2168,9 @@ fn sync_include_group() -> Result<()> { + idna==3.6 + iniconfig==2.0.0 + sniffio==1.3.1 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--no-default-groups").arg("--group").arg("foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-default-groups").arg("--group").arg("foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1946,7 +2178,7 @@ fn sync_include_group() -> Result<()> { ----- stderr ----- Resolved 6 packages in [TIME] Audited 5 packages in [TIME] - "###); + "); Ok(()) } @@ -1972,7 +2204,7 @@ fn sync_exclude_group() -> Result<()> { context.lock().assert().success(); - uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1986,9 +2218,9 @@ fn sync_exclude_group() -> Result<()> { + iniconfig==2.0.0 + sniffio==1.3.1 + typing-extensions==4.10.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo").arg("--no-group").arg("foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo").arg("--no-group").arg("foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -2000,9 +2232,9 @@ fn sync_exclude_group() -> Result<()> { - idna==3.6 - iniconfig==2.0.0 - sniffio==1.3.1 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("bar"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("bar"), @r" success: true exit_code: 0 ----- stdout ----- @@ -2013,9 +2245,9 @@ fn sync_exclude_group() -> Result<()> { Installed 1 package in [TIME] + iniconfig==2.0.0 - typing-extensions==4.10.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("bar").arg("--no-group").arg("bar"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("bar").arg("--no-group").arg("bar"), @r" success: true exit_code: 0 ----- stdout ----- @@ -2024,7 +2256,7 @@ fn sync_exclude_group() -> Result<()> { Resolved 6 packages in [TIME] Uninstalled 1 package in [TIME] - iniconfig==2.0.0 - "###); + "); Ok(()) } @@ -2052,7 +2284,7 @@ fn sync_dev_group() -> Result<()> { context.lock().assert().success(); - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -2066,7 +2298,7 @@ fn sync_dev_group() -> Result<()> { + iniconfig==2.0.0 + sniffio==1.3.1 + typing-extensions==4.10.0 - "###); + "); Ok(()) } @@ -2093,7 +2325,7 @@ fn sync_non_existent_group() -> Result<()> { context.lock().assert().success(); // Requesting a non-existent group should fail. - uv_snapshot!(context.filters(), context.sync().arg("--group").arg("baz"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("baz"), @r" success: false exit_code: 2 ----- stdout ----- @@ -2101,9 +2333,9 @@ fn sync_non_existent_group() -> Result<()> { ----- stderr ----- Resolved 7 packages in [TIME] error: Group `baz` is not defined in the project's `dependency-groups` table - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--no-group").arg("baz"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-group").arg("baz"), @r" success: false exit_code: 2 ----- stdout ----- @@ -2111,10 +2343,10 @@ fn sync_non_existent_group() -> Result<()> { ----- stderr ----- Resolved 7 packages in [TIME] error: Group `baz` is not defined in the project's `dependency-groups` table - "###); + "); // Requesting an empty group should succeed. - uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -2124,11 +2356,11 @@ fn sync_non_existent_group() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + typing-extensions==4.10.0 - "###); + "); // Requesting with `--frozen` should respect the groups in the lockfile, rather than the // `pyproject.toml`. - uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--group").arg("bar"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--group").arg("bar"), @r" success: true exit_code: 0 ----- stdout ----- @@ -2141,7 +2373,7 @@ fn sync_non_existent_group() -> Result<()> { + idna==3.6 + requests==2.31.0 + urllib3==2.2.1 - "###); + "); // Replace `bar` with `baz`. pyproject_toml.write_str( @@ -2157,23 +2389,23 @@ fn sync_non_existent_group() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--group").arg("bar"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--group").arg("bar"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Audited 6 packages in [TIME] - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--group").arg("baz"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--group").arg("baz"), @r" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- error: Group `baz` is not defined in the project's `dependency-groups` table - "###); + "); Ok(()) } @@ -2453,7 +2685,7 @@ fn sync_default_groups() -> Result<()> { context.lock().assert().success(); // The `dev` group should be synced by default. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -2464,7 +2696,7 @@ fn sync_default_groups() -> Result<()> { Installed 2 packages in [TIME] + iniconfig==2.0.0 + typing-extensions==4.10.0 - "###); + "); // If we remove it from the `default-groups` list, it should be removed. pyproject_toml.write_str( @@ -2485,7 +2717,7 @@ fn sync_default_groups() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -2494,7 +2726,7 @@ fn sync_default_groups() -> Result<()> { Resolved 10 packages in [TIME] Uninstalled 1 package in [TIME] - iniconfig==2.0.0 - "###); + "); // If we set a different default group, it should be synced instead. pyproject_toml.write_str( @@ -2515,7 +2747,7 @@ fn sync_default_groups() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -2527,7 +2759,7 @@ fn sync_default_groups() -> Result<()> { + anyio==4.3.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); // `--no-group` should remove from the defaults. pyproject_toml.write_str( @@ -2548,7 +2780,7 @@ fn sync_default_groups() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync().arg("--no-group").arg("foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-group").arg("foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -2559,10 +2791,10 @@ fn sync_default_groups() -> Result<()> { - anyio==4.3.0 - idna==3.6 - sniffio==1.3.1 - "###); + "); // Using `--group` should include the defaults - uv_snapshot!(context.filters(), context.sync().arg("--group").arg("dev"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("dev"), @r" success: true exit_code: 0 ----- stdout ----- @@ -2574,10 +2806,10 @@ fn sync_default_groups() -> Result<()> { + idna==3.6 + iniconfig==2.0.0 + sniffio==1.3.1 - "###); + "); // Using `--all-groups` should include the defaults - uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r" success: true exit_code: 0 ----- stdout ----- @@ -2590,10 +2822,10 @@ fn sync_default_groups() -> Result<()> { + charset-normalizer==3.3.2 + requests==2.31.0 + urllib3==2.2.1 - "###); + "); // Using `--only-group` should exclude the defaults - uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("dev"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("dev"), @r" success: true exit_code: 0 ----- stdout ----- @@ -2609,9 +2841,9 @@ fn sync_default_groups() -> Result<()> { - sniffio==1.3.1 - typing-extensions==4.10.0 - urllib3==2.2.1 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r" success: true exit_code: 0 ----- stdout ----- @@ -2627,10 +2859,10 @@ fn sync_default_groups() -> Result<()> { + sniffio==1.3.1 + typing-extensions==4.10.0 + urllib3==2.2.1 - "###); + "); // Using `--no-default-groups` should exclude all groups - uv_snapshot!(context.filters(), context.sync().arg("--no-default-groups"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-default-groups"), @r" success: true exit_code: 0 ----- stdout ----- @@ -2646,9 +2878,9 @@ fn sync_default_groups() -> Result<()> { - requests==2.31.0 - sniffio==1.3.1 - urllib3==2.2.1 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r" success: true exit_code: 0 ----- stdout ----- @@ -2664,11 +2896,11 @@ fn sync_default_groups() -> Result<()> { + requests==2.31.0 + sniffio==1.3.1 + urllib3==2.2.1 - "###); + "); // Using `--no-default-groups` with `--group foo` and `--group bar` should include those groups, // excluding the remaining `dev` group. - uv_snapshot!(context.filters(), context.sync().arg("--no-default-groups").arg("--group").arg("foo").arg("--group").arg("bar"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-default-groups").arg("--group").arg("foo").arg("--group").arg("bar"), @r" success: true exit_code: 0 ----- stdout ----- @@ -2677,7 +2909,7 @@ fn sync_default_groups() -> Result<()> { Resolved 10 packages in [TIME] Uninstalled 1 package in [TIME] - iniconfig==2.0.0 - "###); + "); Ok(()) } @@ -2749,7 +2981,7 @@ fn sync_default_groups_all() -> Result<()> { "); // Using `--all-groups` should be redundant and work fine - uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r" success: true exit_code: 0 ----- stdout ----- @@ -2765,7 +2997,7 @@ fn sync_default_groups_all() -> Result<()> { + requests==2.31.0 + sniffio==1.3.1 + urllib3==2.2.1 - "###); + "); // Using `--no-dev` should exclude just the dev group uv_snapshot!(context.filters(), context.sync().arg("--no-dev"), @r" @@ -2900,7 +3132,7 @@ fn sync_group_member() -> Result<()> { // Generate a lockfile. context.lock().assert().success(); - uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -2912,7 +3144,7 @@ fn sync_group_member() -> Result<()> { + child==0.1.0 (from file://[TEMP_DIR]/child) + iniconfig==2.0.0 + typing-extensions==4.10.0 - "###); + "); Ok(()) } @@ -3023,7 +3255,7 @@ fn sync_group_legacy_non_project_member() -> Result<()> { ); }); - uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -3035,7 +3267,7 @@ fn sync_group_legacy_non_project_member() -> Result<()> { + child==0.1.0 (from file://[TEMP_DIR]/child) + iniconfig==2.0.0 + typing-extensions==4.10.0 - "###); + "); Ok(()) } @@ -3157,7 +3389,7 @@ fn sync_group_self() -> Result<()> { ); }); - uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -3169,9 +3401,9 @@ fn sync_group_self() -> Result<()> { + iniconfig==2.0.0 + project==0.1.0 (from file://[TEMP_DIR]/) + typing-extensions==4.10.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("bar"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("bar"), @r" success: true exit_code: 0 ----- stdout ----- @@ -3183,7 +3415,7 @@ fn sync_group_self() -> Result<()> { Installed 1 package in [TIME] + idna==3.6 - typing-extensions==4.10.0 - "###); + "); Ok(()) } @@ -3208,7 +3440,7 @@ fn sync_non_existent_extra() -> Result<()> { context.lock().assert().success(); // Requesting a non-existent extra should fail. - uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("baz"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("baz"), @r" success: false exit_code: 2 ----- stdout ----- @@ -3216,10 +3448,10 @@ fn sync_non_existent_extra() -> Result<()> { ----- stderr ----- Resolved 4 packages in [TIME] error: Extra `baz` is not defined in the project's `optional-dependencies` table - "###); + "); // Excluding a non-existing extra when requesting all extras should fail. - uv_snapshot!(context.filters(), context.sync().arg("--all-extras").arg("--no-extra").arg("baz"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-extras").arg("--no-extra").arg("baz"), @r" success: false exit_code: 2 ----- stdout ----- @@ -3227,7 +3459,7 @@ fn sync_non_existent_extra() -> Result<()> { ----- stderr ----- Resolved 4 packages in [TIME] error: Extra `baz` is not defined in the project's `optional-dependencies` table - "###); + "); Ok(()) } @@ -3249,7 +3481,7 @@ fn sync_non_existent_extra_no_optional_dependencies() -> Result<()> { context.lock().assert().success(); // Requesting a non-existent extra should fail. - uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("baz"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("baz"), @r" success: false exit_code: 2 ----- stdout ----- @@ -3257,10 +3489,10 @@ fn sync_non_existent_extra_no_optional_dependencies() -> Result<()> { ----- stderr ----- Resolved 1 package in [TIME] error: Extra `baz` is not defined in the project's `optional-dependencies` table - "###); + "); // Excluding a non-existing extra when requesting all extras should fail. - uv_snapshot!(context.filters(), context.sync().arg("--all-extras").arg("--no-extra").arg("baz"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-extras").arg("--no-extra").arg("baz"), @r" success: false exit_code: 2 ----- stdout ----- @@ -3268,7 +3500,7 @@ fn sync_non_existent_extra_no_optional_dependencies() -> Result<()> { ----- stderr ----- Resolved 1 package in [TIME] error: Extra `baz` is not defined in the project's `optional-dependencies` table - "###); + "); Ok(()) } @@ -3321,14 +3553,14 @@ fn sync_ignore_extras_check_when_no_provides_extras() -> Result<()> { "#})?; // Requesting a non-existent extra should not fail, as no validation should be performed. - uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--extra").arg("baz"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--extra").arg("baz"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Audited in [TIME] - "###); + "); Ok(()) } @@ -3376,7 +3608,7 @@ fn sync_non_existent_extra_workspace_member() -> Result<()> { context.lock().assert().success(); // Requesting an extra that only exists in the child should fail. - uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("async"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("async"), @r" success: false exit_code: 2 ----- stdout ----- @@ -3384,10 +3616,10 @@ fn sync_non_existent_extra_workspace_member() -> Result<()> { ----- stderr ----- Resolved 5 packages in [TIME] error: Extra `async` is not defined in the project's `optional-dependencies` table - "###); + "); // Unless we sync from the child directory. - uv_snapshot!(context.filters(), context.sync().arg("--package").arg("child").arg("--extra").arg("async"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--package").arg("child").arg("--extra").arg("async"), @r" success: true exit_code: 0 ----- stdout ----- @@ -3399,7 +3631,7 @@ fn sync_non_existent_extra_workspace_member() -> Result<()> { + anyio==4.3.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); Ok(()) } @@ -3449,7 +3681,7 @@ fn sync_non_existent_extra_non_project_workspace() -> Result<()> { // Requesting an extra that only exists in the child should succeed, since we sync all members // by default. - uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("async"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("async"), @r" success: true exit_code: 0 ----- stdout ----- @@ -3461,10 +3693,10 @@ fn sync_non_existent_extra_non_project_workspace() -> Result<()> { + anyio==4.3.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); // Syncing from the child should also succeed. - uv_snapshot!(context.filters(), context.sync().arg("--package").arg("child").arg("--extra").arg("async"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--package").arg("child").arg("--extra").arg("async"), @r" success: true exit_code: 0 ----- stdout ----- @@ -3472,10 +3704,10 @@ fn sync_non_existent_extra_non_project_workspace() -> Result<()> { ----- stderr ----- Resolved 5 packages in [TIME] Audited 3 packages in [TIME] - "###); + "); // Syncing from an unrelated child should fail. - uv_snapshot!(context.filters(), context.sync().arg("--package").arg("other").arg("--extra").arg("async"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--package").arg("other").arg("--extra").arg("async"), @r" success: false exit_code: 2 ----- stdout ----- @@ -3483,7 +3715,7 @@ fn sync_non_existent_extra_non_project_workspace() -> Result<()> { ----- stderr ----- Resolved 5 packages in [TIME] error: Extra `async` is not defined in the project's `optional-dependencies` table - "###); + "); Ok(()) } @@ -3551,7 +3783,7 @@ fn no_install_project() -> Result<()> { context.lock().assert().success(); // Running with `--no-install-project` should install `anyio`, but not `project`. - uv_snapshot!(context.filters(), context.sync().arg("--no-install-project"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-install-project"), @r" success: true exit_code: 0 ----- stdout ----- @@ -3563,7 +3795,7 @@ fn no_install_project() -> Result<()> { + anyio==3.7.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); // However, we do require the `pyproject.toml`. fs_err::remove_file(pyproject_toml)?; @@ -3633,7 +3865,7 @@ fn no_install_workspace() -> Result<()> { // Running with `--no-install-workspace` should install `anyio` and `iniconfig`, but not // `project` or `child`. - uv_snapshot!(context.filters(), context.sync().arg("--no-install-workspace"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-install-workspace"), @r" success: true exit_code: 0 ----- stdout ----- @@ -3646,7 +3878,7 @@ fn no_install_workspace() -> Result<()> { + idna==3.6 + iniconfig==2.0.0 + sniffio==1.3.1 - "###); + "); // Remove the virtual environment. fs_err::remove_dir_all(&context.venv)?; @@ -3654,7 +3886,7 @@ fn no_install_workspace() -> Result<()> { // We don't require the `pyproject.toml` for non-root members, if `--frozen` is provided. fs_err::remove_file(child.join("pyproject.toml"))?; - uv_snapshot!(context.filters(), context.sync().arg("--no-install-workspace").arg("--frozen"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-install-workspace").arg("--frozen"), @r" success: true exit_code: 0 ----- stdout ----- @@ -3667,10 +3899,10 @@ fn no_install_workspace() -> Result<()> { + idna==3.6 + iniconfig==2.0.0 + sniffio==1.3.1 - "###); + "); // Even if `--package` is used. - uv_snapshot!(context.filters(), context.sync().arg("--package").arg("child").arg("--no-install-workspace").arg("--frozen"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--package").arg("child").arg("--no-install-workspace").arg("--frozen"), @r" success: true exit_code: 0 ----- stdout ----- @@ -3680,20 +3912,20 @@ fn no_install_workspace() -> Result<()> { - anyio==3.7.0 - idna==3.6 - sniffio==1.3.1 - "###); + "); // Unless the package doesn't exist. - uv_snapshot!(context.filters(), context.sync().arg("--package").arg("fake").arg("--no-install-workspace").arg("--frozen"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--package").arg("fake").arg("--no-install-workspace").arg("--frozen"), @r" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- error: Could not find root package `fake` - "###); + "); // Even if `--all-packages` is used. - uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--no-install-workspace").arg("--frozen"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--no-install-workspace").arg("--frozen"), @r" success: true exit_code: 0 ----- stdout ----- @@ -3703,7 +3935,7 @@ fn no_install_workspace() -> Result<()> { + anyio==3.7.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); // But we do require the root `pyproject.toml`. fs_err::remove_file(context.temp_dir.join("pyproject.toml"))?; @@ -3744,7 +3976,7 @@ fn no_install_package() -> Result<()> { context.lock().assert().success(); // Running with `--no-install-package anyio` should skip anyio but include everything else - uv_snapshot!(context.filters(), context.sync().arg("--no-install-package").arg("anyio"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-install-package").arg("anyio"), @r" success: true exit_code: 0 ----- stdout ----- @@ -3756,11 +3988,11 @@ fn no_install_package() -> Result<()> { + idna==3.6 + project==0.1.0 (from file://[TEMP_DIR]/) + sniffio==1.3.1 - "###); + "); // Running with `--no-install-package project` should skip the project itself (not as a special // case, that's just the name of the project) - uv_snapshot!(context.filters(), context.sync().arg("--no-install-package").arg("project"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-install-package").arg("project"), @r" success: true exit_code: 0 ----- stdout ----- @@ -3772,7 +4004,7 @@ fn no_install_package() -> Result<()> { Installed 1 package in [TIME] + anyio==3.7.0 - project==0.1.0 (from file://[TEMP_DIR]/) - "###); + "); Ok(()) } @@ -3801,7 +4033,7 @@ fn no_install_project_no_build() -> Result<()> { context.lock().assert().success(); // `--no-build` should raise an error, since we try to install the project. - uv_snapshot!(context.filters(), context.sync().arg("--no-build"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-build"), @r" success: false exit_code: 2 ----- stdout ----- @@ -3809,11 +4041,11 @@ fn no_install_project_no_build() -> Result<()> { ----- stderr ----- Resolved 4 packages in [TIME] error: Distribution `project==0.1.0 @ editable+.` can't be installed because it is marked as `--no-build` but has no binary distribution - "###); + "); // But it's fine to combine `--no-install-project` with `--no-build`. We shouldn't error, since // we aren't building the project. - uv_snapshot!(context.filters(), context.sync().arg("--no-install-project").arg("--no-build").arg("--locked"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-install-project").arg("--no-build").arg("--locked"), @r" success: true exit_code: 0 ----- stdout ----- @@ -3825,7 +4057,7 @@ fn no_install_project_no_build() -> Result<()> { + anyio==3.7.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); Ok(()) } @@ -3978,7 +4210,7 @@ fn convert_to_virtual() -> Result<()> { )?; // Running `uv sync` should install the project itself. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -3989,7 +4221,7 @@ fn convert_to_virtual() -> Result<()> { Installed 2 packages in [TIME] + iniconfig==2.0.0 + project==0.1.0 (from file://[TEMP_DIR]/) - "###); + "); let lock = context.read("uv.lock"); @@ -4040,7 +4272,7 @@ fn convert_to_virtual() -> Result<()> { )?; // Running `uv sync` should remove the project itself. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -4049,7 +4281,7 @@ fn convert_to_virtual() -> Result<()> { Resolved 2 packages in [TIME] Uninstalled 1 package in [TIME] - project==0.1.0 (from file://[TEMP_DIR]/) - "###); + "); let lock = context.read("uv.lock"); @@ -4108,7 +4340,7 @@ fn convert_to_package() -> Result<()> { )?; // Running `uv sync` should not install the project itself. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -4118,7 +4350,7 @@ fn convert_to_package() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); let lock = context.read("uv.lock"); @@ -4173,7 +4405,7 @@ fn convert_to_package() -> Result<()> { )?; // Running `uv sync` should install the project itself. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -4183,7 +4415,7 @@ fn convert_to_package() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + project==0.1.0 (from file://[TEMP_DIR]/) - "###); + "); let lock = context.read("uv.lock"); @@ -4243,7 +4475,7 @@ fn sync_custom_environment_path() -> Result<()> { )?; // Running `uv sync` should create `.venv` by default - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -4255,7 +4487,7 @@ fn sync_custom_environment_path() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); context .temp_dir @@ -4263,7 +4495,7 @@ fn sync_custom_environment_path() -> Result<()> { .assert(predicate::path::is_dir()); // Running `uv sync` should create `foo` in the project directory when customized - uv_snapshot!(context.filters(), context.sync().env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo"), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4274,7 +4506,7 @@ fn sync_custom_environment_path() -> Result<()> { Resolved 2 packages in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); context .temp_dir @@ -4288,7 +4520,7 @@ fn sync_custom_environment_path() -> Result<()> { .assert(predicate::path::is_dir()); // An absolute path can be provided - uv_snapshot!(context.filters(), context.sync().env(EnvVars::UV_PROJECT_ENVIRONMENT, "foobar/.venv"), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::UV_PROJECT_ENVIRONMENT, "foobar/.venv"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4299,7 +4531,7 @@ fn sync_custom_environment_path() -> Result<()> { Resolved 2 packages in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); context .temp_dir @@ -4313,7 +4545,7 @@ fn sync_custom_environment_path() -> Result<()> { .assert(predicate::path::is_dir()); // An absolute path can be provided - uv_snapshot!(context.filters(), context.sync().env(EnvVars::UV_PROJECT_ENVIRONMENT, context.temp_dir.join("bar")), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::UV_PROJECT_ENVIRONMENT, context.temp_dir.join("bar")), @r" success: true exit_code: 0 ----- stdout ----- @@ -4324,7 +4556,7 @@ fn sync_custom_environment_path() -> Result<()> { Resolved 2 packages in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); context .temp_dir @@ -4334,7 +4566,7 @@ fn sync_custom_environment_path() -> Result<()> { // And, it can be outside the project let tempdir = tempdir_in(TestContext::test_bucket_dir())?; context = context.with_filtered_path(tempdir.path(), "OTHER_TEMPDIR"); - uv_snapshot!(context.filters(), context.sync().env(EnvVars::UV_PROJECT_ENVIRONMENT, tempdir.path().join(".venv")), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::UV_PROJECT_ENVIRONMENT, tempdir.path().join(".venv")), @r" success: true exit_code: 0 ----- stdout ----- @@ -4345,7 +4577,7 @@ fn sync_custom_environment_path() -> Result<()> { Resolved 2 packages in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); ChildPath::new(tempdir.path()) .child(".venv") @@ -4382,7 +4614,7 @@ fn sync_custom_environment_path() -> Result<()> { fs_err::write(context.temp_dir.join("foo").join("file"), b"")?; // We can delete and use it - uv_snapshot!(context.filters(), context.sync().env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo"), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4394,7 +4626,7 @@ fn sync_custom_environment_path() -> Result<()> { Resolved 2 packages in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); Ok(()) } @@ -4417,7 +4649,7 @@ fn sync_active_project_environment() -> Result<()> { )?; // Running `uv sync` with `VIRTUAL_ENV` should warn - uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, "foo"), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, "foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4430,7 +4662,7 @@ fn sync_active_project_environment() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); context .temp_dir @@ -4443,7 +4675,7 @@ fn sync_active_project_environment() -> Result<()> { .assert(predicate::path::missing()); // Using `--active` should create the environment - uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, "foo").arg("--active"), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, "foo").arg("--active"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4454,7 +4686,7 @@ fn sync_active_project_environment() -> Result<()> { Resolved 2 packages in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); context .temp_dir @@ -4462,7 +4694,7 @@ fn sync_active_project_environment() -> Result<()> { .assert(predicate::path::is_dir()); // A subsequent sync will re-use the environment - uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, "foo").arg("--active"), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, "foo").arg("--active"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4470,13 +4702,13 @@ fn sync_active_project_environment() -> Result<()> { ----- stderr ----- Resolved 2 packages in [TIME] Audited 1 package in [TIME] - "###); + "); // Setting both the `VIRTUAL_ENV` and `UV_PROJECT_ENVIRONMENT` is fine if they agree uv_snapshot!(context.filters(), context.sync() .arg("--active") .env(EnvVars::VIRTUAL_ENV, "foo") - .env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo"), @r###" + .env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4484,13 +4716,13 @@ fn sync_active_project_environment() -> Result<()> { ----- stderr ----- Resolved 2 packages in [TIME] Audited 1 package in [TIME] - "###); + "); // If they disagree, we use `VIRTUAL_ENV` because of `--active` uv_snapshot!(context.filters(), context.sync() .arg("--active") .env(EnvVars::VIRTUAL_ENV, "foo") - .env(EnvVars::UV_PROJECT_ENVIRONMENT, "bar"), @r###" + .env(EnvVars::UV_PROJECT_ENVIRONMENT, "bar"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4498,7 +4730,7 @@ fn sync_active_project_environment() -> Result<()> { ----- stderr ----- Resolved 2 packages in [TIME] Audited 1 package in [TIME] - "###); + "); context .temp_dir @@ -4507,7 +4739,7 @@ fn sync_active_project_environment() -> Result<()> { // Requesting another Python version will invalidate the environment uv_snapshot!(context.filters(), context.sync() - .env(EnvVars::VIRTUAL_ENV, "foo").arg("--active").arg("-p").arg("3.12"), @r###" + .env(EnvVars::VIRTUAL_ENV, "foo").arg("--active").arg("-p").arg("3.12"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4519,7 +4751,7 @@ fn sync_active_project_environment() -> Result<()> { Resolved 2 packages in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); Ok(()) } @@ -4553,7 +4785,7 @@ fn sync_active_script_environment() -> Result<()> { .collect::>(); // Running `uv sync --script` with `VIRTUAL_ENV` should warn - uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").env(EnvVars::VIRTUAL_ENV, "foo"), @r###" + uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").env(EnvVars::VIRTUAL_ENV, "foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4567,7 +4799,7 @@ fn sync_active_script_environment() -> Result<()> { + anyio==4.3.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); context .temp_dir @@ -4575,7 +4807,7 @@ fn sync_active_script_environment() -> Result<()> { .assert(predicate::path::missing()); // Using `--active` should create the environment - uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").env(EnvVars::VIRTUAL_ENV, "foo").arg("--active"), @r###" + uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").env(EnvVars::VIRTUAL_ENV, "foo").arg("--active"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4587,7 +4819,7 @@ fn sync_active_script_environment() -> Result<()> { + anyio==4.3.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); context .temp_dir @@ -4595,7 +4827,7 @@ fn sync_active_script_environment() -> Result<()> { .assert(predicate::path::is_dir()); // A subsequent sync will re-use the environment - uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").env(EnvVars::VIRTUAL_ENV, "foo").arg("--active"), @r###" + uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").env(EnvVars::VIRTUAL_ENV, "foo").arg("--active"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4604,7 +4836,7 @@ fn sync_active_script_environment() -> Result<()> { Using script environment at: foo Resolved 3 packages in [TIME] Audited 3 packages in [TIME] - "###); + "); // Requesting another Python version will invalidate the environment uv_snapshot!(&filters, context.sync() @@ -4613,19 +4845,198 @@ fn sync_active_script_environment() -> Result<()> { .env(EnvVars::VIRTUAL_ENV, "foo") .arg("--active") .arg("-p") - .arg("3.12"), @r###" + .arg("3.12"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - Recreating script environment at: foo + Updating script environment at: foo Resolved 3 packages in [TIME] Installed 3 packages in [TIME] + anyio==4.3.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); + + Ok(()) +} + +#[test] +fn sync_active_script_environment_json() -> Result<()> { + let context = TestContext::new_with_versions(&["3.11", "3.12"]) + .with_filtered_virtualenv_bin() + .with_filtered_python_names(); + + let script = context.temp_dir.child("script.py"); + script.write_str(indoc! { r#" + # /// script + # requires-python = ">=3.11" + # dependencies = [ + # "anyio", + # ] + # /// + + import anyio + "# + })?; + + let filters = context + .filters() + .into_iter() + .chain(vec![ + ( + r"environments-v2/script-[a-z0-9]+", + "environments-v2/script-[HASH]", + ), + ("bin/python3", "[PYTHON]"), + ("Scripts/python.exe", "[PYTHON]"), + ]) + .collect::>(); + + // Running `uv sync --script` with `VIRTUAL_ENV` should warn + uv_snapshot!(&filters, context.sync() + .arg("--script").arg("script.py") + .arg("--output-format").arg("json") + .env(EnvVars::VIRTUAL_ENV, "foo"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + { + "schema": { + "version": "preview" + }, + "target": "script", + "script": { + "path": "[TEMP_DIR]/script.py" + }, + "sync": { + "environment": { + "path": "[CACHE_DIR]/environments-v2/script-[HASH]", + "python": { + "path": "[CACHE_DIR]/environments-v2/script-[HASH]/[BIN]/python", + "version": "3.11.[X]", + "implementation": "cpython" + } + }, + "action": "create" + }, + "lock": null, + "dry_run": false + } + + ----- stderr ----- + warning: `VIRTUAL_ENV=foo` does not match the script environment path `[CACHE_DIR]/environments-v2/script-[HASH]` and will be ignored; use `--active` to target the active environment instead + Resolved 3 packages in [TIME] + Prepared 3 packages in [TIME] + Installed 3 packages in [TIME] + + anyio==4.3.0 + + idna==3.6 + + sniffio==1.3.1 + "#); + + context + .temp_dir + .child("foo") + .assert(predicate::path::missing()); + + // Using `--active` should create the environment + uv_snapshot!(&filters, context.sync() + .arg("--script").arg("script.py") + .arg("--output-format").arg("json") + .env(EnvVars::VIRTUAL_ENV, "foo").arg("--active"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + { + "schema": { + "version": "preview" + }, + "target": "script", + "script": { + "path": "[TEMP_DIR]/script.py" + }, + "sync": { + "environment": { + "path": "[TEMP_DIR]/foo", + "python": { + "path": "[TEMP_DIR]/foo/[BIN]/python", + "version": "3.11.[X]", + "implementation": "cpython" + } + }, + "action": "create" + }, + "lock": null, + "dry_run": false + } + + ----- stderr ----- + Resolved 3 packages in [TIME] + Installed 3 packages in [TIME] + + anyio==4.3.0 + + idna==3.6 + + sniffio==1.3.1 + "#); + + context + .temp_dir + .child("foo") + .assert(predicate::path::is_dir()); + + // A subsequent sync will re-use the environment + uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").env(EnvVars::VIRTUAL_ENV, "foo").arg("--active"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using script environment at: foo + Resolved 3 packages in [TIME] + Audited 3 packages in [TIME] + "); + + // Requesting another Python version will invalidate the environment + uv_snapshot!(&filters, context.sync() + .arg("--script").arg("script.py") + .arg("--output-format").arg("json") + .env(EnvVars::VIRTUAL_ENV, "foo") + .arg("--active") + .arg("-p") + .arg("3.12"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + { + "schema": { + "version": "preview" + }, + "target": "script", + "script": { + "path": "[TEMP_DIR]/script.py" + }, + "sync": { + "environment": { + "path": "[TEMP_DIR]/foo", + "python": { + "path": "[TEMP_DIR]/foo/[BIN]/python", + "version": "3.12.[X]", + "implementation": "cpython" + } + }, + "action": "update" + }, + "lock": null, + "dry_run": false + } + + ----- stderr ----- + Resolved 3 packages in [TIME] + Installed 3 packages in [TIME] + + anyio==4.3.0 + + idna==3.6 + + sniffio==1.3.1 + "#); Ok(()) } @@ -4650,7 +5061,7 @@ fn sync_workspace_custom_environment_path() -> Result<()> { context.init().arg("child").assert().success(); // Running `uv sync` should create `.venv` in the workspace root - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -4660,7 +5071,7 @@ fn sync_workspace_custom_environment_path() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); context .temp_dir @@ -4668,7 +5079,7 @@ fn sync_workspace_custom_environment_path() -> Result<()> { .assert(predicate::path::is_dir()); // Similarly, `uv sync` from the child project uses `.venv` in the workspace root - uv_snapshot!(context.filters(), context.sync().current_dir(context.temp_dir.join("child")), @r###" + uv_snapshot!(context.filters(), context.sync().current_dir(context.temp_dir.join("child")), @r" success: true exit_code: 0 ----- stdout ----- @@ -4677,7 +5088,7 @@ fn sync_workspace_custom_environment_path() -> Result<()> { Resolved 3 packages in [TIME] Uninstalled 1 package in [TIME] - iniconfig==2.0.0 - "###); + "); context .temp_dir @@ -4691,7 +5102,7 @@ fn sync_workspace_custom_environment_path() -> Result<()> { .assert(predicate::path::missing()); // Running `uv sync` should create `foo` in the workspace root when customized - uv_snapshot!(context.filters(), context.sync().env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo"), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4702,7 +5113,7 @@ fn sync_workspace_custom_environment_path() -> Result<()> { Resolved 3 packages in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); context .temp_dir @@ -4716,7 +5127,7 @@ fn sync_workspace_custom_environment_path() -> Result<()> { .assert(predicate::path::is_dir()); // Similarly, `uv sync` from the child project uses `foo` relative to the workspace root - uv_snapshot!(context.filters(), context.sync().env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo").current_dir(context.temp_dir.join("child")), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo").current_dir(context.temp_dir.join("child")), @r" success: true exit_code: 0 ----- stdout ----- @@ -4725,7 +5136,7 @@ fn sync_workspace_custom_environment_path() -> Result<()> { Resolved 3 packages in [TIME] Uninstalled 1 package in [TIME] - iniconfig==2.0.0 - "###); + "); context .temp_dir @@ -4739,7 +5150,7 @@ fn sync_workspace_custom_environment_path() -> Result<()> { .assert(predicate::path::missing()); // And, `uv sync --package child` uses `foo` relative to the workspace root - uv_snapshot!(context.filters(), context.sync().arg("--package").arg("child").env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--package").arg("child").env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4747,7 +5158,7 @@ fn sync_workspace_custom_environment_path() -> Result<()> { ----- stderr ----- Resolved 3 packages in [TIME] Audited in [TIME] - "###); + "); context .temp_dir @@ -4782,7 +5193,7 @@ fn sync_empty_virtual_environment() -> Result<()> { )?; // Running `uv sync` should work - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -4794,7 +5205,7 @@ fn sync_empty_virtual_environment() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); Ok(()) } @@ -4816,7 +5227,7 @@ fn sync_legacy_non_project_warning() -> Result<()> { )?; // We should not warn if it matches the project environment - uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, context.temp_dir.join(".venv")), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, context.temp_dir.join(".venv")), @r" success: true exit_code: 0 ----- stdout ----- @@ -4826,10 +5237,10 @@ fn sync_legacy_non_project_warning() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); // Including if it's a relative path that matches - uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, ".venv"), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, ".venv"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4837,7 +5248,7 @@ fn sync_legacy_non_project_warning() -> Result<()> { ----- stderr ----- Resolved 2 packages in [TIME] Audited 1 package in [TIME] - "###); + "); // Or, if it's a link that resolves to the same path #[cfg(unix)] @@ -4847,7 +5258,7 @@ fn sync_legacy_non_project_warning() -> Result<()> { let link = context.temp_dir.join("link"); symlink(context.temp_dir.join(".venv"), &link)?; - uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, link), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, link), @r" success: true exit_code: 0 ----- stdout ----- @@ -4855,11 +5266,11 @@ fn sync_legacy_non_project_warning() -> Result<()> { ----- stderr ----- Resolved 2 packages in [TIME] Audited 1 package in [TIME] - "###); + "); } // But we should warn if it's a different path - uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, "foo"), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, "foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4868,10 +5279,10 @@ fn sync_legacy_non_project_warning() -> Result<()> { warning: `VIRTUAL_ENV=foo` does not match the project environment path `.venv` and will be ignored; use `--active` to target the active environment instead Resolved 2 packages in [TIME] Audited 1 package in [TIME] - "###); + "); // Including absolute paths - uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, context.temp_dir.join("foo")), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, context.temp_dir.join("foo")), @r" success: true exit_code: 0 ----- stdout ----- @@ -4880,10 +5291,10 @@ fn sync_legacy_non_project_warning() -> Result<()> { warning: `VIRTUAL_ENV=foo` does not match the project environment path `.venv` and will be ignored; use `--active` to target the active environment instead Resolved 2 packages in [TIME] Audited 1 package in [TIME] - "###); + "); // We should not warn if the project environment has been customized and matches - uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, "foo").env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo"), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, "foo").env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4894,10 +5305,10 @@ fn sync_legacy_non_project_warning() -> Result<()> { Resolved 2 packages in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); // But we should warn if they don't match still - uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, "foo").env(EnvVars::UV_PROJECT_ENVIRONMENT, "bar"), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, "foo").env(EnvVars::UV_PROJECT_ENVIRONMENT, "bar"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4909,14 +5320,14 @@ fn sync_legacy_non_project_warning() -> Result<()> { Resolved 2 packages in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); let child = context.temp_dir.child("child"); child.create_dir_all()?; // And `VIRTUAL_ENV` is resolved relative to the project root so with relative paths we should // warn from a child too - uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, "foo").env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo").current_dir(&child), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, "foo").env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo").current_dir(&child), @r" success: true exit_code: 0 ----- stdout ----- @@ -4925,10 +5336,10 @@ fn sync_legacy_non_project_warning() -> Result<()> { warning: `VIRTUAL_ENV=foo` does not match the project environment path `[TEMP_DIR]/foo` and will be ignored; use `--active` to target the active environment instead Resolved 2 packages in [TIME] Audited 1 package in [TIME] - "###); + "); // But, a matching absolute path shouldn't warn - uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, context.temp_dir.join("foo")).env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo").current_dir(&child), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, context.temp_dir.join("foo")).env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo").current_dir(&child), @r" success: true exit_code: 0 ----- stdout ----- @@ -4936,7 +5347,7 @@ fn sync_legacy_non_project_warning() -> Result<()> { ----- stderr ----- Resolved 2 packages in [TIME] Audited 1 package in [TIME] - "###); + "); Ok(()) } @@ -4956,7 +5367,7 @@ fn sync_update_project() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -4968,7 +5379,7 @@ fn sync_update_project() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); // Bump the project version. pyproject_toml.write_str( @@ -4985,7 +5396,7 @@ fn sync_update_project() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -4995,7 +5406,7 @@ fn sync_update_project() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + my-project==0.2.0 (from file://[TEMP_DIR]/) - "###); + "); Ok(()) } @@ -5016,7 +5427,7 @@ fn sync_environment_prompt() -> Result<()> { )?; // Running `uv sync` should create `.venv` - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -5028,7 +5439,7 @@ fn sync_environment_prompt() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); // The `pyvenv.cfg` should contain the prompt matching the project name let pyvenv_cfg = context.read(".venv/pyvenv.cfg"); @@ -5055,7 +5466,7 @@ fn no_binary() -> Result<()> { context.lock().assert().success(); - uv_snapshot!(context.filters(), context.sync().arg("--no-binary-package").arg("iniconfig"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-binary-package").arg("iniconfig"), @r" success: true exit_code: 0 ----- stdout ----- @@ -5065,11 +5476,11 @@ fn no_binary() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); assert!(context.temp_dir.child("uv.lock").exists()); - uv_snapshot!(context.filters(), context.sync().arg("--reinstall").arg("--no-binary"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--reinstall").arg("--no-binary"), @r" success: true exit_code: 0 ----- stdout ----- @@ -5080,9 +5491,9 @@ fn no_binary() -> Result<()> { Uninstalled 1 package in [TIME] Installed 1 package in [TIME] ~ iniconfig==2.0.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--reinstall").env("UV_NO_BINARY_PACKAGE", "iniconfig"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--reinstall").env("UV_NO_BINARY_PACKAGE", "iniconfig"), @r" success: true exit_code: 0 ----- stdout ----- @@ -5093,9 +5504,9 @@ fn no_binary() -> Result<()> { Uninstalled 1 package in [TIME] Installed 1 package in [TIME] ~ iniconfig==2.0.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--reinstall").env("UV_NO_BINARY", "1"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--reinstall").env("UV_NO_BINARY", "1"), @r" success: true exit_code: 0 ----- stdout ----- @@ -5106,7 +5517,7 @@ fn no_binary() -> Result<()> { Uninstalled 1 package in [TIME] Installed 1 package in [TIME] ~ iniconfig==2.0.0 - "###); + "); uv_snapshot!(context.filters(), context.sync().arg("--reinstall").env("UV_NO_BINARY", "iniconfig"), @r###" success: false @@ -5139,7 +5550,7 @@ fn no_binary_error() -> Result<()> { context.lock().assert().success(); - uv_snapshot!(context.filters(), context.sync().arg("--no-binary-package").arg("odrive"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-binary-package").arg("odrive"), @r" success: false exit_code: 2 ----- stdout ----- @@ -5147,7 +5558,7 @@ fn no_binary_error() -> Result<()> { ----- stderr ----- Resolved 31 packages in [TIME] error: Distribution `odrive==0.6.8 @ registry+https://pypi.org/simple` can't be installed because it is marked as `--no-binary` but has no source distribution - "###); + "); assert!(context.temp_dir.child("uv.lock").exists()); @@ -5171,7 +5582,7 @@ fn no_build() -> Result<()> { context.lock().assert().success(); - uv_snapshot!(context.filters(), context.sync().arg("--no-build-package").arg("iniconfig"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-build-package").arg("iniconfig"), @r" success: true exit_code: 0 ----- stdout ----- @@ -5181,11 +5592,11 @@ fn no_build() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); assert!(context.temp_dir.child("uv.lock").exists()); - uv_snapshot!(context.filters(), context.sync().arg("--reinstall").env("UV_NO_BUILD_PACKAGE", "iniconfig"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--reinstall").env("UV_NO_BUILD_PACKAGE", "iniconfig"), @r" success: true exit_code: 0 ----- stdout ----- @@ -5196,7 +5607,7 @@ fn no_build() -> Result<()> { Uninstalled 1 package in [TIME] Installed 1 package in [TIME] ~ iniconfig==2.0.0 - "###); + "); Ok(()) } @@ -5218,7 +5629,7 @@ fn no_build_error() -> Result<()> { context.lock().assert().success(); - uv_snapshot!(context.filters(), context.sync().arg("--no-build-package").arg("django-allauth"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-build-package").arg("django-allauth"), @r" success: false exit_code: 2 ----- stdout ----- @@ -5226,7 +5637,7 @@ fn no_build_error() -> Result<()> { ----- stderr ----- Resolved 19 packages in [TIME] error: Distribution `django-allauth==0.51.0 @ registry+https://pypi.org/simple` can't be installed because it is marked as `--no-build` but has no binary distribution - "###); + "); uv_snapshot!(context.filters(), context.sync().arg("--no-build"), @r" success: false @@ -5248,7 +5659,7 @@ fn no_build_error() -> Result<()> { error: Distribution `django-allauth==0.51.0 @ registry+https://pypi.org/simple` can't be installed because it is marked as `--no-build` but has no binary distribution "); - uv_snapshot!(context.filters(), context.sync().arg("--reinstall").env("UV_NO_BUILD_PACKAGE", "django-allauth"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--reinstall").env("UV_NO_BUILD_PACKAGE", "django-allauth"), @r" success: false exit_code: 2 ----- stdout ----- @@ -5256,7 +5667,7 @@ fn no_build_error() -> Result<()> { ----- stderr ----- Resolved 19 packages in [TIME] error: Distribution `django-allauth==0.51.0 @ registry+https://pypi.org/simple` can't be installed because it is marked as `--no-build` but has no binary distribution - "###); + "); uv_snapshot!(context.filters(), context.sync().arg("--reinstall").env("UV_NO_BUILD", "django-allauth"), @r###" success: false @@ -5300,7 +5711,7 @@ fn sync_wheel_url_source_error() -> Result<()> { Resolved 3 packages in [TIME] "###); - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: false exit_code: 2 ----- stdout ----- @@ -5310,7 +5721,7 @@ fn sync_wheel_url_source_error() -> Result<()> { error: Distribution `cffi==1.17.1 @ direct+https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl` can't be installed because the binary distribution is incompatible with the current platform hint: You're using CPython 3.12 (`cp312`), but `cffi` (v1.17.1) only has wheels with the following Python ABI tag: `cp310` - "###); + "); Ok(()) } @@ -5351,7 +5762,7 @@ fn sync_wheel_path_source_error() -> Result<()> { Resolved 3 packages in [TIME] "###); - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: false exit_code: 2 ----- stdout ----- @@ -5361,7 +5772,7 @@ fn sync_wheel_path_source_error() -> Result<()> { error: Distribution `cffi==1.17.1 @ path+cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl` can't be installed because the binary distribution is incompatible with the current platform hint: You're using CPython 3.12 (`cp312`), but `cffi` (v1.17.1) only has wheels with the following Python ABI tag: `cp310` - "###); + "); Ok(()) } @@ -5423,7 +5834,7 @@ fn sync_override_package() -> Result<()> { .touch()?; // Syncing the project should _not_ install `core`. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -5433,7 +5844,7 @@ fn sync_override_package() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + project==0.0.0 (from file://[TEMP_DIR]/) - "###); + "); // Mark the source as `package = true`. let pyproject_toml = context.temp_dir.child("pyproject.toml"); @@ -5455,7 +5866,7 @@ fn sync_override_package() -> Result<()> { )?; // Syncing the project _should_ install `core`. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -5467,7 +5878,7 @@ fn sync_override_package() -> Result<()> { Installed 2 packages in [TIME] + core==0.1.0 (from file://[TEMP_DIR]/core) ~ project==0.0.0 (from file://[TEMP_DIR]/) - "###); + "); // Remove `package = false`. let pyproject_toml = context.temp_dir.child("core").child("pyproject.toml"); @@ -5485,7 +5896,7 @@ fn sync_override_package() -> Result<()> { )?; // Syncing the project _should_ install `core`. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -5496,7 +5907,7 @@ fn sync_override_package() -> Result<()> { Uninstalled 1 package in [TIME] Installed 1 package in [TIME] ~ core==0.1.0 (from file://[TEMP_DIR]/core) - "###); + "); // Mark the source as `package = false`. let pyproject_toml = context.temp_dir.child("pyproject.toml"); @@ -5518,7 +5929,7 @@ fn sync_override_package() -> Result<()> { )?; // Syncing the project should _not_ install `core`. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -5530,7 +5941,7 @@ fn sync_override_package() -> Result<()> { Installed 1 package in [TIME] - core==0.1.0 (from file://[TEMP_DIR]/core) ~ project==0.0.0 (from file://[TEMP_DIR]/) - "###); + "); Ok(()) } @@ -5592,7 +6003,7 @@ fn transitive_dev() -> Result<()> { let init = src.child("__init__.py"); init.touch()?; - uv_snapshot!(context.filters(), context.sync().arg("--dev"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--dev"), @r" success: true exit_code: 0 ----- stdout ----- @@ -5605,7 +6016,7 @@ fn transitive_dev() -> Result<()> { + child==0.1.0 (from file://[TEMP_DIR]/child) + idna==3.6 + sniffio==1.3.1 - "###); + "); Ok(()) } @@ -5665,7 +6076,7 @@ fn sync_no_editable() -> Result<()> { let init = src.child("__init__.py"); init.touch()?; - uv_snapshot!(context.filters(), context.sync().arg("--no-editable"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-editable"), @r" success: true exit_code: 0 ----- stdout ----- @@ -5676,7 +6087,7 @@ fn sync_no_editable() -> Result<()> { Installed 2 packages in [TIME] + child==0.1.0 (from file://[TEMP_DIR]/child) + root==0.1.0 (from file://[TEMP_DIR]/) - "###); + "); uv_snapshot!(context.filters(), context.sync().env(EnvVars::UV_NO_EDITABLE, "1"), @r" success: true @@ -5731,7 +6142,7 @@ fn sync_scripts_without_build_system() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -5740,7 +6151,7 @@ fn sync_scripts_without_build_system() -> Result<()> { warning: Skipping installation of entry points (`project.scripts`) because this project is not packaged; to install entry points, set `tool.uv.package = true` or define a `build-system` Resolved 1 package in [TIME] Audited in [TIME] - "###); + "); Ok(()) } @@ -5780,7 +6191,7 @@ fn sync_scripts_project_not_packaged() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -5789,7 +6200,7 @@ fn sync_scripts_project_not_packaged() -> Result<()> { warning: Skipping installation of entry points (`project.scripts`) because this project is not packaged; to install entry points, set `tool.uv.package = true` or define a `build-system` Resolved 1 package in [TIME] Audited in [TIME] - "###); + "); Ok(()) } @@ -5822,7 +6233,7 @@ fn sync_dynamic_extra() -> Result<()> { .child("requirements-dev.txt") .write_str("typing-extensions")?; - uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("dev"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("dev"), @r" success: true exit_code: 0 ----- stdout ----- @@ -5834,7 +6245,7 @@ fn sync_dynamic_extra() -> Result<()> { + iniconfig==2.0.0 + project==0.1.0 (from file://[TEMP_DIR]/) + typing-extensions==4.10.0 - "###); + "); let lock = context.read("uv.lock"); @@ -5895,7 +6306,7 @@ fn sync_dynamic_extra() -> Result<()> { ); // Check that we can re-read the lockfile. - uv_snapshot!(context.filters(), context.sync().arg("--locked"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--locked"), @r" success: true exit_code: 0 ----- stdout ----- @@ -5904,7 +6315,7 @@ fn sync_dynamic_extra() -> Result<()> { Resolved 3 packages in [TIME] Uninstalled 1 package in [TIME] - typing-extensions==4.10.0 - "###); + "); Ok(()) } @@ -5974,7 +6385,7 @@ fn build_system_requires_workspace() -> Result<()> { ", })?; - uv_snapshot!(context.filters(), context.sync().current_dir(context.temp_dir.child("project")), @r###" + uv_snapshot!(context.filters(), context.sync().current_dir(context.temp_dir.child("project")), @r" success: true exit_code: 0 ----- stdout ----- @@ -5987,7 +6398,7 @@ fn build_system_requires_workspace() -> Result<()> { Installed 2 packages in [TIME] + iniconfig==2.0.0 + project==0.1.0 (from file://[TEMP_DIR]/project) - "###); + "); Ok(()) } @@ -6054,7 +6465,7 @@ fn build_system_requires_path() -> Result<()> { ", })?; - uv_snapshot!(context.filters(), context.sync().current_dir(context.temp_dir.child("project")), @r###" + uv_snapshot!(context.filters(), context.sync().current_dir(context.temp_dir.child("project")), @r" success: true exit_code: 0 ----- stdout ----- @@ -6067,7 +6478,7 @@ fn build_system_requires_path() -> Result<()> { Installed 2 packages in [TIME] + iniconfig==2.0.0 + project==0.1.0 (from file://[TEMP_DIR]/project) - "###); + "); Ok(()) } @@ -6119,7 +6530,7 @@ fn sync_invalid_environment() -> Result<()> { fs_err::write(context.temp_dir.join(".venv").join("file"), b"")?; // We can delete and use it - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -6132,7 +6543,7 @@ fn sync_invalid_environment() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); let bin = venv_bin_path(context.temp_dir.join(".venv")); @@ -6141,7 +6552,7 @@ fn sync_invalid_environment() -> Result<()> { { fs_err::remove_file(bin.join("python"))?; fs_err::os::unix::fs::symlink(context.temp_dir.join("does-not-exist"), bin.join("python"))?; - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -6154,7 +6565,7 @@ fn sync_invalid_environment() -> Result<()> { Resolved 2 packages in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); } // But if the Python executable is missing entirely we should also fail @@ -6242,7 +6653,7 @@ fn sync_no_sources_missing_member() -> Result<()> { let init = src.child("__init__.py"); init.touch()?; - uv_snapshot!(context.filters(), context.sync().arg("--no-sources"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-sources"), @r" success: true exit_code: 0 ----- stdout ----- @@ -6254,7 +6665,7 @@ fn sync_no_sources_missing_member() -> Result<()> { + anyio==4.3.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); Ok(()) } @@ -6273,7 +6684,7 @@ fn sync_python_version() -> Result<()> { "#})?; // We should respect the project's required version, not the first on the path - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -6287,7 +6698,7 @@ fn sync_python_version() -> Result<()> { + anyio==3.7.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); // Unless explicitly requested... uv_snapshot!(context.filters(), context.sync().arg("--python").arg("3.10"), @r" @@ -6310,7 +6721,7 @@ fn sync_python_version() -> Result<()> { ----- stderr ----- "###); - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -6324,7 +6735,7 @@ fn sync_python_version() -> Result<()> { + anyio==3.7.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); // Create a pin that's incompatible with the project uv_snapshot!(context.filters(), context.python_pin().arg("3.10").arg("--no-workspace"), @r###" @@ -6363,7 +6774,7 @@ fn sync_python_version() -> Result<()> { "#}) .unwrap(); - uv_snapshot!(context.filters(), context.sync().current_dir(&child_dir), @r###" + uv_snapshot!(context.filters(), context.sync().current_dir(&child_dir), @r" success: true exit_code: 0 ----- stdout ----- @@ -6376,7 +6787,7 @@ fn sync_python_version() -> Result<()> { + anyio==3.7.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); Ok(()) } @@ -6406,7 +6817,7 @@ fn sync_explicit() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -6416,13 +6827,13 @@ fn sync_explicit() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + idna==2.7 - "###); + "); // Clear the environment. fs_err::remove_dir_all(&context.venv)?; // The package should be drawn from the cache. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -6433,7 +6844,7 @@ fn sync_explicit() -> Result<()> { Resolved 2 packages in [TIME] Installed 1 package in [TIME] + idna==2.7 - "###); + "); Ok(()) } @@ -6495,7 +6906,7 @@ fn sync_all() -> Result<()> { context.lock().assert().success(); // Sync all workspace members. - uv_snapshot!(context.filters(), context.sync().arg("--all-packages"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-packages"), @r" success: true exit_code: 0 ----- stdout ----- @@ -6510,7 +6921,7 @@ fn sync_all() -> Result<()> { + iniconfig==2.0.0 + project==0.1.0 (from file://[TEMP_DIR]/) + sniffio==1.3.1 - "###); + "); Ok(()) } @@ -6576,7 +6987,7 @@ fn sync_all_extras() -> Result<()> { context.lock().assert().success(); // Sync an extra that exists in both the parent and child. - uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--extra").arg("types"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--extra").arg("types"), @r" success: true exit_code: 0 ----- stdout ----- @@ -6589,10 +7000,10 @@ fn sync_all_extras() -> Result<()> { + iniconfig==2.0.0 + sniffio==1.3.1 + typing-extensions==4.10.0 - "###); + "); // Sync an extra that only exists in the child. - uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--extra").arg("testing"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--extra").arg("testing"), @r" success: true exit_code: 0 ----- stdout ----- @@ -6605,10 +7016,10 @@ fn sync_all_extras() -> Result<()> { + packaging==24.0 - sniffio==1.3.1 - typing-extensions==4.10.0 - "###); + "); // Sync all extras. - uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--all-extras"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--all-extras"), @r" success: true exit_code: 0 ----- stdout ----- @@ -6621,10 +7032,10 @@ fn sync_all_extras() -> Result<()> { + idna==3.6 + sniffio==1.3.1 + typing-extensions==4.10.0 - "###); + "); // Sync all extras excluding an extra that exists in both the parent and child. - uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--all-extras").arg("--no-extra").arg("types"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--all-extras").arg("--no-extra").arg("types"), @r" success: true exit_code: 0 ----- stdout ----- @@ -6633,10 +7044,10 @@ fn sync_all_extras() -> Result<()> { Resolved 8 packages in [TIME] Uninstalled 1 package in [TIME] - typing-extensions==4.10.0 - "###); + "); // Sync an extra that doesn't exist. - uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--extra").arg("foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--extra").arg("foo"), @r" success: false exit_code: 2 ----- stdout ----- @@ -6644,10 +7055,10 @@ fn sync_all_extras() -> Result<()> { ----- stderr ----- Resolved 8 packages in [TIME] error: Extra `foo` is not defined in any project's `optional-dependencies` table - "###); + "); // Sync all extras excluding an extra that doesn't exist. - uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--all-extras").arg("--no-extra").arg("foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--all-extras").arg("--no-extra").arg("foo"), @r" success: false exit_code: 2 ----- stdout ----- @@ -6655,7 +7066,7 @@ fn sync_all_extras() -> Result<()> { ----- stderr ----- Resolved 8 packages in [TIME] error: Extra `foo` is not defined in any project's `optional-dependencies` table - "###); + "); Ok(()) } @@ -6731,7 +7142,7 @@ fn sync_all_extras_dynamic() -> Result<()> { context.lock().assert().success(); // Sync an extra that exists in the parent. - uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--extra").arg("types"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--extra").arg("types"), @r" success: true exit_code: 0 ----- stdout ----- @@ -6743,10 +7154,10 @@ fn sync_all_extras_dynamic() -> Result<()> { + child==0.1.0 (from file://[TEMP_DIR]/child) + project==0.1.0 (from file://[TEMP_DIR]/) + sniffio==1.3.1 - "###); + "); // Sync a dynamic extra that exists in the child. - uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--extra").arg("dev"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--extra").arg("dev"), @r" success: true exit_code: 0 ----- stdout ----- @@ -6758,10 +7169,10 @@ fn sync_all_extras_dynamic() -> Result<()> { Installed 1 package in [TIME] - sniffio==1.3.1 + typing-extensions==4.10.0 - "###); + "); // Sync a dynamic extra that doesn't exist in the child. - uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--extra").arg("foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--extra").arg("foo"), @r" success: false exit_code: 2 ----- stdout ----- @@ -6769,7 +7180,7 @@ fn sync_all_extras_dynamic() -> Result<()> { ----- stderr ----- Resolved 6 packages in [TIME] error: Extra `foo` is not defined in any project's `optional-dependencies` table - "###); + "); Ok(()) } @@ -6836,7 +7247,7 @@ fn sync_all_groups() -> Result<()> { context.lock().assert().success(); // Sync a group that exists in both the parent and child. - uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--group").arg("types"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--group").arg("types"), @r" success: true exit_code: 0 ----- stdout ----- @@ -6849,10 +7260,10 @@ fn sync_all_groups() -> Result<()> { + iniconfig==2.0.0 + sniffio==1.3.1 + typing-extensions==4.10.0 - "###); + "); // Sync a group that only exists in the child. - uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--group").arg("testing"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--group").arg("testing"), @r" success: true exit_code: 0 ----- stdout ----- @@ -6865,10 +7276,10 @@ fn sync_all_groups() -> Result<()> { + packaging==24.0 - sniffio==1.3.1 - typing-extensions==4.10.0 - "###); + "); // Sync a group that doesn't exist. - uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--group").arg("foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--group").arg("foo"), @r" success: false exit_code: 2 ----- stdout ----- @@ -6876,10 +7287,10 @@ fn sync_all_groups() -> Result<()> { ----- stderr ----- Resolved 8 packages in [TIME] error: Group `foo` is not defined in any project's `dependency-groups` table - "###); + "); // Sync an empty group. - uv_snapshot!(context.filters(), context.sync().arg("--group").arg("empty"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("empty"), @r" success: true exit_code: 0 ----- stdout ----- @@ -6888,7 +7299,7 @@ fn sync_all_groups() -> Result<()> { Resolved 8 packages in [TIME] Uninstalled 1 package in [TIME] - packaging==24.0 - "###); + "); Ok(()) } @@ -6940,7 +7351,7 @@ fn sync_multiple_sources_index_disjoint_extras() -> Result<()> { // Generate a lockfile. context.lock().assert().success(); - uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("cu124"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("cu124"), @r" success: true exit_code: 0 ----- stdout ----- @@ -6951,7 +7362,7 @@ fn sync_multiple_sources_index_disjoint_extras() -> Result<()> { Installed 2 packages in [TIME] + jinja2==3.1.3 + markupsafe==2.1.5 - "###); + "); Ok(()) } @@ -6982,7 +7393,7 @@ fn sync_derivation_chain() -> Result<()> { .chain([(r"/.*/src", "/[TMP]/src")]) .collect::>(); - uv_snapshot!(filters, context.sync(), @r###" + uv_snapshot!(filters, context.sync(), @r#" success: false exit_code: 1 ----- stdout ----- @@ -7013,7 +7424,7 @@ fn sync_derivation_chain() -> Result<()> { hint: This usually indicates a problem with the package or the build environment. help: `wsgiref` (v0.1.2) was included because `project` (v0.1.0) depends on `wsgiref` - "###); + "#); Ok(()) } @@ -7045,7 +7456,7 @@ fn sync_derivation_chain_extra() -> Result<()> { .chain([(r"/.*/src", "/[TMP]/src")]) .collect::>(); - uv_snapshot!(filters, context.sync().arg("--extra").arg("wsgi"), @r###" + uv_snapshot!(filters, context.sync().arg("--extra").arg("wsgi"), @r#" success: false exit_code: 1 ----- stdout ----- @@ -7076,7 +7487,7 @@ fn sync_derivation_chain_extra() -> Result<()> { hint: This usually indicates a problem with the package or the build environment. help: `wsgiref` (v0.1.2) was included because `project[wsgi]` (v0.1.0) depends on `wsgiref` - "###); + "#); Ok(()) } @@ -7110,7 +7521,7 @@ fn sync_derivation_chain_group() -> Result<()> { .chain([(r"/.*/src", "/[TMP]/src")]) .collect::>(); - uv_snapshot!(filters, context.sync().arg("--group").arg("wsgi"), @r###" + uv_snapshot!(filters, context.sync().arg("--group").arg("wsgi"), @r#" success: false exit_code: 1 ----- stdout ----- @@ -7141,7 +7552,7 @@ fn sync_derivation_chain_group() -> Result<()> { hint: This usually indicates a problem with the package or the build environment. help: `wsgiref` (v0.1.2) was included because `project:wsgi` (v0.1.0) depends on `wsgiref` - "###); + "#); Ok(()) } @@ -7235,7 +7646,7 @@ fn sync_stale_egg_info() -> Result<()> { } ); - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -7247,7 +7658,7 @@ fn sync_stale_egg_info() -> Result<()> { + member==0.1.dev5+gfea1041 (from git+https://github.com/astral-sh/uv-stale-egg-info-test.git@fea10416b9c479ac88fb217e14e40249b63bfbee#subdirectory=member) + root==0.1.dev5+gfea1041 (from git+https://github.com/astral-sh/uv-stale-egg-info-test.git@fea10416b9c479ac88fb217e14e40249b63bfbee) + setuptools==69.2.0 - "###); + "); Ok(()) } @@ -7330,7 +7741,7 @@ fn sync_git_repeated_member_static_metadata() -> Result<()> { } ); - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -7341,7 +7752,7 @@ fn sync_git_repeated_member_static_metadata() -> Result<()> { Installed 2 packages in [TIME] + uv-git-workspace-in-root==0.1.0 (from git+https://github.com/astral-sh/workspace-in-root-test.git@d3ab48d2338296d47e28dbb2fb327c5e2ac4ac68) + workspace-member-in-subdir==0.1.0 (from git+https://github.com/astral-sh/workspace-in-root-test.git@d3ab48d2338296d47e28dbb2fb327c5e2ac4ac68#subdirectory=workspace-member-in-subdir) - "###); + "); Ok(()) } @@ -7446,7 +7857,7 @@ fn sync_git_repeated_member_dynamic_metadata() -> Result<()> { } ); - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -7459,7 +7870,7 @@ fn sync_git_repeated_member_dynamic_metadata() -> Result<()> { + iniconfig==2.0.0 + package==0.1.0 (from git+https://github.com/astral-sh/uv-dynamic-metadata-test.git@6c5aa0a65db737c9e7e2e60dc865bd8087012e64) + typing-extensions==4.10.0 - "###); + "); Ok(()) } @@ -7542,7 +7953,7 @@ fn sync_git_repeated_member_backwards_path() -> Result<()> { } ); - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -7553,7 +7964,7 @@ fn sync_git_repeated_member_backwards_path() -> Result<()> { Installed 2 packages in [TIME] + dependency==0.1.0 (from git+https://github.com/astral-sh/uv-backwards-path-test@4bcc7fcd2e548c2ab7ba6b97b1c4e3ababccc7a9#subdirectory=dependency) + package==0.1.0 (from git+https://github.com/astral-sh/uv-backwards-path-test@4bcc7fcd2e548c2ab7ba6b97b1c4e3ababccc7a9#subdirectory=root) - "###); + "); Ok(()) } @@ -7578,7 +7989,7 @@ fn mismatched_name_self_editable() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: false exit_code: 1 ----- stdout ----- @@ -7588,7 +7999,7 @@ fn mismatched_name_self_editable() -> Result<()> { × Failed to build `foo @ file://[TEMP_DIR]/` ╰─▶ Package metadata name `project` does not match given name `foo` help: `foo` was included because `project` (v0.1.0) depends on `foo` - "###); + "); Ok(()) } @@ -7610,7 +8021,7 @@ fn mismatched_name_cached_wheel() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -7620,7 +8031,7 @@ fn mismatched_name_cached_wheel() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 (from https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz) - "###); + "); pyproject_toml.write_str( r#" @@ -7632,7 +8043,7 @@ fn mismatched_name_cached_wheel() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: false exit_code: 1 ----- stdout ----- @@ -7640,7 +8051,7 @@ fn mismatched_name_cached_wheel() -> Result<()> { ----- stderr ----- × Failed to download and build `foo @ https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz` ╰─▶ Package metadata name `iniconfig` does not match given name `foo` - "###); + "); Ok(()) } @@ -7720,7 +8131,7 @@ fn sync_git_path_dependency() -> Result<()> { } ); - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -7731,7 +8142,7 @@ fn sync_git_path_dependency() -> Result<()> { Installed 2 packages in [TIME] + package1==0.1.0 (from git+https://github.com/astral-sh/uv-path-dependency-test.git@28781b32cf1f260cdb2c8040628079eb265202bd#subdirectory=package1) + package2==0.1.0 (from git+https://github.com/astral-sh/uv-path-dependency-test.git@28781b32cf1f260cdb2c8040628079eb265202bd#subdirectory=package2) - "###); + "); Ok(()) } @@ -7835,7 +8246,7 @@ fn sync_build_tag() -> Result<()> { "###); // Install from the lockfile. - uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r" success: true exit_code: 0 ----- stdout ----- @@ -7844,7 +8255,7 @@ fn sync_build_tag() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + build-tag==1.0.0 - "###); + "); // Ensure that we choose the highest build tag (5). uv_snapshot!(context.filters(), context.run().arg("--no-sync").arg("python").arg("-c").arg("import build_tag; build_tag.main()"), @r###" @@ -7904,7 +8315,7 @@ fn url_hash_mismatch() -> Result<()> { "#})?; // Running `uv sync` should fail. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: false exit_code: 1 ----- stdout ----- @@ -7920,7 +8331,7 @@ fn url_hash_mismatch() -> Result<()> { Computed: sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 help: `iniconfig` was included because `project` (v0.1.0) depends on `iniconfig` - "###); + "); Ok(()) } @@ -7977,7 +8388,7 @@ fn path_hash_mismatch() -> Result<()> { "#})?; // Running `uv sync` should fail. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: false exit_code: 1 ----- stdout ----- @@ -7993,7 +8404,7 @@ fn path_hash_mismatch() -> Result<()> { Computed: sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 help: `iniconfig` was included because `project` (v0.1.0) depends on `iniconfig` - "###); + "); Ok(()) } @@ -8029,7 +8440,7 @@ fn find_links_relative_in_config_works_from_subdir() -> Result<()> { subdir.create_dir_all()?; // Run `uv sync --offline` from subdir. We expect it to find the local wheel in ../packages/. - uv_snapshot!(context.filters(), context.sync().current_dir(&subdir).arg("--offline"), @r###" + uv_snapshot!(context.filters(), context.sync().current_dir(&subdir).arg("--offline"), @r" success: true exit_code: 0 ----- stdout ----- @@ -8039,7 +8450,7 @@ fn find_links_relative_in_config_works_from_subdir() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + ok==1.0.0 - "###); + "); Ok(()) } @@ -8060,23 +8471,23 @@ fn sync_dry_run() -> Result<()> { )?; // Perform a `--dry-run`. - uv_snapshot!(context.filters(), context.sync().arg("--dry-run"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--dry-run"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] - Would create virtual environment at: .venv + Would create project environment at: .venv Resolved 2 packages in [TIME] Would create lockfile at: uv.lock Would download 1 package Would install 1 package + iniconfig==2.0.0 - "###); + "); // Perform a full sync. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -8088,7 +8499,7 @@ fn sync_dry_run() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); // Update the requirements. pyproject_toml.write_str( @@ -8101,13 +8512,13 @@ fn sync_dry_run() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync().arg("--dry-run"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--dry-run"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - Discovered existing environment at: .venv + Would use project environment at: .venv Resolved 2 packages in [TIME] Would update lockfile at: uv.lock Would download 1 package @@ -8115,7 +8526,7 @@ fn sync_dry_run() -> Result<()> { Would install 1 package - iniconfig==2.0.0 + typing-extensions==4.10.0 - "###); + "); // Update the `requires-python`. pyproject_toml.write_str( @@ -8135,7 +8546,7 @@ fn sync_dry_run() -> Result<()> { ----- stderr ----- Using CPython 3.9.[X] interpreter at: [PYTHON-3.9] - Would replace existing virtual environment at: .venv + Would replace project environment at: .venv warning: Ignoring existing lockfile due to fork markers being disjoint with `requires-python`: `python_full_version >= '3.12'` vs `python_full_version == '3.9.*'` Resolved 2 packages in [TIME] Would update lockfile at: uv.lock @@ -8175,7 +8586,7 @@ fn sync_dry_run() -> Result<()> { ----- stdout ----- ----- stderr ----- - Discovered existing environment at: .venv + Would use project environment at: .venv Resolved 2 packages in [TIME] Found up-to-date lockfile at: uv.lock Audited 1 package in [TIME] @@ -8223,7 +8634,7 @@ fn sync_dry_run_and_locked() -> Result<()> { ----- stdout ----- ----- stderr ----- - Discovered existing environment at: .venv + Would use project environment at: .venv Resolved 2 packages in [TIME] Would download 1 package Would install 1 package @@ -8275,8 +8686,7 @@ fn sync_dry_run_and_frozen() -> Result<()> { ----- stdout ----- ----- stderr ----- - Discovered existing environment at: .venv - Found up-to-date lockfile at: uv.lock + Would use project environment at: .venv Would download 3 packages Would install 3 packages + anyio==3.7.0 @@ -8371,7 +8781,7 @@ fn sync_script() -> Result<()> { "# })?; - uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r###" + uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r" success: true exit_code: 0 ----- stdout ----- @@ -8381,7 +8791,7 @@ fn sync_script() -> Result<()> { Resolved 3 packages in [TIME] Uninstalled 1 package in [TIME] - iniconfig==2.0.0 - "###); + "); // Modify the `requires-python`. script.write_str(indoc! { r#" @@ -8396,13 +8806,13 @@ fn sync_script() -> Result<()> { "# })?; - uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r###" + uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - Recreating script environment at: [CACHE_DIR]/environments-v2/script-[HASH] + Updating script environment at: [CACHE_DIR]/environments-v2/script-[HASH] Resolved 5 packages in [TIME] Prepared 2 packages in [TIME] Installed 5 packages in [TIME] @@ -8411,7 +8821,7 @@ fn sync_script() -> Result<()> { + idna==3.6 + sniffio==1.3.1 + typing-extensions==4.10.0 - "###); + "); // `--locked` and `--frozen` should fail with helpful error messages. uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").arg("--locked"), @r" @@ -8662,7 +9072,7 @@ fn sync_locked_script() -> Result<()> { ----- stdout ----- ----- stderr ----- - Recreating script environment at: [CACHE_DIR]/environments-v2/script-[HASH] + Updating script environment at: [CACHE_DIR]/environments-v2/script-[HASH] warning: Ignoring existing lockfile due to fork markers being disjoint with `requires-python`: `python_full_version >= '3.11'` vs `python_full_version >= '3.8' and python_full_version < '3.11'` Resolved 6 packages in [TIME] error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. @@ -8722,7 +9132,7 @@ fn sync_script_with_compatible_build_constraints() -> Result<()> { )]) .collect::>(); - uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r###" + uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r" success: true exit_code: 0 ----- stdout ----- @@ -8738,7 +9148,7 @@ fn sync_script_with_compatible_build_constraints() -> Result<()> { + requests==1.2.0 + sniffio==1.3.1 + typing-extensions==4.10.0 - "###); + "); Ok(()) } @@ -8774,7 +9184,7 @@ fn sync_script_with_incompatible_build_constraints() -> Result<()> { "# })?; - uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r###" + uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r" success: false exit_code: 1 ----- stdout ----- @@ -8785,7 +9195,7 @@ fn sync_script_with_incompatible_build_constraints() -> Result<()> { ├─▶ Failed to resolve requirements from `setup.py` build ├─▶ No solution found when resolving: `setuptools>=40.8.0` ╰─▶ Because you require setuptools>=40.8.0 and setuptools==1, we can conclude that your requirements are unsatisfiable. - "###); + "); Ok(()) } @@ -8808,7 +9218,7 @@ fn unsupported_git_scheme() -> Result<()> { "#}, )?; - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: false exit_code: 1 ----- stdout ----- @@ -8819,7 +9229,7 @@ fn unsupported_git_scheme() -> Result<()> { × Failed to build `foo @ file://[TEMP_DIR]/` ├─▶ Failed to parse entry: `foo` ╰─▶ Unsupported Git URL scheme `c:` in `c:/home/ferris/projects/foo` (expected one of `https:`, `ssh:`, or `file:`) - "###); + "); Ok(()) } @@ -8858,7 +9268,7 @@ fn multiple_group_conflicts() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -8866,9 +9276,9 @@ fn multiple_group_conflicts() -> Result<()> { ----- stderr ----- Resolved 3 packages in [TIME] Audited in [TIME] - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--group").arg("baz"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("baz"), @r" success: true exit_code: 0 ----- stdout ----- @@ -8878,9 +9288,9 @@ fn multiple_group_conflicts() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo").arg("--group").arg("baz"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo").arg("--group").arg("baz"), @r" success: true exit_code: 0 ----- stdout ----- @@ -8888,9 +9298,9 @@ fn multiple_group_conflicts() -> Result<()> { ----- stderr ----- Resolved 3 packages in [TIME] Audited 1 package in [TIME] - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--group").arg("bar").arg("--group").arg("baz"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("bar").arg("--group").arg("baz"), @r" success: true exit_code: 0 ----- stdout ----- @@ -8902,7 +9312,7 @@ fn multiple_group_conflicts() -> Result<()> { Installed 1 package in [TIME] - iniconfig==2.0.0 + iniconfig==1.1.1 - "###); + "); uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo").arg("--group").arg("bar"), @r" success: false @@ -9316,7 +9726,7 @@ fn prune_cache_url_subdirectory() -> Result<()> { context.prune().arg("--ci").assert().success(); // Install the project. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -9329,7 +9739,7 @@ fn prune_cache_url_subdirectory() -> Result<()> { + idna==3.6 + root==0.0.1 (from https://github.com/user-attachments/files/18216295/subdirectory-test.tar.gz#subdirectory=packages/root) + sniffio==1.3.1 - "###); + "); Ok(()) } @@ -9745,7 +10155,7 @@ fn sync_upload_time() -> Result<()> { "#)?; // Install from the lockfile. - uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r" success: true exit_code: 0 ----- stdout ----- @@ -9756,17 +10166,17 @@ fn sync_upload_time() -> Result<()> { + anyio==3.7.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); // Re-install from the lockfile. - uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Audited 3 packages in [TIME] - "###); + "); Ok(()) } @@ -9994,7 +10404,7 @@ fn read_only() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -10004,7 +10414,7 @@ fn read_only() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); assert!(context.temp_dir.child("uv.lock").exists()); diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 0364703c2..13df63c19 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -1114,7 +1114,12 @@ uv sync [OPTIONS]
    --only-group only-group

    Only include dependencies from the specified dependency group.

    The project and its dependencies will be omitted.

    May be provided multiple times. Implies --no-default-groups.

    -
    --package package

    Sync for a specific package in the workspace.

    +
    --output-format output-format

    Select the output format

    +

    [default: text]

    Possible values:

    +
      +
    • text: Display the result in a human-readable format
    • +
    • json: Display the result in JSON format
    • +
    --package package

    Sync for a specific package in the workspace.

    The workspace's environment (.venv) is updated to reflect the subset of dependencies declared by the specified workspace member package.

    If the workspace member does not exist, uv will exit with an error.

    --prerelease prerelease

    The strategy to use when considering pre-release versions.

    From 4d82e8886340821b435f36e6eccccf95654ca23a Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Mon, 14 Jul 2025 16:35:34 +0100 Subject: [PATCH 208/349] Follow links when cache-key is a glob (#13438) ## Summary There's some inconsistent behaviour in handling symlinks when `cache-key` is a glob or a file path. This PR attempts to address that. - When cache-key is a path, [`Path::metadata()`](https://doc.rust-lang.org/std/path/struct.Path.html#method.metadata) is used to check if it's a file or not. According to the docs: > This function will traverse symbolic links to query information about the destination file. So, if the target file is a symlink, it will be resolved and the metadata will be queried for the underlying file. - When cache-key is a glob, `globwalk` is used, specifically allowing for symlinks: ```rust .file_type(globwalk::FileType::FILE | globwalk::FileType::SYMLINK) ``` - However, without enabling link following, `DirEntry::metadata()` will return an equivalent of `Path::symlink_metadata()` (and not `Path::metadata()`), which will have a file type that looks like ```rust FileType { is_file: false, is_dir: false, is_symlink: true, .. } ``` - Then, there's a check for `metadata.is_file()` which fails and complains that the target entry "is a directory when file was expected". - TLDR: glob cache-keys don't work with symlinks. ## Solutions Option 1 (current PR): follow symlinks. Option 2 (also doable): don't follow symlinks, but resolve the resulting target entry manually in case its file type is a symlink. However, this would be a little weird and unobvious in that we resolve files but not directories for some reason. Also, symlinking directories is pretty useful if you want to symlink directories of local dependencies that are not under the project's path. ## Test Plan This has been tested manually: ```rust fn main() { for follow_links in [false, true] { let walker = globwalk::GlobWalkerBuilder::from_patterns(".", &["a/*"]) .file_type(globwalk::FileType::FILE | globwalk::FileType::SYMLINK) .follow_links(follow_links) .build() .unwrap(); let entry = walker.into_iter().next().unwrap().unwrap(); dbg!(&entry); dbg!(entry.file_type()); dbg!(entry.path_is_symlink()); dbg!(entry.path()); let meta = entry.metadata().unwrap(); dbg!(meta.is_file()); } let path = std::path::PathBuf::from("./a/b"); dbg!(path.metadata().unwrap().file_type()); dbg!(path.symlink_metadata().unwrap().file_type()); } ``` Current behaviour (glob cache-key, don't follow links): ``` [src/main.rs:9:9] &entry = DirEntry("./a/b") [src/main.rs:10:9] entry.file_type() = FileType { is_file: false, is_dir: false, is_symlink: true, .. } [src/main.rs:11:9] entry.path_is_symlink() = true [src/main.rs:12:9] entry.path() = "./a/b" [src/main.rs:14:9] meta.is_file() = false ``` Glob cache-key, follow links: ``` [src/main.rs:9:9] &entry = DirEntry("./a/b") [src/main.rs:10:9] entry.file_type() = FileType { is_file: true, is_dir: false, is_symlink: false, .. } [src/main.rs:11:9] entry.path_is_symlink() = true [src/main.rs:12:9] entry.path() = "./a/b" [src/main.rs:14:9] meta.is_file() = true ``` Using `path.metadata()` for a non-glob cache key: ``` [src/main.rs:18:5] path.metadata().unwrap().file_type() = FileType { is_file: true, is_dir: false, is_symlink: false, .. } [src/main.rs:19:5] path.symlink_metadata().unwrap().file_type() = FileType { is_file: false, is_dir: false, is_symlink: true, .. } ``` --- Cargo.lock | 2 + crates/uv-cache-info/Cargo.toml | 4 + crates/uv-cache-info/src/cache_info.rs | 100 ++++++++++++++++++++++--- 3 files changed, 97 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f2bebefc9..0069cbb65 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4907,10 +4907,12 @@ dependencies = [ name = "uv-cache-info" version = "0.0.1" dependencies = [ + "anyhow", "fs-err 3.1.1", "globwalk", "schemars", "serde", + "tempfile", "thiserror 2.0.12", "toml", "tracing", diff --git a/crates/uv-cache-info/Cargo.toml b/crates/uv-cache-info/Cargo.toml index 6b10bbebe..83df384be 100644 --- a/crates/uv-cache-info/Cargo.toml +++ b/crates/uv-cache-info/Cargo.toml @@ -24,3 +24,7 @@ thiserror = { workspace = true } toml = { workspace = true } tracing = { workspace = true } walkdir = { workspace = true } + +[dev-dependencies] +anyhow = { workspace = true } +tempfile = { workspace = true } diff --git a/crates/uv-cache-info/src/cache_info.rs b/crates/uv-cache-info/src/cache_info.rs index 27a98ab54..d2f836c84 100644 --- a/crates/uv-cache-info/src/cache_info.rs +++ b/crates/uv-cache-info/src/cache_info.rs @@ -230,18 +230,32 @@ impl CacheInfo { continue; } }; - let metadata = match entry.metadata() { - Ok(metadata) => metadata, - Err(err) => { - warn!("Failed to read metadata for glob entry: {err}"); - continue; + let metadata = if entry.path_is_symlink() { + // resolve symlinks for leaf entries without following symlinks while globbing + match fs_err::metadata(entry.path()) { + Ok(metadata) => metadata, + Err(err) => { + warn!("Failed to resolve symlink for glob entry: {err}"); + continue; + } + } + } else { + match entry.metadata() { + Ok(metadata) => metadata, + Err(err) => { + warn!("Failed to read metadata for glob entry: {err}"); + continue; + } } }; if !metadata.is_file() { - warn!( - "Expected file for cache key, but found directory: `{}`", - entry.path().display() - ); + if !entry.path_is_symlink() { + // don't warn if it was a symlink - it may legitimately resolve to a directory + warn!( + "Expected file for cache key, but found directory: `{}`", + entry.path().display() + ); + } continue; } timestamp = max(timestamp, Some(Timestamp::from_metadata(&metadata))); @@ -346,3 +360,71 @@ enum DirectoryTimestamp { Timestamp(Timestamp), Inode(u64), } + +#[cfg(all(test, unix))] +mod tests_unix { + use anyhow::Result; + + use super::{CacheInfo, Timestamp}; + + #[test] + fn test_cache_info_symlink_resolve() -> Result<()> { + let dir = tempfile::tempdir()?; + let dir = dir.path().join("dir"); + fs_err::create_dir_all(&dir)?; + + let write_manifest = |cache_key: &str| { + fs_err::write( + dir.join("pyproject.toml"), + format!( + r#" + [tool.uv] + cache-keys = [ + "{cache_key}" + ] + "# + ), + ) + }; + + let touch = |path: &str| -> Result<_> { + let path = dir.join(path); + fs_err::create_dir_all(path.parent().unwrap())?; + fs_err::write(&path, "")?; + Ok(Timestamp::from_metadata(&path.metadata()?)) + }; + + let cache_timestamp = || -> Result<_> { Ok(CacheInfo::from_directory(&dir)?.timestamp) }; + + write_manifest("x/**")?; + assert_eq!(cache_timestamp()?, None); + let y = touch("x/y")?; + assert_eq!(cache_timestamp()?, Some(y)); + let z = touch("x/z")?; + assert_eq!(cache_timestamp()?, Some(z)); + + // leaf entry symlink should be resolved + let a = touch("../a")?; + fs_err::os::unix::fs::symlink(dir.join("../a"), dir.join("x/a"))?; + assert_eq!(cache_timestamp()?, Some(a)); + + // symlink directories should not be followed while globbing + let c = touch("../b/c")?; + fs_err::os::unix::fs::symlink(dir.join("../b"), dir.join("x/b"))?; + assert_eq!(cache_timestamp()?, Some(a)); + + // no globs, should work as expected + write_manifest("x/y")?; + assert_eq!(cache_timestamp()?, Some(y)); + write_manifest("x/a")?; + assert_eq!(cache_timestamp()?, Some(a)); + write_manifest("x/b/c")?; + assert_eq!(cache_timestamp()?, Some(c)); + + // symlink pointing to a directory + write_manifest("x/*b*")?; + assert_eq!(cache_timestamp()?, None); + + Ok(()) + } +} From 77c771c7f33df1040c854aeea462c18b74d39987 Mon Sep 17 00:00:00 2001 From: Geoffrey Thomas Date: Mon, 14 Jul 2025 14:01:28 -0400 Subject: [PATCH 209/349] Bump version to 0.7.21 (#14611) --- CHANGELOG.md | 35 +++++++++++++++++++++++++++ Cargo.lock | 6 ++--- crates/uv-build/Cargo.toml | 2 +- crates/uv-build/pyproject.toml | 2 +- crates/uv-version/Cargo.toml | 2 +- crates/uv/Cargo.toml | 2 +- docs/concepts/build-backend.md | 2 +- 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/pre-commit.md | 10 ++++---- pyproject.toml | 2 +- 13 files changed, 59 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fa93fb03..0c7d62c75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,41 @@ +## 0.7.21 + +### Python + +- Restore the SQLite `fts4`, `fts5`, `rtree`, and `geopoly` extensions on macOS and Linux + +See the +[`python-build-standalone` release notes](https://github.com/astral-sh/python-build-standalone/releases/tag/20250712) +for more details. + +### Enhancements + +- Add `--python-platform` to `uv sync` ([#14320](https://github.com/astral-sh/uv/pull/14320)) +- Support pre-releases in `uv version --bump` ([#13578](https://github.com/astral-sh/uv/pull/13578)) +- Add `-w` shorthand for `--with` ([#14530](https://github.com/astral-sh/uv/pull/14530)) +- Add an exception handler on Windows to display information on crash ([#14582](https://github.com/astral-sh/uv/pull/14582)) +- Add hint when Python downloads are disabled ([#14522](https://github.com/astral-sh/uv/pull/14522)) +- Add `UV_HTTP_RETRIES` to customize retry counts ([#14544](https://github.com/astral-sh/uv/pull/14544)) + +### Preview features + +- Add `uv sync --output-format json` ([#13689](https://github.com/astral-sh/uv/pull/13689)) + +### Bug fixes + +- Do not re-resolve with a new Python version in `uv tool` if it is incompatible with `--python` ([#14606](https://github.com/astral-sh/uv/pull/14606)) +- Fix handling of globs in `cache-key`: follow symlinks ([#13438](https://github.com/astral-sh/uv/pull/13438)) and `..` and improve performance ([#13469](https://github.com/astral-sh/uv/pull/13469)) + +### Documentation + +- Document how to nest dependency groups with `include-group` ([#14539](https://github.com/astral-sh/uv/pull/14539)) +- Fix repeated word in Pyodide doc ([#14554](https://github.com/astral-sh/uv/pull/14554)) +- Update CONTRIBUTING.md with instructions to format markdown files ([#14246](https://github.com/astral-sh/uv/pull/14246)) +- Fix version number for `setup-python` ([#14533](https://github.com/astral-sh/uv/pull/14533)) + ## 0.7.20 ### Python diff --git a/Cargo.lock b/Cargo.lock index 0069cbb65..c43f4872d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4633,7 +4633,7 @@ dependencies = [ [[package]] name = "uv" -version = "0.7.20" +version = "0.7.21" dependencies = [ "anstream", "anyhow", @@ -4798,7 +4798,7 @@ dependencies = [ [[package]] name = "uv-build" -version = "0.7.20" +version = "0.7.21" dependencies = [ "anyhow", "uv-build-backend", @@ -5991,7 +5991,7 @@ dependencies = [ [[package]] name = "uv-version" -version = "0.7.20" +version = "0.7.21" [[package]] name = "uv-virtualenv" diff --git a/crates/uv-build/Cargo.toml b/crates/uv-build/Cargo.toml index ffbea0ea9..f943010ae 100644 --- a/crates/uv-build/Cargo.toml +++ b/crates/uv-build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-build" -version = "0.7.20" +version = "0.7.21" edition.workspace = true rust-version.workspace = true homepage.workspace = true diff --git a/crates/uv-build/pyproject.toml b/crates/uv-build/pyproject.toml index 13c21edd8..5a2209155 100644 --- a/crates/uv-build/pyproject.toml +++ b/crates/uv-build/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uv-build" -version = "0.7.20" +version = "0.7.21" description = "The uv build backend" authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }] requires-python = ">=3.8" diff --git a/crates/uv-version/Cargo.toml b/crates/uv-version/Cargo.toml index f1b47dd1d..a9fe788a5 100644 --- a/crates/uv-version/Cargo.toml +++ b/crates/uv-version/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-version" -version = "0.7.20" +version = "0.7.21" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml index 904cc8fc3..fe2f2200c 100644 --- a/crates/uv/Cargo.toml +++ b/crates/uv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv" -version = "0.7.20" +version = "0.7.21" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } diff --git a/docs/concepts/build-backend.md b/docs/concepts/build-backend.md index a34bc7658..69694f317 100644 --- a/docs/concepts/build-backend.md +++ b/docs/concepts/build-backend.md @@ -36,7 +36,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.7.20,<0.8.0"] +requires = ["uv_build>=0.7.21,<0.8.0"] build-backend = "uv_build" ``` diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 12895b56e..fa68d210a 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.7.20/install.sh | sh + $ curl -LsSf https://astral.sh/uv/0.7.21/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.7.20/install.ps1 | iex" + PS> powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/0.7.21/install.ps1 | iex" ``` !!! tip diff --git a/docs/guides/integration/aws-lambda.md b/docs/guides/integration/aws-lambda.md index 1e6c7c47a..4cdb75b7a 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.7.20 AS uv +FROM ghcr.io/astral-sh/uv:0.7.21 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.7.20 AS uv +FROM ghcr.io/astral-sh/uv:0.7.21 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 0445b155c..bbea9b264 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.7.20` +- `ghcr.io/astral-sh/uv:{major}.{minor}.{patch}`, e.g., `ghcr.io/astral-sh/uv:0.7.21` - `ghcr.io/astral-sh/uv:{major}.{minor}`, e.g., `ghcr.io/astral-sh/uv:0.7` (the latest patch version) @@ -75,7 +75,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.7.20-alpine`. +`ghcr.io/astral-sh/uv:{major}.{minor}-{base}`, e.g., `ghcr.io/astral-sh/uv:0.7.21-alpine`. For more details, see the [GitHub Container](https://github.com/astral-sh/uv/pkgs/container/uv) page. @@ -113,7 +113,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.7.20 /uv /uvx /bin/ +COPY --from=ghcr.io/astral-sh/uv:0.7.21 /uv /uvx /bin/ ``` !!! tip @@ -131,7 +131,7 @@ COPY --from=ghcr.io/astral-sh/uv:0.7.20 /uv /uvx /bin/ Or, with the installer: ```dockerfile -ADD https://astral.sh/uv/0.7.20/install.sh /uv-installer.sh +ADD https://astral.sh/uv/0.7.21/install.sh /uv-installer.sh ``` ### Installing a project @@ -557,5 +557,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.7.20`, or (even better) the specific image digest, + version tag, e.g., `ghcr.io/astral-sh/uv:0.7.21`, 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 e7fea7b29..d206febd1 100644 --- a/docs/guides/integration/github.md +++ b/docs/guides/integration/github.md @@ -47,7 +47,7 @@ jobs: uses: astral-sh/setup-uv@v6 with: # Install a specific version of uv. - version: "0.7.20" + version: "0.7.21" ``` ## Setting up Python diff --git a/docs/guides/integration/pre-commit.md b/docs/guides/integration/pre-commit.md index 0495581c2..912ff0213 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.7.20 + rev: 0.7.21 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.7.20 + rev: 0.7.21 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.7.20 + rev: 0.7.21 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.7.20 + rev: 0.7.21 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.7.20 + rev: 0.7.21 hooks: # Compile requirements - id: pip-compile diff --git a/pyproject.toml b/pyproject.toml index df118d720..f3c9c4f64 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "uv" -version = "0.7.20" +version = "0.7.21" 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" From b046e7f3be3b495f695a474ce0088f9b2280eb5c Mon Sep 17 00:00:00 2001 From: InSync Date: Tue, 15 Jul 2025 02:06:05 +0700 Subject: [PATCH 210/349] Add missing comma in `projects/dependencies.md` (#14613) ## Summary Diff: ```diff [dependency-groups] dev = [ - {include-group = "lint"} + {include-group = "lint"}, {include-group = "test"} ] ``` ## Test Plan None. --- docs/concepts/projects/dependencies.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/concepts/projects/dependencies.md b/docs/concepts/projects/dependencies.md index e5c64a3ee..2eabbf4dc 100644 --- a/docs/concepts/projects/dependencies.md +++ b/docs/concepts/projects/dependencies.md @@ -686,7 +686,7 @@ A dependency group can include other dependency groups, e.g.: ```toml title="pyproject.toml" [dependency-groups] dev = [ - {include-group = "lint"} + {include-group = "lint"}, {include-group = "test"} ] lint = [ From 9871bbdc7931c5ca664a798fec7df2da9b71e1ba Mon Sep 17 00:00:00 2001 From: Geoffrey Thomas Date: Mon, 14 Jul 2025 16:29:02 -0400 Subject: [PATCH 211/349] Fix 0.7.21 changelog (#14615) --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c7d62c75..38be00d2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,9 @@ for more details. - Add an exception handler on Windows to display information on crash ([#14582](https://github.com/astral-sh/uv/pull/14582)) - Add hint when Python downloads are disabled ([#14522](https://github.com/astral-sh/uv/pull/14522)) - Add `UV_HTTP_RETRIES` to customize retry counts ([#14544](https://github.com/astral-sh/uv/pull/14544)) +- Follow leaf symlinks matched by globs in `cache-key` ([#13438](https://github.com/astral-sh/uv/pull/13438)) +- Support parent path components (`..`) in globs in `cache-key` ([#13469](https://github.com/astral-sh/uv/pull/13469)) +- Improve `cache-key` performance ([#13469](https://github.com/astral-sh/uv/pull/13469)) ### Preview features @@ -29,13 +32,12 @@ for more details. ### Bug fixes - Do not re-resolve with a new Python version in `uv tool` if it is incompatible with `--python` ([#14606](https://github.com/astral-sh/uv/pull/14606)) -- Fix handling of globs in `cache-key`: follow symlinks ([#13438](https://github.com/astral-sh/uv/pull/13438)) and `..` and improve performance ([#13469](https://github.com/astral-sh/uv/pull/13469)) ### Documentation - Document how to nest dependency groups with `include-group` ([#14539](https://github.com/astral-sh/uv/pull/14539)) - Fix repeated word in Pyodide doc ([#14554](https://github.com/astral-sh/uv/pull/14554)) -- Update CONTRIBUTING.md with instructions to format markdown files ([#14246](https://github.com/astral-sh/uv/pull/14246)) +- Update CONTRIBUTING.md with instructions to format Markdown files via Docker ([#14246](https://github.com/astral-sh/uv/pull/14246)) - Fix version number for `setup-python` ([#14533](https://github.com/astral-sh/uv/pull/14533)) ## 0.7.20 From 405ef66cef6cb67817d039277f05c924a5cff19e Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 15 Jul 2025 10:00:04 -0400 Subject: [PATCH 212/349] Allow users to override index `cache-control` headers (#14620) ## Summary You can now override the cache control headers for the Simple API, file downloads, or both: ```toml [[tool.uv.index]] name = "example" url = "https://example.com/simple" cache-control = { api = "max-age=600", files = "max-age=365000000, immutable" } ``` Closes https://github.com/astral-sh/uv/issues/10444. --- crates/uv-client/src/cached_client.rs | 25 ++++-- crates/uv-client/src/error.rs | 3 + crates/uv-client/src/registry_client.rs | 58 +++++++++---- crates/uv-distribution-types/src/index.rs | 83 +++++++++++++++++++ crates/uv-distribution-types/src/index_url.rs | 80 ++++++++++++++++++ crates/uv/tests/it/show_settings.rs | 29 +++++++ docs/concepts/indexes.md | 37 +++++++++ uv.schema.json | 32 +++++++ 8 files changed, 323 insertions(+), 24 deletions(-) diff --git a/crates/uv-client/src/cached_client.rs b/crates/uv-client/src/cached_client.rs index ee3314d1c..f888ea5f1 100644 --- a/crates/uv-client/src/cached_client.rs +++ b/crates/uv-client/src/cached_client.rs @@ -196,16 +196,18 @@ impl + std::error::Error + 'static> From> for } #[derive(Debug, Clone, Copy)] -pub enum CacheControl { +pub enum CacheControl<'a> { /// Respect the `cache-control` header from the response. None, /// Apply `max-age=0, must-revalidate` to the request. MustRevalidate, /// Allow the client to return stale responses. AllowStale, + /// Override the cache control header with a custom value. + Override(&'a str), } -impl From for CacheControl { +impl From for CacheControl<'_> { fn from(value: Freshness) -> Self { match value { Freshness::Fresh => Self::None, @@ -259,7 +261,7 @@ impl CachedClient { &self, req: Request, cache_entry: &CacheEntry, - cache_control: CacheControl, + cache_control: CacheControl<'_>, response_callback: Callback, ) -> Result> { let payload = self @@ -292,7 +294,7 @@ impl CachedClient { &self, req: Request, cache_entry: &CacheEntry, - cache_control: CacheControl, + cache_control: CacheControl<'_>, response_callback: Callback, ) -> Result> { let fresh_req = req.try_clone().expect("HTTP request must be cloneable"); @@ -469,7 +471,7 @@ impl CachedClient { async fn send_cached( &self, mut req: Request, - cache_control: CacheControl, + cache_control: CacheControl<'_>, cached: DataWithCachePolicy, ) -> Result { // Apply the cache control header, if necessary. @@ -481,6 +483,13 @@ impl CachedClient { http::HeaderValue::from_static("no-cache"), ); } + CacheControl::Override(value) => { + req.headers_mut().insert( + http::header::CACHE_CONTROL, + http::HeaderValue::from_str(value) + .map_err(|_| ErrorKind::InvalidCacheControl(value.to_string()))?, + ); + } } Ok(match cached.cache_policy.before_request(&mut req) { BeforeRequest::Fresh => { @@ -488,7 +497,7 @@ impl CachedClient { CachedResponse::FreshCache(cached) } BeforeRequest::Stale(new_cache_policy_builder) => match cache_control { - CacheControl::None | CacheControl::MustRevalidate => { + CacheControl::None | CacheControl::MustRevalidate | CacheControl::Override(_) => { debug!("Found stale response for: {}", req.url()); self.send_cached_handle_stale(req, cached, new_cache_policy_builder) .await? @@ -599,7 +608,7 @@ impl CachedClient { &self, req: Request, cache_entry: &CacheEntry, - cache_control: CacheControl, + cache_control: CacheControl<'_>, response_callback: Callback, ) -> Result> { let payload = self @@ -623,7 +632,7 @@ impl CachedClient { &self, req: Request, cache_entry: &CacheEntry, - cache_control: CacheControl, + cache_control: CacheControl<'_>, response_callback: Callback, ) -> Result> { let mut past_retries = 0; diff --git a/crates/uv-client/src/error.rs b/crates/uv-client/src/error.rs index 754237fe2..035cdea71 100644 --- a/crates/uv-client/src/error.rs +++ b/crates/uv-client/src/error.rs @@ -259,6 +259,9 @@ pub enum ErrorKind { "Network connectivity is disabled, but the requested data wasn't found in the cache for: `{0}`" )] Offline(String), + + #[error("Invalid cache control header: `{0}`")] + InvalidCacheControl(String), } impl ErrorKind { diff --git a/crates/uv-client/src/registry_client.rs b/crates/uv-client/src/registry_client.rs index afa1b03ae..1d12c5adf 100644 --- a/crates/uv-client/src/registry_client.rs +++ b/crates/uv-client/src/registry_client.rs @@ -511,11 +511,17 @@ impl RegistryClient { format!("{package_name}.rkyv"), ); let cache_control = match self.connectivity { - Connectivity::Online => CacheControl::from( - self.cache - .freshness(&cache_entry, Some(package_name), None) - .map_err(ErrorKind::Io)?, - ), + Connectivity::Online => { + if let Some(header) = self.index_urls.simple_api_cache_control_for(index) { + CacheControl::Override(header) + } else { + CacheControl::from( + self.cache + .freshness(&cache_entry, Some(package_name), None) + .map_err(ErrorKind::Io)?, + ) + } + } Connectivity::Offline => CacheControl::AllowStale, }; @@ -571,7 +577,7 @@ impl RegistryClient { package_name: &PackageName, url: &DisplaySafeUrl, cache_entry: &CacheEntry, - cache_control: CacheControl, + cache_control: CacheControl<'_>, ) -> Result, Error> { let simple_request = self .uncached_client(url) @@ -783,11 +789,17 @@ impl RegistryClient { format!("{}.msgpack", filename.cache_key()), ); let cache_control = match self.connectivity { - Connectivity::Online => CacheControl::from( - self.cache - .freshness(&cache_entry, Some(&filename.name), None) - .map_err(ErrorKind::Io)?, - ), + Connectivity::Online => { + if let Some(header) = self.index_urls.artifact_cache_control_for(index) { + CacheControl::Override(header) + } else { + CacheControl::from( + self.cache + .freshness(&cache_entry, Some(&filename.name), None) + .map_err(ErrorKind::Io)?, + ) + } + } Connectivity::Offline => CacheControl::AllowStale, }; @@ -853,11 +865,25 @@ impl RegistryClient { format!("{}.msgpack", filename.cache_key()), ); let cache_control = match self.connectivity { - Connectivity::Online => CacheControl::from( - self.cache - .freshness(&cache_entry, Some(&filename.name), None) - .map_err(ErrorKind::Io)?, - ), + Connectivity::Online => { + if let Some(index) = index { + if let Some(header) = self.index_urls.artifact_cache_control_for(index) { + CacheControl::Override(header) + } else { + CacheControl::from( + self.cache + .freshness(&cache_entry, Some(&filename.name), None) + .map_err(ErrorKind::Io)?, + ) + } + } else { + CacheControl::from( + self.cache + .freshness(&cache_entry, Some(&filename.name), None) + .map_err(ErrorKind::Io)?, + ) + } + } Connectivity::Offline => CacheControl::AllowStale, }; diff --git a/crates/uv-distribution-types/src/index.rs b/crates/uv-distribution-types/src/index.rs index 8ac7c3cd4..04614a18e 100644 --- a/crates/uv-distribution-types/src/index.rs +++ b/crates/uv-distribution-types/src/index.rs @@ -6,11 +6,23 @@ use thiserror::Error; use uv_auth::{AuthPolicy, Credentials}; use uv_redacted::DisplaySafeUrl; +use uv_small_str::SmallString; use crate::index_name::{IndexName, IndexNameError}; use crate::origin::Origin; use crate::{IndexStatusCodeStrategy, IndexUrl, IndexUrlError, SerializableStatusCode}; +/// Cache control configuration for an index. +#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, Default)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[serde(rename_all = "kebab-case")] +pub struct IndexCacheControl { + /// Cache control header for Simple API requests. + pub api: Option, + /// Cache control header for file downloads. + pub files: Option, +} + #[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[serde(rename_all = "kebab-case")] @@ -104,6 +116,19 @@ pub struct Index { /// ``` #[serde(default)] pub ignore_error_codes: Option>, + /// Cache control configuration for this index. + /// + /// When set, these headers will override the server's cache control headers + /// for both package metadata requests and artifact downloads. + /// + /// ```toml + /// [[tool.uv.index]] + /// name = "my-index" + /// url = "https:///simple" + /// cache-control = { api = "max-age=600", files = "max-age=3600" } + /// ``` + #[serde(default)] + pub cache_control: Option, } #[derive( @@ -142,6 +167,7 @@ impl Index { publish_url: None, authenticate: AuthPolicy::default(), ignore_error_codes: None, + cache_control: None, } } @@ -157,6 +183,7 @@ impl Index { publish_url: None, authenticate: AuthPolicy::default(), ignore_error_codes: None, + cache_control: None, } } @@ -172,6 +199,7 @@ impl Index { publish_url: None, authenticate: AuthPolicy::default(), ignore_error_codes: None, + cache_control: None, } } @@ -250,6 +278,7 @@ impl From for Index { publish_url: None, authenticate: AuthPolicy::default(), ignore_error_codes: None, + cache_control: None, } } } @@ -273,6 +302,7 @@ impl FromStr for Index { publish_url: None, authenticate: AuthPolicy::default(), ignore_error_codes: None, + cache_control: None, }); } } @@ -289,6 +319,7 @@ impl FromStr for Index { publish_url: None, authenticate: AuthPolicy::default(), ignore_error_codes: None, + cache_control: None, }) } } @@ -384,3 +415,55 @@ pub enum IndexSourceError { #[error("Index included a name, but the name was empty")] EmptyName, } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_index_cache_control_headers() { + // Test that cache control headers are properly parsed from TOML + let toml_str = r#" + name = "test-index" + url = "https://test.example.com/simple" + cache-control = { api = "max-age=600", files = "max-age=3600" } + "#; + + let index: Index = toml::from_str(toml_str).unwrap(); + assert_eq!(index.name.as_ref().unwrap().as_ref(), "test-index"); + assert!(index.cache_control.is_some()); + let cache_control = index.cache_control.as_ref().unwrap(); + assert_eq!(cache_control.api.as_deref(), Some("max-age=600")); + assert_eq!(cache_control.files.as_deref(), Some("max-age=3600")); + } + + #[test] + fn test_index_without_cache_control() { + // Test that indexes work without cache control headers + let toml_str = r#" + name = "test-index" + url = "https://test.example.com/simple" + "#; + + let index: Index = toml::from_str(toml_str).unwrap(); + assert_eq!(index.name.as_ref().unwrap().as_ref(), "test-index"); + assert_eq!(index.cache_control, None); + } + + #[test] + fn test_index_partial_cache_control() { + // Test that cache control can have just one field + let toml_str = r#" + name = "test-index" + url = "https://test.example.com/simple" + cache-control = { api = "max-age=300" } + "#; + + let index: Index = toml::from_str(toml_str).unwrap(); + assert_eq!(index.name.as_ref().unwrap().as_ref(), "test-index"); + assert!(index.cache_control.is_some()); + let cache_control = index.cache_control.as_ref().unwrap(); + assert_eq!(cache_control.api.as_deref(), Some("max-age=300")); + assert_eq!(cache_control.files, None); + } +} diff --git a/crates/uv-distribution-types/src/index_url.rs b/crates/uv-distribution-types/src/index_url.rs index 1c8cd0a76..bd3e9abc2 100644 --- a/crates/uv-distribution-types/src/index_url.rs +++ b/crates/uv-distribution-types/src/index_url.rs @@ -599,6 +599,26 @@ impl<'a> IndexUrls { } IndexStatusCodeStrategy::Default } + + /// Return the Simple API cache control header for an [`IndexUrl`], if configured. + pub fn simple_api_cache_control_for(&self, url: &IndexUrl) -> Option<&str> { + for index in &self.indexes { + if index.url() == url { + return index.cache_control.as_ref()?.api.as_deref(); + } + } + None + } + + /// Return the artifact cache control header for an [`IndexUrl`], if configured. + pub fn artifact_cache_control_for(&self, url: &IndexUrl) -> Option<&str> { + for index in &self.indexes { + if index.url() == url { + return index.cache_control.as_ref()?.files.as_deref(); + } + } + None + } } bitflags::bitflags! { @@ -717,4 +737,64 @@ mod tests { "git+https://github.com/example/repo.git" )); } + + #[test] + fn test_cache_control_lookup() { + use std::str::FromStr; + + use uv_small_str::SmallString; + + use crate::IndexFormat; + use crate::index_name::IndexName; + + let indexes = vec![ + Index { + name: Some(IndexName::from_str("index1").unwrap()), + url: IndexUrl::from_str("https://index1.example.com/simple").unwrap(), + cache_control: Some(crate::IndexCacheControl { + api: Some(SmallString::from("max-age=300")), + files: Some(SmallString::from("max-age=1800")), + }), + explicit: false, + default: false, + origin: None, + format: IndexFormat::Simple, + publish_url: None, + authenticate: uv_auth::AuthPolicy::default(), + ignore_error_codes: None, + }, + Index { + name: Some(IndexName::from_str("index2").unwrap()), + url: IndexUrl::from_str("https://index2.example.com/simple").unwrap(), + cache_control: None, + explicit: false, + default: false, + origin: None, + format: IndexFormat::Simple, + publish_url: None, + authenticate: uv_auth::AuthPolicy::default(), + ignore_error_codes: None, + }, + ]; + + let index_urls = IndexUrls::from_indexes(indexes); + + let url1 = IndexUrl::from_str("https://index1.example.com/simple").unwrap(); + assert_eq!( + index_urls.simple_api_cache_control_for(&url1), + Some("max-age=300") + ); + assert_eq!( + index_urls.artifact_cache_control_for(&url1), + Some("max-age=1800") + ); + + let url2 = IndexUrl::from_str("https://index2.example.com/simple").unwrap(); + assert_eq!(index_urls.simple_api_cache_control_for(&url2), None); + assert_eq!(index_urls.artifact_cache_control_for(&url2), None); + + let url3 = IndexUrl::from_str("https://index3.example.com/simple").unwrap(); + assert_eq!(index_urls.simple_api_cache_control_for(&url3), None); + assert_eq!(index_urls.artifact_cache_control_for(&url3), None); + } } diff --git a/crates/uv/tests/it/show_settings.rs b/crates/uv/tests/it/show_settings.rs index 7635bd523..2637af8ac 100644 --- a/crates/uv/tests/it/show_settings.rs +++ b/crates/uv/tests/it/show_settings.rs @@ -139,6 +139,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> { publish_url: None, authenticate: Auto, ignore_error_codes: None, + cache_control: None, }, ], flat_index: [], @@ -320,6 +321,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> { publish_url: None, authenticate: Auto, ignore_error_codes: None, + cache_control: None, }, ], flat_index: [], @@ -502,6 +504,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> { publish_url: None, authenticate: Auto, ignore_error_codes: None, + cache_control: None, }, ], flat_index: [], @@ -716,6 +719,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { publish_url: None, authenticate: Auto, ignore_error_codes: None, + cache_control: None, }, ], flat_index: [], @@ -1059,6 +1063,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { publish_url: None, authenticate: Auto, ignore_error_codes: None, + cache_control: None, }, ], flat_index: [], @@ -1267,6 +1272,7 @@ fn resolve_index_url() -> anyhow::Result<()> { publish_url: None, authenticate: Auto, ignore_error_codes: None, + cache_control: None, }, Index { name: None, @@ -1299,6 +1305,7 @@ fn resolve_index_url() -> anyhow::Result<()> { publish_url: None, authenticate: Auto, ignore_error_codes: None, + cache_control: None, }, ], flat_index: [], @@ -1484,6 +1491,7 @@ fn resolve_index_url() -> anyhow::Result<()> { publish_url: None, authenticate: Auto, ignore_error_codes: None, + cache_control: None, }, Index { name: None, @@ -1516,6 +1524,7 @@ fn resolve_index_url() -> anyhow::Result<()> { publish_url: None, authenticate: Auto, ignore_error_codes: None, + cache_control: None, }, Index { name: None, @@ -1548,6 +1557,7 @@ fn resolve_index_url() -> anyhow::Result<()> { publish_url: None, authenticate: Auto, ignore_error_codes: None, + cache_control: None, }, ], flat_index: [], @@ -1755,6 +1765,7 @@ fn resolve_find_links() -> anyhow::Result<()> { publish_url: None, authenticate: Auto, ignore_error_codes: None, + cache_control: None, }, ], no_index: true, @@ -2124,6 +2135,7 @@ fn resolve_top_level() -> anyhow::Result<()> { publish_url: None, authenticate: Auto, ignore_error_codes: None, + cache_control: None, }, Index { name: None, @@ -2156,6 +2168,7 @@ fn resolve_top_level() -> anyhow::Result<()> { publish_url: None, authenticate: Auto, ignore_error_codes: None, + cache_control: None, }, ], flat_index: [], @@ -2337,6 +2350,7 @@ fn resolve_top_level() -> anyhow::Result<()> { publish_url: None, authenticate: Auto, ignore_error_codes: None, + cache_control: None, }, Index { name: None, @@ -2369,6 +2383,7 @@ fn resolve_top_level() -> anyhow::Result<()> { publish_url: None, authenticate: Auto, ignore_error_codes: None, + cache_control: None, }, ], flat_index: [], @@ -3564,6 +3579,7 @@ fn resolve_both() -> anyhow::Result<()> { publish_url: None, authenticate: Auto, ignore_error_codes: None, + cache_control: None, }, ], flat_index: [], @@ -3870,6 +3886,7 @@ fn resolve_config_file() -> anyhow::Result<()> { publish_url: None, authenticate: Auto, ignore_error_codes: None, + cache_control: None, }, ], flat_index: [], @@ -4658,6 +4675,7 @@ fn index_priority() -> anyhow::Result<()> { publish_url: None, authenticate: Auto, ignore_error_codes: None, + cache_control: None, }, Index { name: None, @@ -4690,6 +4708,7 @@ fn index_priority() -> anyhow::Result<()> { publish_url: None, authenticate: Auto, ignore_error_codes: None, + cache_control: None, }, ], flat_index: [], @@ -4873,6 +4892,7 @@ fn index_priority() -> anyhow::Result<()> { publish_url: None, authenticate: Auto, ignore_error_codes: None, + cache_control: None, }, Index { name: None, @@ -4905,6 +4925,7 @@ fn index_priority() -> anyhow::Result<()> { publish_url: None, authenticate: Auto, ignore_error_codes: None, + cache_control: None, }, ], flat_index: [], @@ -5094,6 +5115,7 @@ fn index_priority() -> anyhow::Result<()> { publish_url: None, authenticate: Auto, ignore_error_codes: None, + cache_control: None, }, Index { name: None, @@ -5126,6 +5148,7 @@ fn index_priority() -> anyhow::Result<()> { publish_url: None, authenticate: Auto, ignore_error_codes: None, + cache_control: None, }, ], flat_index: [], @@ -5310,6 +5333,7 @@ fn index_priority() -> anyhow::Result<()> { publish_url: None, authenticate: Auto, ignore_error_codes: None, + cache_control: None, }, Index { name: None, @@ -5342,6 +5366,7 @@ fn index_priority() -> anyhow::Result<()> { publish_url: None, authenticate: Auto, ignore_error_codes: None, + cache_control: None, }, ], flat_index: [], @@ -5533,6 +5558,7 @@ fn index_priority() -> anyhow::Result<()> { publish_url: None, authenticate: Auto, ignore_error_codes: None, + cache_control: None, }, Index { name: None, @@ -5565,6 +5591,7 @@ fn index_priority() -> anyhow::Result<()> { publish_url: None, authenticate: Auto, ignore_error_codes: None, + cache_control: None, }, ], flat_index: [], @@ -5749,6 +5776,7 @@ fn index_priority() -> anyhow::Result<()> { publish_url: None, authenticate: Auto, ignore_error_codes: None, + cache_control: None, }, Index { name: None, @@ -5781,6 +5809,7 @@ fn index_priority() -> anyhow::Result<()> { publish_url: None, authenticate: Auto, ignore_error_codes: None, + cache_control: None, }, ], flat_index: [], diff --git a/docs/concepts/indexes.md b/docs/concepts/indexes.md index 6c03bae66..5e6c3866c 100644 --- a/docs/concepts/indexes.md +++ b/docs/concepts/indexes.md @@ -244,6 +244,43 @@ authenticate = "never" When `authenticate` is set to `never`, uv will never search for credentials for the given index and will error if credentials are provided directly. +### Customizing cache control headers + +By default, uv will respect the cache control headers provided by the index. For example, PyPI +serves package metadata with a `max-age=600` header, thereby allowing uv to cache package metadata +for 10 minutes; and wheels and source distributions with a `max-age=365000000, immutable` header, +thereby allowing uv to cache artifacts indefinitely. + +To override the cache control headers for an index, use the `cache-control` setting: + +```toml +[[tool.uv.index]] +name = "example" +url = "https://example.com/simple" +cache-control = { api = "max-age=600", files = "max-age=365000000, immutable" } +``` + +The `cache-control` setting accepts an object with two optional keys: + +- `api`: Controls caching for Simple API requests (package metadata). +- `files`: Controls caching for artifact downloads (wheels and source distributions). + +The values for these keys are strings that follow the +[HTTP Cache-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control) +syntax. For example, to force uv to always revalidate package metadata, set `api = "no-cache"`: + +```toml +[[tool.uv.index]] +name = "example" +url = "https://example.com/simple" +cache-control = { api = "no-cache" } +``` + +This setting is most commonly used to override the default cache control headers for private indexes +that otherwise disable caching, often unintentionally. We typically recommend following PyPI's +approach to caching headers, i.e., setting `api = "max-age=600"` and +`files = "max-age=365000000, immutable"`. + ## "Flat" indexes By default, `[[tool.uv.index]]` entries are assumed to be PyPI-style registries that implement the diff --git a/uv.schema.json b/uv.schema.json index 4190672e9..e418f37f0 100644 --- a/uv.schema.json +++ b/uv.schema.json @@ -907,6 +907,18 @@ ], "default": "auto" }, + "cache-control": { + "description": "Cache control configuration for this index.\n\nWhen set, these headers will override the server's cache control headers\nfor both package metadata requests and artifact downloads.\n\n```toml\n[[tool.uv.index]]\nname = \"my-index\"\nurl = \"https:///simple\"\ncache-control = { api = \"max-age=600\", files = \"max-age=3600\" }\n```", + "anyOf": [ + { + "$ref": "#/definitions/IndexCacheControl" + }, + { + "type": "null" + } + ], + "default": null + }, "default": { "description": "Mark the index as the default index.\n\nBy default, uv uses PyPI as the default index, such that even if additional indexes are\ndefined via `[[tool.uv.index]]`, PyPI will still be used as a fallback for packages that\naren't found elsewhere. To disable the PyPI default, set `default = true` on at least one\nother index.\n\nMarking an index as default will move it to the front of the list of indexes, such that it\nis given the highest priority when resolving packages.", "type": "boolean", @@ -972,6 +984,26 @@ "url" ] }, + "IndexCacheControl": { + "description": "Cache control configuration for an index.", + "type": "object", + "properties": { + "api": { + "description": "Cache control header for Simple API requests.", + "type": [ + "string", + "null" + ] + }, + "files": { + "description": "Cache control header for file downloads.", + "type": [ + "string", + "null" + ] + } + } + }, "IndexFormat": { "oneOf": [ { From cd0d5d4748af11007e05718005437c0780d09048 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Preng=C3=A8re?= <2138730+alexprengere@users.noreply.github.com> Date: Tue, 15 Jul 2025 19:03:01 +0200 Subject: [PATCH 213/349] Fix --all-arches when paired with --only-downloads (#14629) ## Summary On current main, and on the latest released version 0.7.21, I have: ``` $ uv python list --only-downloads --all-arches cpython-3.14.0b4-linux-x86_64-gnu cpython-3.14.0b4+freethreaded-linux-x86_64-gnu cpython-3.13.5-linux-x86_64-gnu cpython-3.13.5+freethreaded-linux-x86_64-gnu cpython-3.12.11-linux-x86_64-gnu cpython-3.11.13-linux-x86_64-gnu cpython-3.10.18-linux-x86_64-gnu cpython-3.9.23-linux-x86_64-gnu cpython-3.8.20-linux-x86_64-gnu pypy-3.11.13-linux-x86_64-gnu pypy-3.10.16-linux-x86_64-gnu pypy-3.9.19-linux-x86_64-gnu pypy-3.8.16-linux-x86_64-gnu graalpy-3.11.0-linux-x86_64-gnu graalpy-3.10.0-linux-x86_64-gnu graalpy-3.8.5-linux-x86_64-gnu ``` As you can see, `--all-arches` is not respected here. ## Test Plan With the patch: ``` $ cargo run python list --only-downloads --all-arches cpython-3.14.0b4-linux-x86_64-gnu cpython-3.14.0b4+freethreaded-linux-x86_64-gnu cpython-3.14.0b4-linux-x86_64_v2-gnu cpython-3.14.0b4+freethreaded-linux-x86_64_v2-gnu cpython-3.14.0b4-linux-x86_64_v3-gnu cpython-3.14.0b4+freethreaded-linux-x86_64_v3-gnu cpython-3.14.0b4-linux-x86_64_v4-gnu cpython-3.14.0b4+freethreaded-linux-x86_64_v4-gnu cpython-3.14.0b4-linux-aarch64-gnu cpython-3.14.0b4+freethreaded-linux-aarch64-gnu cpython-3.14.0b4-linux-powerpc64le-gnu cpython-3.14.0b4+freethreaded-linux-powerpc64le-gnu cpython-3.14.0b4-linux-riscv64gc-gnu cpython-3.14.0b4+freethreaded-linux-riscv64gc-gnu cpython-3.14.0b4-linux-s390x-gnu cpython-3.14.0b4+freethreaded-linux-s390x-gnu cpython-3.13.5-linux-x86_64-gnu cpython-3.13.5+freethreaded-linux-x86_64-gnu cpython-3.13.5-linux-x86_64_v2-gnu cpython-3.13.5+freethreaded-linux-x86_64_v2-gnu cpython-3.13.5-linux-x86_64_v3-gnu cpython-3.13.5+freethreaded-linux-x86_64_v3-gnu cpython-3.13.5-linux-x86_64_v4-gnu cpython-3.13.5+freethreaded-linux-x86_64_v4-gnu cpython-3.13.5-linux-aarch64-gnu cpython-3.13.5+freethreaded-linux-aarch64-gnu cpython-3.13.5-linux-powerpc64le-gnu cpython-3.13.5+freethreaded-linux-powerpc64le-gnu cpython-3.13.5-linux-riscv64gc-gnu cpython-3.13.5+freethreaded-linux-riscv64gc-gnu cpython-3.13.5-linux-s390x-gnu cpython-3.13.5+freethreaded-linux-s390x-gnu cpython-3.12.11-linux-x86_64-gnu cpython-3.12.11-linux-x86_64_v2-gnu cpython-3.12.11-linux-x86_64_v3-gnu cpython-3.12.11-linux-x86_64_v4-gnu cpython-3.12.11-linux-aarch64-gnu cpython-3.12.11-linux-powerpc64le-gnu cpython-3.12.11-linux-riscv64gc-gnu cpython-3.12.11-linux-s390x-gnu cpython-3.11.13-linux-x86_64-gnu cpython-3.11.13-linux-x86_64_v2-gnu cpython-3.11.13-linux-x86_64_v3-gnu cpython-3.11.13-linux-x86_64_v4-gnu cpython-3.11.13-linux-aarch64-gnu cpython-3.11.13-linux-powerpc64le-gnu cpython-3.11.13-linux-riscv64gc-gnu cpython-3.11.13-linux-s390x-gnu cpython-3.11.5-linux-x86-gnu cpython-3.10.18-linux-x86_64-gnu cpython-3.10.18-linux-x86_64_v2-gnu cpython-3.10.18-linux-x86_64_v3-gnu cpython-3.10.18-linux-x86_64_v4-gnu cpython-3.10.18-linux-aarch64-gnu cpython-3.10.18-linux-powerpc64le-gnu cpython-3.10.18-linux-riscv64gc-gnu cpython-3.10.18-linux-s390x-gnu cpython-3.10.13-linux-x86-gnu cpython-3.9.23-linux-x86_64-gnu cpython-3.9.23-linux-x86_64_v2-gnu cpython-3.9.23-linux-x86_64_v3-gnu cpython-3.9.23-linux-x86_64_v4-gnu cpython-3.9.23-linux-aarch64-gnu cpython-3.9.23-linux-powerpc64le-gnu cpython-3.9.23-linux-riscv64gc-gnu cpython-3.9.23-linux-s390x-gnu cpython-3.9.18-linux-x86-gnu cpython-3.8.20-linux-x86_64-gnu cpython-3.8.20-linux-aarch64-gnu cpython-3.8.17-linux-x86-gnu pypy-3.11.13-linux-x86_64-gnu pypy-3.11.13-linux-aarch64-gnu pypy-3.11.13-linux-x86-gnu pypy-3.10.16-linux-x86_64-gnu pypy-3.10.16-linux-aarch64-gnu pypy-3.10.16-linux-x86-gnu pypy-3.10.14-linux-s390x-gnu pypy-3.9.19-linux-x86_64-gnu pypy-3.9.19-linux-aarch64-gnu pypy-3.9.19-linux-x86-gnu pypy-3.9.19-linux-s390x-gnu pypy-3.8.16-linux-x86_64-gnu pypy-3.8.16-linux-aarch64-gnu pypy-3.8.16-linux-x86-gnu pypy-3.8.16-linux-s390x-gnu graalpy-3.11.0-linux-x86_64-gnu graalpy-3.11.0-linux-aarch64-gnu graalpy-3.10.0-linux-x86_64-gnu graalpy-3.10.0-linux-aarch64-gnu graalpy-3.8.5-linux-x86_64-gnu graalpy-3.8.5-linux-aarch64-gnu ``` --- crates/uv/src/commands/python/list.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/uv/src/commands/python/list.rs b/crates/uv/src/commands/python/list.rs index 2cd54747c..17528a11e 100644 --- a/crates/uv/src/commands/python/list.rs +++ b/crates/uv/src/commands/python/list.rs @@ -81,6 +81,8 @@ pub(crate) async fn list( PythonListKinds::Installed => None, PythonListKinds::Downloads => Some(if all_platforms { base_download_request + } else if all_arches { + base_download_request.fill_platform()?.with_any_arch() } else { base_download_request.fill_platform()? }), From bb1e9a247c5e488a712e8f1cc040f025f9751337 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 15 Jul 2025 12:12:36 -0500 Subject: [PATCH 214/349] Update preview installation of Python executables to be non-fatal (#14612) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, if installation of executables into the bin directory failed we'd with a non-zero code. However, if we make this behavior the default we don't want it to be fatal. There's a `--bin` opt-in to _require_ successful executable installation and a `--no-bin` opt-out to silence the warning / opt-out of installation entirely. Part of https://github.com/astral-sh/uv/issues/14296 — we need this before we can stabilize the behavior. In #14614 we do the same for writing entries to the Windows registry. --- crates/uv-cli/src/lib.rs | 15 ++- crates/uv-python/src/windows_registry.rs | 7 +- crates/uv/src/commands/python/install.rs | 145 +++++++++++++++++------ crates/uv/src/lib.rs | 2 + crates/uv/src/settings.rs | 7 ++ crates/uv/tests/it/help.rs | 5 + crates/uv/tests/it/python_install.rs | 68 ++++++++++- docs/reference/cli.md | 3 +- 8 files changed, 212 insertions(+), 40 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 0f3652341..70d5322d9 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -4941,6 +4941,19 @@ pub struct PythonInstallArgs { #[arg(long, short, env = EnvVars::UV_PYTHON_INSTALL_DIR)] pub install_dir: Option, + /// Install a Python executable into the `bin` directory. + /// + /// This is the default behavior. If this flag is provided explicitly, uv will error if the + /// executable cannot be installed. + /// + /// See `UV_PYTHON_BIN_DIR` to customize the target directory. + #[arg(long, overrides_with("no_bin"), hide = true)] + pub bin: bool, + + /// Do not install a Python executable into the `bin` directory. + #[arg(long, overrides_with("bin"), conflicts_with("default"))] + pub no_bin: bool, + /// The Python version(s) to install. /// /// If not provided, the requested Python version(s) will be read from the `UV_PYTHON` @@ -5003,7 +5016,7 @@ pub struct PythonInstallArgs { /// and `python`. /// /// If multiple Python versions are requested, uv will exit with an error. - #[arg(long)] + #[arg(long, conflicts_with("no_bin"))] pub default: bool, } diff --git a/crates/uv-python/src/windows_registry.rs b/crates/uv-python/src/windows_registry.rs index 69e179bbf..7c6f6f307 100644 --- a/crates/uv-python/src/windows_registry.rs +++ b/crates/uv-python/src/windows_registry.rs @@ -129,12 +129,13 @@ fn read_registry_entry(company: &str, tag: &str, tag_key: &Key) -> Option, ) -> Result<(), ManagedPep514Error> { let pointer_width = match installation.key().arch().family().pointer_width() { Ok(PointerWidth::U32) => 32, @@ -146,9 +147,7 @@ pub fn create_registry_entry( } }; - if let Err(err) = write_registry_entry(installation, pointer_width) { - errors.push((installation.key().clone(), err.into())); - } + write_registry_entry(installation, pointer_width)?; Ok(()) } diff --git a/crates/uv/src/commands/python/install.rs b/crates/uv/src/commands/python/install.rs index 8c8387d07..b22d6010e 100644 --- a/crates/uv/src/commands/python/install.rs +++ b/crates/uv/src/commands/python/install.rs @@ -135,6 +135,14 @@ impl Changelog { } } +#[derive(Debug, Clone, Copy)] +enum InstallErrorKind { + DownloadUnpack, + Bin, + #[cfg(windows)] + Registry, +} + /// Download and install Python versions. #[allow(clippy::fn_params_excessive_bools)] pub(crate) async fn install( @@ -143,6 +151,7 @@ pub(crate) async fn install( targets: Vec, reinstall: bool, upgrade: bool, + bin: Option, force: bool, python_install_mirror: Option, pypy_install_mirror: Option, @@ -432,12 +441,16 @@ pub(crate) async fn install( downloaded.push(installation.clone()); } Err(err) => { - errors.push((download.key().clone(), anyhow::Error::new(err))); + errors.push(( + InstallErrorKind::DownloadUnpack, + download.key().clone(), + anyhow::Error::new(err), + )); } } } - let bin = if preview.is_enabled() { + let bin_dir = if matches!(bin, Some(true)) || preview.is_enabled() { Some(python_executable_dir()?) } else { None @@ -460,7 +473,7 @@ pub(crate) async fn install( continue; } - let bin = bin + let bin_dir = bin_dir .as_ref() .expect("We should have a bin directory with preview enabled") .as_path(); @@ -468,27 +481,38 @@ pub(crate) async fn install( let upgradeable = (default || is_default_install) || requested_minor_versions.contains(&installation.key().version().python_version()); - create_bin_links( - installation, - bin, - reinstall, - force, - default, - upgradeable, - upgrade, - is_default_install, - first_request, - &existing_installations, - &installations, - &mut changelog, - &mut errors, - preview, - )?; + if !matches!(bin, Some(false)) { + create_bin_links( + installation, + bin_dir, + reinstall, + force, + default, + upgradeable, + upgrade, + is_default_install, + first_request, + &existing_installations, + &installations, + &mut changelog, + &mut errors, + preview, + ); + } if preview.is_enabled() { #[cfg(windows)] { - uv_python::windows_registry::create_registry_entry(installation, &mut errors)?; + match uv_python::windows_registry::create_registry_entry(installation) { + Ok(()) => {} + Err(err) => { + errors.push(( + InstallErrorKind::Registry, + installation.key().clone(), + err.into(), + )); + } + } } } } @@ -636,24 +660,47 @@ pub(crate) async fn install( } } - if preview.is_enabled() { - let bin = bin + if preview.is_enabled() && !matches!(bin, Some(false)) { + let bin_dir = bin_dir .as_ref() .expect("We should have a bin directory with preview enabled") .as_path(); - warn_if_not_on_path(bin); + warn_if_not_on_path(bin_dir); } } if !errors.is_empty() { - for (key, err) in errors + // If there are only bin install errors and the user didn't opt-in, we're only going to warn + let fatal = errors + .iter() + .all(|(kind, _, _)| matches!(kind, InstallErrorKind::Bin)) + && bin.is_none(); + + for (kind, key, err) in errors .into_iter() - .sorted_unstable_by(|(key_a, _), (key_b, _)| key_a.cmp(key_b)) + .sorted_unstable_by(|(_, key_a, _), (_, key_b, _)| key_a.cmp(key_b)) { + let (level, verb) = match kind { + InstallErrorKind::DownloadUnpack => ("error".red().bold().to_string(), "install"), + InstallErrorKind::Bin => { + let level = match bin { + None => "warning".yellow().bold().to_string(), + Some(false) => continue, + Some(true) => "error".red().bold().to_string(), + }; + (level, "install executable for") + } + #[cfg(windows)] + InstallErrorKind::Registry => ( + "error".red().bold().to_string(), + "install registry entry for", + ), + }; + writeln!( printer.stderr(), - "{}: Failed to install {}", - "error".red().bold(), + "{level}{} Failed to {verb} {}", + ":".bold(), key.green() )?; for err in err.chain() { @@ -665,6 +712,11 @@ pub(crate) async fn install( )?; } } + + if fatal { + return Ok(ExitStatus::Success); + } + return Ok(ExitStatus::Failure); } @@ -672,6 +724,8 @@ pub(crate) async fn install( } /// Link the binaries of a managed Python installation to the bin directory. +/// +/// This function is fallible, but errors are pushed to `errors` instead of being thrown. #[allow(clippy::fn_params_excessive_bools)] fn create_bin_links( installation: &ManagedPythonInstallation, @@ -686,9 +740,9 @@ fn create_bin_links( existing_installations: &[ManagedPythonInstallation], installations: &[&ManagedPythonInstallation], changelog: &mut Changelog, - errors: &mut Vec<(PythonInstallationKey, Error)>, + errors: &mut Vec<(InstallErrorKind, PythonInstallationKey, Error)>, preview: PreviewMode, -) -> Result<(), Error> { +) { let targets = if (default || is_default_install) && first_request.matches_installation(installation) { vec![ @@ -773,6 +827,7 @@ fn create_bin_links( ); } else { errors.push(( + InstallErrorKind::Bin, installation.key().clone(), anyhow::anyhow!( "Executable already exists at `{}` but is not managed by uv; use `--force` to replace it", @@ -848,7 +903,17 @@ fn create_bin_links( } // Replace the existing link - fs_err::remove_file(&to)?; + if let Err(err) = fs_err::remove_file(&to) { + errors.push(( + InstallErrorKind::Bin, + installation.key().clone(), + anyhow::anyhow!( + "Executable already exists at `{}` but could not be removed: {err}", + to.simplified_display() + ), + )); + continue; + } if let Some(existing) = existing { // Ensure we do not report installation of this executable for an existing @@ -860,7 +925,18 @@ fn create_bin_links( .remove(&target); } - create_link_to_executable(&target, executable)?; + if let Err(err) = create_link_to_executable(&target, executable) { + errors.push(( + InstallErrorKind::Bin, + installation.key().clone(), + anyhow::anyhow!( + "Failed to create link at `{}`: {err}", + target.simplified_display() + ), + )); + continue; + } + debug!( "Updated executable at `{}` to {}", target.simplified_display(), @@ -874,11 +950,14 @@ fn create_bin_links( .insert(target.clone()); } Err(err) => { - errors.push((installation.key().clone(), anyhow::Error::new(err))); + errors.push(( + InstallErrorKind::Bin, + installation.key().clone(), + anyhow::Error::new(err), + )); } } } - Ok(()) } pub(crate) fn format_executables( diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 0b4d0bb82..3a700b965 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -1402,6 +1402,7 @@ async fn run(mut cli: Cli) -> Result { args.targets, args.reinstall, upgrade, + args.bin, args.force, args.python_install_mirror, args.pypy_install_mirror, @@ -1430,6 +1431,7 @@ async fn run(mut cli: Cli) -> Result { args.targets, reinstall, upgrade, + args.bin, args.force, args.python_install_mirror, args.pypy_install_mirror, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 8a325d538..d373250ac 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -933,6 +933,7 @@ pub(crate) struct PythonInstallSettings { pub(crate) targets: Vec, pub(crate) reinstall: bool, pub(crate) force: bool, + pub(crate) bin: Option, pub(crate) python_install_mirror: Option, pub(crate) pypy_install_mirror: Option, pub(crate) python_downloads_json_url: Option, @@ -961,6 +962,8 @@ impl PythonInstallSettings { install_dir, targets, reinstall, + bin, + no_bin, force, mirror: _, pypy_mirror: _, @@ -973,6 +976,7 @@ impl PythonInstallSettings { targets, reinstall, force, + bin: flag(bin, no_bin, "bin"), python_install_mirror: python_mirror, pypy_install_mirror: pypy_mirror, python_downloads_json_url, @@ -992,6 +996,7 @@ pub(crate) struct PythonUpgradeSettings { pub(crate) pypy_install_mirror: Option, pub(crate) python_downloads_json_url: Option, pub(crate) default: bool, + pub(crate) bin: Option, } impl PythonUpgradeSettings { @@ -1013,6 +1018,7 @@ impl PythonUpgradeSettings { args.python_downloads_json_url.or(python_downloads_json_url); let force = false; let default = false; + let bin = None; let PythonUpgradeArgs { install_dir, @@ -1030,6 +1036,7 @@ impl PythonUpgradeSettings { pypy_install_mirror: pypy_mirror, python_downloads_json_url, default, + bin, } } } diff --git a/crates/uv/tests/it/help.rs b/crates/uv/tests/it/help.rs index 8faebd040..a6230108c 100644 --- a/crates/uv/tests/it/help.rs +++ b/crates/uv/tests/it/help.rs @@ -504,6 +504,9 @@ fn help_subsubcommand() { [env: UV_PYTHON_INSTALL_DIR=] + --no-bin + Do not install a Python executable into the `bin` directory + --mirror Set the URL to use as the source for downloading Python installations. @@ -790,6 +793,8 @@ fn help_flag_subsubcommand() { Options: -i, --install-dir The directory to store the Python installation in [env: UV_PYTHON_INSTALL_DIR=] + --no-bin + Do not install a Python executable into the `bin` directory --mirror Set the URL to use as the source for downloading Python installations [env: UV_PYTHON_INSTALL_MIRROR=] diff --git a/crates/uv/tests/it/python_install.rs b/crates/uv/tests/it/python_install.rs index bd723e5d1..0cb952054 100644 --- a/crates/uv/tests/it/python_install.rs +++ b/crates/uv/tests/it/python_install.rs @@ -430,15 +430,35 @@ fn python_install_preview() { bin_python.touch().unwrap(); uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.13"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: Failed to install executable for cpython-3.13.5-[PLATFORM] + Caused by: Executable already exists at `[BIN]/python3.13` but is not managed by uv; use `--force` to replace it + "); + + // With `--bin`, this should error instead of warn + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("--bin").arg("3.13"), @r" success: false exit_code: 1 ----- stdout ----- ----- stderr ----- - error: Failed to install cpython-3.13.5-[PLATFORM] + error: Failed to install executable for cpython-3.13.5-[PLATFORM] Caused by: Executable already exists at `[BIN]/python3.13` but is not managed by uv; use `--force` to replace it "); + // With `--no-bin`, this should be silent + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("--no-bin").arg("3.13"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + "); + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("--force").arg("3.13"), @r" success: true exit_code: 0 @@ -565,6 +585,52 @@ fn python_install_preview() { } } +#[test] +fn python_install_preview_no_bin() { + let context: TestContext = TestContext::new_with_versions(&[]) + .with_filtered_python_keys() + .with_filtered_exe_suffix() + .with_managed_python_dirs(); + + // Install the latest version + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("--no-bin"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.13.5 in [TIME] + + cpython-3.13.5-[PLATFORM] + "); + + let bin_python = context + .bin_dir + .child(format!("python3.13{}", std::env::consts::EXE_SUFFIX)); + + // The executable should not be installed in the bin directory + bin_python.assert(predicate::path::missing()); + + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("--no-bin").arg("--default"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: the argument '--no-bin' cannot be used with '--default' + + Usage: uv python install --no-bin --install-dir [TARGETS]... + + For more information, try '--help'. + "); + + let bin_python = context + .bin_dir + .child(format!("python{}", std::env::consts::EXE_SUFFIX)); + + // The executable should not be installed in the bin directory + bin_python.assert(predicate::path::missing()); +} + #[test] fn python_install_preview_upgrade() { let context = TestContext::new_with_versions(&[]) diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 13df63c19..93d928518 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -2795,7 +2795,8 @@ uv python install [OPTIONS] [TARGETS]...

    May also be set with the UV_PYTHON_INSTALL_MIRROR environment variable.

    --native-tls

    Whether to load TLS certificates from the platform's native certificate store.

    By default, uv loads certificates from the bundled webpki-roots crate. The webpki-roots are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).

    However, in some cases, you may want to use the platform's native certificate store, especially if you're relying on a corporate trust root (e.g., for a mandatory proxy) that's included in your system's certificate store.

    -

    May also be set with the UV_NATIVE_TLS environment variable.

    --no-cache, --no-cache-dir, -n

    Avoid reading from or writing to the cache, instead using a temporary directory for the duration of the operation

    +

    May also be set with the UV_NATIVE_TLS environment variable.

    --no-bin

    Do not install a Python executable into the bin directory

    +
    --no-cache, --no-cache-dir, -n

    Avoid reading from or writing to the cache, instead using a temporary directory for the duration of the operation

    May also be set with the UV_NO_CACHE environment variable.

    --no-config

    Avoid discovering configuration files (pyproject.toml, uv.toml).

    Normally, configuration files are discovered in the current directory, parent directories, or user configuration directories.

    May also be set with the UV_NO_CONFIG environment variable.

    --no-managed-python

    Disable use of uv-managed Python versions.

    From d2c81e503f19cf63bd335e8a08df1bf6b542dac0 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 15 Jul 2025 12:29:11 -0500 Subject: [PATCH 215/349] Make preview Python registration on Windows non-fatal (#14614) Same as #14612 for registration with the Windows Registry. --- crates/uv-cli/src/lib.rs | 11 ++++++++ crates/uv/src/commands/python/install.rs | 32 ++++++++++++++---------- crates/uv/src/lib.rs | 2 ++ crates/uv/src/settings.rs | 7 ++++++ crates/uv/tests/it/help.rs | 5 ++++ docs/reference/cli.md | 1 + 6 files changed, 45 insertions(+), 13 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 70d5322d9..2efb30724 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -4954,6 +4954,17 @@ pub struct PythonInstallArgs { #[arg(long, overrides_with("bin"), conflicts_with("default"))] pub no_bin: bool, + /// Register the Python installation in the Windows registry. + /// + /// This is the default behavior on Windows. If this flag is provided explicitly, uv will error if the + /// registry entry cannot be created. + #[arg(long, overrides_with("no_registry"), hide = true)] + pub registry: bool, + + /// Do not register the Python installation in the Windows registry. + #[arg(long, overrides_with("registry"))] + pub no_registry: bool, + /// The Python version(s) to install. /// /// If not provided, the requested Python version(s) will be read from the `UV_PYTHON` diff --git a/crates/uv/src/commands/python/install.rs b/crates/uv/src/commands/python/install.rs index b22d6010e..bbab7cbb1 100644 --- a/crates/uv/src/commands/python/install.rs +++ b/crates/uv/src/commands/python/install.rs @@ -152,6 +152,7 @@ pub(crate) async fn install( reinstall: bool, upgrade: bool, bin: Option, + registry: Option, force: bool, python_install_mirror: Option, pypy_install_mirror: Option, @@ -500,7 +501,7 @@ pub(crate) async fn install( ); } - if preview.is_enabled() { + if preview.is_enabled() && !matches!(registry, Some(false)) { #[cfg(windows)] { match uv_python::windows_registry::create_registry_entry(installation) { @@ -670,11 +671,14 @@ pub(crate) async fn install( } if !errors.is_empty() { - // If there are only bin install errors and the user didn't opt-in, we're only going to warn - let fatal = errors - .iter() - .all(|(kind, _, _)| matches!(kind, InstallErrorKind::Bin)) - && bin.is_none(); + // If there are only side-effect install errors and the user didn't opt-in, we're only going + // to warn + let fatal = !errors.iter().all(|(kind, _, _)| match kind { + InstallErrorKind::Bin => bin.is_none(), + #[cfg(windows)] + InstallErrorKind::Registry => registry.is_none(), + InstallErrorKind::DownloadUnpack => false, + }); for (kind, key, err) in errors .into_iter() @@ -691,10 +695,14 @@ pub(crate) async fn install( (level, "install executable for") } #[cfg(windows)] - InstallErrorKind::Registry => ( - "error".red().bold().to_string(), - "install registry entry for", - ), + InstallErrorKind::Registry => { + let level = match registry { + None => "warning".yellow().bold().to_string(), + Some(false) => continue, + Some(true) => "error".red().bold().to_string(), + }; + (level, "install registry entry for") + } }; writeln!( @@ -714,10 +722,8 @@ pub(crate) async fn install( } if fatal { - return Ok(ExitStatus::Success); + return Ok(ExitStatus::Failure); } - - return Ok(ExitStatus::Failure); } Ok(ExitStatus::Success) diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 3a700b965..e6fea035f 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -1403,6 +1403,7 @@ async fn run(mut cli: Cli) -> Result { args.reinstall, upgrade, args.bin, + args.registry, args.force, args.python_install_mirror, args.pypy_install_mirror, @@ -1432,6 +1433,7 @@ async fn run(mut cli: Cli) -> Result { reinstall, upgrade, args.bin, + args.registry, args.force, args.python_install_mirror, args.pypy_install_mirror, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index d373250ac..b221f0f5d 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -934,6 +934,7 @@ pub(crate) struct PythonInstallSettings { pub(crate) reinstall: bool, pub(crate) force: bool, pub(crate) bin: Option, + pub(crate) registry: Option, pub(crate) python_install_mirror: Option, pub(crate) pypy_install_mirror: Option, pub(crate) python_downloads_json_url: Option, @@ -964,6 +965,8 @@ impl PythonInstallSettings { reinstall, bin, no_bin, + registry, + no_registry, force, mirror: _, pypy_mirror: _, @@ -977,6 +980,7 @@ impl PythonInstallSettings { reinstall, force, bin: flag(bin, no_bin, "bin"), + registry: flag(registry, no_registry, "registry"), python_install_mirror: python_mirror, pypy_install_mirror: pypy_mirror, python_downloads_json_url, @@ -992,6 +996,7 @@ pub(crate) struct PythonUpgradeSettings { pub(crate) install_dir: Option, pub(crate) targets: Vec, pub(crate) force: bool, + pub(crate) registry: Option, pub(crate) python_install_mirror: Option, pub(crate) pypy_install_mirror: Option, pub(crate) python_downloads_json_url: Option, @@ -1019,6 +1024,7 @@ impl PythonUpgradeSettings { let force = false; let default = false; let bin = None; + let registry = None; let PythonUpgradeArgs { install_dir, @@ -1032,6 +1038,7 @@ impl PythonUpgradeSettings { install_dir, targets, force, + registry, python_install_mirror: python_mirror, pypy_install_mirror: pypy_mirror, python_downloads_json_url, diff --git a/crates/uv/tests/it/help.rs b/crates/uv/tests/it/help.rs index a6230108c..a557b0eff 100644 --- a/crates/uv/tests/it/help.rs +++ b/crates/uv/tests/it/help.rs @@ -507,6 +507,9 @@ fn help_subsubcommand() { --no-bin Do not install a Python executable into the `bin` directory + --no-registry + Do not register the Python installation in the Windows registry + --mirror Set the URL to use as the source for downloading Python installations. @@ -795,6 +798,8 @@ fn help_flag_subsubcommand() { The directory to store the Python installation in [env: UV_PYTHON_INSTALL_DIR=] --no-bin Do not install a Python executable into the `bin` directory + --no-registry + Do not register the Python installation in the Windows registry --mirror Set the URL to use as the source for downloading Python installations [env: UV_PYTHON_INSTALL_MIRROR=] diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 93d928518..f6bc028df 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -2804,6 +2804,7 @@ uv python install [OPTIONS] [TARGETS]...

    May also be set with the UV_NO_MANAGED_PYTHON environment variable.

    --no-progress

    Hide all progress outputs.

    For example, spinners or progress bars.

    May also be set with the UV_NO_PROGRESS environment variable.

    --no-python-downloads

    Disable automatic downloads of Python.

    +
    --no-registry

    Do not register the Python installation in the Windows registry

    --offline

    Disable network access.

    When disabled, uv will only use locally cached data and locally available files.

    May also be set with the UV_OFFLINE environment variable.

    --project project

    Run the command within the given project directory.

    From c226d66f35b17df57018015c5f9c21d446d51849 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 15 Jul 2025 12:55:57 -0500 Subject: [PATCH 216/349] Rename "Dependency specifiers" section to exclude PEP 508 reference (#14631) --- docs/concepts/projects/dependencies.md | 8 ++++---- docs/js/extra.js | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/concepts/projects/dependencies.md b/docs/concepts/projects/dependencies.md index 2eabbf4dc..022db4d7e 100644 --- a/docs/concepts/projects/dependencies.md +++ b/docs/concepts/projects/dependencies.md @@ -808,12 +808,12 @@ Or, to opt-out of using an editable dependency in a workspace: $ uv add --no-editable ./path/foo ``` -## Dependency specifiers (PEP 508) +## Dependency specifiers -uv uses +uv uses standard [dependency specifiers](https://packaging.python.org/en/latest/specifications/dependency-specifiers/), -previously known as [PEP 508](https://peps.python.org/pep-0508/). A dependency specifier is composed -of, in order: +originally defined in [PEP 508](https://peps.python.org/pep-0508/). A dependency specifier is +composed of, in order: - The dependency name - The extras you want (optional) diff --git a/docs/js/extra.js b/docs/js/extra.js index bfb34c7fa..58a71e98d 100644 --- a/docs/js/extra.js +++ b/docs/js/extra.js @@ -78,6 +78,8 @@ document$.subscribe(function () { "concepts/projects/#building-projects": "concepts/projects/build/", "concepts/projects/#build-isolation": "concepts/projects/config/#build-isolation", + "concepts/projects/dependencies/#dependency-specifiers-pep-508": + "concepts/projects/dependencies/#dependency-specifiers", }; // The prefix for the site, see `site_dir` in `mkdocs.yml` From d5257202662773b6794b1b2de6c490dd1404d7b5 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 15 Jul 2025 13:47:02 -0500 Subject: [PATCH 217/349] Add `uv python update-shell` (#14627) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part of #14296 This is the same as `uv tool update-shell` but handles the case where the Python bin directory is configured to a different path. ``` ❯ UV_PYTHON_BIN_DIR=/tmp/foo cargo run -q -- python install --preview 3.13.3 Installed Python 3.13.3 in 1.75s + cpython-3.13.3-macos-aarch64-none warning: `/tmp/foo` is not on your PATH. To use installed Python executables, run `export PATH="/tmp/foo:$PATH"` or `uv python update-shell`. ❯ UV_PYTHON_BIN_DIR=/tmp/foo cargo run -q -- python update-shell Created configuration file: /Users/zb/.zshenv Restart your shell to apply changes ❯ cat /Users/zb/.zshenv # uv export PATH="/tmp/foo:$PATH" ❯ UV_TOOL_BIN_DIR=/tmp/bar cargo run -q -- tool update-shell Updated configuration file: /Users/zb/.zshenv Restart your shell to apply changes ❯ cat /Users/zb/.zshenv # uv export PATH="/tmp/foo:$PATH" # uv export PATH="/tmp/bar:$PATH" ``` --- crates/uv-cli/src/lib.rs | 13 ++ crates/uv/src/commands/mod.rs | 1 + crates/uv/src/commands/python/install.rs | 23 ++- crates/uv/src/commands/python/mod.rs | 1 + crates/uv/src/commands/python/update_shell.rs | 153 ++++++++++++++++++ crates/uv/src/lib.rs | 6 + crates/uv/tests/it/help.rs | 35 ++-- docs/reference/cli.md | 65 ++++++++ 8 files changed, 274 insertions(+), 23 deletions(-) create mode 100644 crates/uv/src/commands/python/update_shell.rs diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 2efb30724..a846aec59 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -4856,6 +4856,19 @@ pub enum PythonCommand { /// Uninstall Python versions. Uninstall(PythonUninstallArgs), + + /// Ensure that the Python executable directory is on the `PATH`. + /// + /// If the Python executable directory is not present on the `PATH`, uv will attempt to add it to + /// the relevant shell configuration files. + /// + /// If the shell configuration files already include a blurb to add the executable directory to + /// the path, but the directory is not present on the `PATH`, uv will exit with an error. + /// + /// The Python executable directory is determined according to the XDG standard and can be + /// retrieved with `uv python dir --bin`. + #[command(alias = "ensurepath")] + UpdateShell, } #[derive(Args)] diff --git a/crates/uv/src/commands/mod.rs b/crates/uv/src/commands/mod.rs index d1e647363..405aad955 100644 --- a/crates/uv/src/commands/mod.rs +++ b/crates/uv/src/commands/mod.rs @@ -38,6 +38,7 @@ pub(crate) use python::install::install as python_install; pub(crate) use python::list::list as python_list; pub(crate) use python::pin::pin as python_pin; pub(crate) use python::uninstall::uninstall as python_uninstall; +pub(crate) use python::update_shell::update_shell as python_update_shell; #[cfg(feature = "self-update")] pub(crate) use self_update::self_update; pub(crate) use tool::dir::dir as tool_dir; diff --git a/crates/uv/src/commands/python/install.rs b/crates/uv/src/commands/python/install.rs index bbab7cbb1..feb0cf7c7 100644 --- a/crates/uv/src/commands/python/install.rs +++ b/crates/uv/src/commands/python/install.rs @@ -993,20 +993,29 @@ fn warn_if_not_on_path(bin: &Path) { if !Shell::contains_path(bin) { if let Some(shell) = Shell::from_env() { if let Some(command) = shell.prepend_path(bin) { - warn_user!( - "`{}` is not on your PATH. To use the installed Python executable, run `{}`.", - bin.simplified_display().cyan(), - command.green(), - ); + if shell.supports_update() { + warn_user!( + "`{}` is not on your PATH. To use installed Python executables, run `{}` or `{}`.", + bin.simplified_display().cyan(), + command.green(), + "uv python update-shell".green() + ); + } else { + warn_user!( + "`{}` is not on your PATH. To use installed Python executables, run `{}`.", + bin.simplified_display().cyan(), + command.green() + ); + } } else { warn_user!( - "`{}` is not on your PATH. To use the installed Python executable, add the directory to your PATH.", + "`{}` is not on your PATH. To use installed Python executables, add the directory to your PATH.", bin.simplified_display().cyan(), ); } } else { warn_user!( - "`{}` is not on your PATH. To use the installed Python executable, add the directory to your PATH.", + "`{}` is not on your PATH. To use installed Python executables, add the directory to your PATH.", bin.simplified_display().cyan(), ); } diff --git a/crates/uv/src/commands/python/mod.rs b/crates/uv/src/commands/python/mod.rs index afc700d23..6f7a5c980 100644 --- a/crates/uv/src/commands/python/mod.rs +++ b/crates/uv/src/commands/python/mod.rs @@ -4,6 +4,7 @@ pub(crate) mod install; pub(crate) mod list; pub(crate) mod pin; pub(crate) mod uninstall; +pub(crate) mod update_shell; #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] pub(super) enum ChangeEventKind { diff --git a/crates/uv/src/commands/python/update_shell.rs b/crates/uv/src/commands/python/update_shell.rs new file mode 100644 index 000000000..18757ff9e --- /dev/null +++ b/crates/uv/src/commands/python/update_shell.rs @@ -0,0 +1,153 @@ +#![cfg_attr(windows, allow(unreachable_code))] + +use std::fmt::Write; + +use anyhow::Result; +use owo_colors::OwoColorize; +use tokio::io::AsyncWriteExt; +use tracing::debug; + +use uv_fs::Simplified; +use uv_python::managed::python_executable_dir; +use uv_shell::Shell; + +use crate::commands::ExitStatus; +use crate::printer::Printer; + +/// Ensure that the executable directory is in PATH. +pub(crate) async fn update_shell(printer: Printer) -> Result { + let executable_directory = python_executable_dir()?; + debug!( + "Ensuring that the executable directory is in PATH: {}", + executable_directory.simplified_display() + ); + + #[cfg(windows)] + { + if uv_shell::windows::prepend_path(&executable_directory)? { + writeln!( + printer.stderr(), + "Updated PATH to include executable directory {}", + executable_directory.simplified_display().cyan() + )?; + writeln!(printer.stderr(), "Restart your shell to apply changes")?; + } else { + writeln!( + printer.stderr(), + "Executable directory {} is already in PATH", + executable_directory.simplified_display().cyan() + )?; + } + + return Ok(ExitStatus::Success); + } + + if Shell::contains_path(&executable_directory) { + writeln!( + printer.stderr(), + "Executable directory {} is already in PATH", + executable_directory.simplified_display().cyan() + )?; + return Ok(ExitStatus::Success); + } + + // Determine the current shell. + let Some(shell) = Shell::from_env() else { + return Err(anyhow::anyhow!( + "The executable directory {} is not in PATH, but the current shell could not be determined", + executable_directory.simplified_display().cyan() + )); + }; + + // Look up the configuration files (e.g., `.bashrc`, `.zshrc`) for the shell. + let files = shell.configuration_files(); + if files.is_empty() { + return Err(anyhow::anyhow!( + "The executable directory {} is not in PATH, but updating {shell} is currently unsupported", + executable_directory.simplified_display().cyan() + )); + } + + // Prepare the command (e.g., `export PATH="$HOME/.cargo/bin:$PATH"`). + let Some(command) = shell.prepend_path(&executable_directory) else { + return Err(anyhow::anyhow!( + "The executable directory {} is not in PATH, but the necessary command to update {shell} could not be determined", + executable_directory.simplified_display().cyan() + )); + }; + + // Update each file, as necessary. + let mut updated = false; + for file in files { + // Search for the command in the file, to avoid redundant updates. + match fs_err::tokio::read_to_string(&file).await { + Ok(contents) => { + if contents + .lines() + .map(str::trim) + .filter(|line| !line.starts_with('#')) + .any(|line| line.contains(&command)) + { + debug!( + "Skipping already-updated configuration file: {}", + file.simplified_display() + ); + continue; + } + + // Append the command to the file. + fs_err::tokio::OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open(&file) + .await? + .write_all(format!("{contents}\n# uv\n{command}\n").as_bytes()) + .await?; + + writeln!( + printer.stderr(), + "Updated configuration file: {}", + file.simplified_display().cyan() + )?; + updated = true; + } + Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + // Ensure that the directory containing the file exists. + if let Some(parent) = file.parent() { + fs_err::tokio::create_dir_all(&parent).await?; + } + + // Append the command to the file. + fs_err::tokio::OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open(&file) + .await? + .write_all(format!("# uv\n{command}\n").as_bytes()) + .await?; + + writeln!( + printer.stderr(), + "Created configuration file: {}", + file.simplified_display().cyan() + )?; + updated = true; + } + Err(err) => { + return Err(err.into()); + } + } + } + + if updated { + writeln!(printer.stderr(), "Restart your shell to apply changes")?; + Ok(ExitStatus::Success) + } else { + Err(anyhow::anyhow!( + "The executable directory {} is not in PATH, but the {shell} configuration files are already up-to-date", + executable_directory.simplified_display().cyan() + )) + } +} diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index e6fea035f..384f48ac4 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -1537,6 +1537,12 @@ async fn run(mut cli: Cli) -> Result { commands::python_dir(args.bin)?; Ok(ExitStatus::Success) } + Commands::Python(PythonNamespace { + command: PythonCommand::UpdateShell, + }) => { + commands::python_update_shell(printer).await?; + Ok(ExitStatus::Success) + } Commands::Publish(args) => { show_settings!(args); diff --git a/crates/uv/tests/it/help.rs b/crates/uv/tests/it/help.rs index a557b0eff..39de4c6f9 100644 --- a/crates/uv/tests/it/help.rs +++ b/crates/uv/tests/it/help.rs @@ -290,14 +290,15 @@ fn help_subcommand() { Usage: uv python [OPTIONS] Commands: - list List the available Python installations - install Download and install Python versions - upgrade Upgrade installed Python versions to the latest supported patch release (requires the - `--preview` flag) - find Search for a Python installation - pin Pin to a specific Python version - dir Show the uv Python installation directory - uninstall Uninstall Python versions + list List the available Python installations + install Download and install Python versions + upgrade Upgrade installed Python versions to the latest supported patch release (requires + the `--preview` flag) + find Search for a Python installation + pin Pin to a specific Python version + dir Show the uv Python installation directory + uninstall Uninstall Python versions + update-shell Ensure that the Python executable directory is on the `PATH` Cache options: -n, --no-cache @@ -725,14 +726,15 @@ fn help_flag_subcommand() { Usage: uv python [OPTIONS] Commands: - list List the available Python installations - install Download and install Python versions - upgrade Upgrade installed Python versions to the latest supported patch release (requires the - `--preview` flag) - find Search for a Python installation - pin Pin to a specific Python version - dir Show the uv Python installation directory - uninstall Uninstall Python versions + list List the available Python installations + install Download and install Python versions + upgrade Upgrade installed Python versions to the latest supported patch release (requires + the `--preview` flag) + find Search for a Python installation + pin Pin to a specific Python version + dir Show the uv Python installation directory + uninstall Uninstall Python versions + update-shell Ensure that the Python executable directory is on the `PATH` Cache options: -n, --no-cache Avoid reading from or writing to the cache, instead using a temporary @@ -934,6 +936,7 @@ fn help_unknown_subsubcommand() { pin dir uninstall + update-shell "); } diff --git a/docs/reference/cli.md b/docs/reference/cli.md index f6bc028df..66c46ae0c 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -2633,6 +2633,7 @@ uv python [OPTIONS]
    uv python pin

    Pin to a specific Python version

    uv python dir

    Show the uv Python installation directory

    uv python uninstall

    Uninstall Python versions

    +
    uv python update-shell

    Ensure that the Python executable directory is on the PATH

    ### uv python list @@ -3206,6 +3207,70 @@ uv python uninstall [OPTIONS] ...

    You can configure fine-grained logging using the RUST_LOG environment variable. (https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives)

    +### uv python update-shell + +Ensure that the Python executable directory is on the `PATH`. + +If the Python executable directory is not present on the `PATH`, uv will attempt to add it to the relevant shell configuration files. + +If the shell configuration files already include a blurb to add the executable directory to the path, but the directory is not present on the `PATH`, uv will exit with an error. + +The Python executable directory is determined according to the XDG standard and can be retrieved with `uv python dir --bin`. + +

    Usage

    + +``` +uv python update-shell [OPTIONS] +``` + +

    Options

    + +
    --allow-insecure-host, --trusted-host allow-insecure-host

    Allow insecure connections to a host.

    +

    Can be provided multiple times.

    +

    Expects to receive either a hostname (e.g., localhost), a host-port pair (e.g., localhost:8080), or a URL (e.g., https://localhost).

    +

    WARNING: Hosts included in this list will not be verified against the system's certificate store. Only use --allow-insecure-host in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.

    +

    May also be set with the UV_INSECURE_HOST environment variable.

    --cache-dir cache-dir

    Path to the cache directory.

    +

    Defaults to $XDG_CACHE_HOME/uv or $HOME/.cache/uv on macOS and Linux, and %LOCALAPPDATA%\uv\cache on Windows.

    +

    To view the location of the cache directory, run uv cache dir.

    +

    May also be set with the UV_CACHE_DIR environment variable.

    --color color-choice

    Control the use of color in output.

    +

    By default, uv will automatically detect support for colors when writing to a terminal.

    +

    Possible values:

    +
      +
    • auto: Enables colored output only when the output is going to a terminal or TTY with support
    • +
    • always: Enables colored output regardless of the detected environment
    • +
    • never: Disables colored output
    • +
    --config-file config-file

    The path to a uv.toml file to use for configuration.

    +

    While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.

    +

    May also be set with the UV_CONFIG_FILE environment variable.

    --directory directory

    Change to the given directory prior to running the command.

    +

    Relative paths are resolved with the given directory as the base.

    +

    See --project to only change the project root directory.

    +
    --help, -h

    Display the concise help for this command

    +
    --managed-python

    Require use of uv-managed Python versions.

    +

    By default, uv prefers using Python versions it manages. However, it will use system Python versions if a uv-managed Python is not installed. This option disables use of system Python versions.

    +

    May also be set with the UV_MANAGED_PYTHON environment variable.

    --native-tls

    Whether to load TLS certificates from the platform's native certificate store.

    +

    By default, uv loads certificates from the bundled webpki-roots crate. The webpki-roots are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).

    +

    However, in some cases, you may want to use the platform's native certificate store, especially if you're relying on a corporate trust root (e.g., for a mandatory proxy) that's included in your system's certificate store.

    +

    May also be set with the UV_NATIVE_TLS environment variable.

    --no-cache, --no-cache-dir, -n

    Avoid reading from or writing to the cache, instead using a temporary directory for the duration of the operation

    +

    May also be set with the UV_NO_CACHE environment variable.

    --no-config

    Avoid discovering configuration files (pyproject.toml, uv.toml).

    +

    Normally, configuration files are discovered in the current directory, parent directories, or user configuration directories.

    +

    May also be set with the UV_NO_CONFIG environment variable.

    --no-managed-python

    Disable use of uv-managed Python versions.

    +

    Instead, uv will search for a suitable Python version on the system.

    +

    May also be set with the UV_NO_MANAGED_PYTHON environment variable.

    --no-progress

    Hide all progress outputs.

    +

    For example, spinners or progress bars.

    +

    May also be set with the UV_NO_PROGRESS environment variable.

    --no-python-downloads

    Disable automatic downloads of Python.

    +
    --offline

    Disable network access.

    +

    When disabled, uv will only use locally cached data and locally available files.

    +

    May also be set with the UV_OFFLINE environment variable.

    --project project

    Run the command within the given project directory.

    +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project's virtual environment (.venv).

    +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    +

    See --directory to change the working directory entirely.

    +

    This setting has no effect when used in the uv pip interface.

    +

    May also be set with the UV_PROJECT environment variable.

    --quiet, -q

    Use quiet output.

    +

    Repeating this option, e.g., -qq, will enable a silent mode in which uv will write no output to stdout.

    +
    --verbose, -v

    Use verbose output.

    +

    You can configure fine-grained logging using the RUST_LOG environment variable. (https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives)

    +
    + ## uv pip Manage Python packages with a pip-compatible interface From ab2bd0179bac888188b69524a20f8d336e439a64 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 15 Jul 2025 14:35:54 -0500 Subject: [PATCH 218/349] Mention the `revision` in the lockfile versioning doc (#14634) --- docs/concepts/resolution.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/concepts/resolution.md b/docs/concepts/resolution.md index ec28d71a3..e857e7b1d 100644 --- a/docs/concepts/resolution.md +++ b/docs/concepts/resolution.md @@ -535,3 +535,7 @@ The schema version is considered part of the public API, and so is only bumped i a breaking change (see [Versioning](../reference/policies/versioning.md)). As such, all uv patch versions within a given minor uv release are guaranteed to have full lockfile compatibility. In other words, lockfiles may only be rejected across minor releases. + +The `revision` field of the lockfile is used to track backwards compatible changes to the lockfile. +For example, adding a new field to distributions. Changes to the revision will not cause older +versions of uv to error. From 863e73a841ca6360a611196e9c5e3ccee894f9d8 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 15 Jul 2025 16:47:35 -0500 Subject: [PATCH 219/349] Skip Windows Python interpreters that return a broken MSIX package code (#14636) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently we treat all spawn failures as fatal, because they indicate a broken interpreter. In this case, I think we should just skip these broken interpreters — though I don't know the root cause of why it's broken yet. Closes https://github.com/astral-sh/uv/issues/14637 See https://discord.com/channels/1039017663004942429/1039017663512449056/1394758502647333025 --- crates/uv-python/src/discovery.rs | 8 ++++++++ crates/uv-python/src/interpreter.rs | 31 +++++++++++++++++++++++++---- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/crates/uv-python/src/discovery.rs b/crates/uv-python/src/discovery.rs index 67f8f37ff..c067082dd 100644 --- a/crates/uv-python/src/discovery.rs +++ b/crates/uv-python/src/discovery.rs @@ -884,6 +884,14 @@ impl Error { ); false } + #[cfg(windows)] + InterpreterError::CorruptWindowsPackage { path, err } => { + debug!( + "Skipping bad interpreter at {} from {source}: {err}", + path.display() + ); + false + } InterpreterError::NotFound(path) | InterpreterError::BrokenSymlink(BrokenSymlink { path, .. }) => { // If the interpreter is from an active, valid virtual environment, we should diff --git a/crates/uv-python/src/interpreter.rs b/crates/uv-python/src/interpreter.rs index 0f074ebb6..fc5adb833 100644 --- a/crates/uv-python/src/interpreter.rs +++ b/crates/uv-python/src/interpreter.rs @@ -34,6 +34,9 @@ use crate::{ VirtualEnvironment, }; +#[cfg(windows)] +use windows_sys::Win32::Foundation::{APPMODEL_ERROR_NO_PACKAGE, ERROR_CANT_ACCESS_FILE}; + /// A Python executable and its associated platform markers. #[derive(Debug, Clone)] pub struct Interpreter { @@ -760,6 +763,13 @@ pub enum Error { #[source] err: io::Error, }, + #[cfg(windows)] + #[error("Failed to query Python interpreter at `{path}`")] + CorruptWindowsPackage { + path: PathBuf, + #[source] + err: io::Error, + }, #[error("{0}")] UnexpectedResponse(UnexpectedResponseError), #[error("{0}")] @@ -872,10 +882,23 @@ impl InterpreterInfo { .arg("-c") .arg(script) .output() - .map_err(|err| Error::SpawnFailed { - path: interpreter.to_path_buf(), - err, - })?; + .map_err( + |err| match err.raw_os_error().and_then(|code| u32::try_from(code).ok()) { + // These error codes are returned if the Python interpreter is a corrupt MSIX + // package, which we want to differentiate from a typical spawn failure. + #[cfg(windows)] + Some(APPMODEL_ERROR_NO_PACKAGE | ERROR_CANT_ACCESS_FILE) => { + Error::CorruptWindowsPackage { + path: interpreter.to_path_buf(), + err, + } + } + _ => Error::SpawnFailed { + path: interpreter.to_path_buf(), + err, + }, + }, + )?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string(); From 8d6d0678a71d86020caaf20107b1e81af29f471d Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 15 Jul 2025 16:47:43 -0500 Subject: [PATCH 220/349] Move "Conflicting dependencies" to the "Resolution" page (#14633) --- docs/concepts/projects/config.md | 121 +++++++++++-------------------- docs/concepts/resolution.md | 81 +++++++++++++++++++-- 2 files changed, 118 insertions(+), 84 deletions(-) diff --git a/docs/concepts/projects/config.md b/docs/concepts/projects/config.md index f9d33ed90..8efb667a1 100644 --- a/docs/concepts/projects/config.md +++ b/docs/concepts/projects/config.md @@ -196,41 +196,6 @@ To target this environment, you'd export `UV_PROJECT_ENVIRONMENT=/usr/local`. environment. The `--active` flag can be used to opt-in to respecting `VIRTUAL_ENV`. The `--no-active` flag can be used to silence the warning. -## Limited resolution environments - -If your project supports a more limited set of platforms or Python versions, you can constrain the -set of solved platforms via the `environments` setting, which accepts a list of PEP 508 environment -markers. For example, to constrain the lockfile to macOS and Linux, and exclude Windows: - -```toml title="pyproject.toml" -[tool.uv] -environments = [ - "sys_platform == 'darwin'", - "sys_platform == 'linux'", -] -``` - -See the [resolution documentation](../resolution.md#limited-resolution-environments) for more. - -## Required environments - -If your project _must_ support a specific platform or Python version, you can mark that platform as -required via the `required-environments` setting. For example, to require that the project supports -Intel macOS: - -```toml title="pyproject.toml" -[tool.uv] -required-environments = [ - "sys_platform == 'darwin' and platform_machine == 'x86_64'", -] -``` - -The `required-environments` setting is only relevant for packages that do not publish a source -distribution (like PyTorch), as such packages can _only_ be installed on environments covered by the -set of pre-built binary distributions (wheels) published by that package. - -See the [resolution documentation](../resolution.md#required-environments) for more. - ## Build isolation By default, uv builds all packages in isolated virtual environments, as per @@ -401,33 +366,12 @@ in the deployed environment without a dependency on the originating source code. ## Conflicting dependencies -uv requires that all optional dependencies ("extras") declared by the project are compatible with -each other and resolves all optional dependencies together when creating the lockfile. +uv requires resolves all project dependencies together, including optional dependencies ("extras") +and dependency groups. If dependencies declared in one section are not compatible with those in +another section, uv will fail to resolve the requirements of the project with an error. -If optional dependencies declared in one extra are not compatible with those in another extra, uv -will fail to resolve the requirements of the project with an error. - -To work around this, uv supports declaring conflicting extras. For example, consider two sets of -optional dependencies that conflict with one another: - -```toml title="pyproject.toml" -[project.optional-dependencies] -extra1 = ["numpy==2.1.2"] -extra2 = ["numpy==2.0.0"] -``` - -If you run `uv lock` with the above dependencies, resolution will fail: - -```console -$ uv lock - x No solution found when resolving dependencies: - `-> Because myproject[extra2] depends on numpy==2.0.0 and myproject[extra1] depends on numpy==2.1.2, we can conclude that myproject[extra1] and - myproject[extra2] are incompatible. - And because your project requires myproject[extra1] and myproject[extra2], we can conclude that your projects's requirements are unsatisfiable. -``` - -But if you specify that `extra1` and `extra2` are conflicting, uv will resolve them separately. -Specify conflicts in the `tool.uv` section: +uv supports explicit declaration of conflicting dependency groups. For example, to declare that the +`optional-dependency` groups `extra1` and `extra2` are incompatible: ```toml title="pyproject.toml" [tool.uv] @@ -439,25 +383,9 @@ conflicts = [ ] ``` -Now, running `uv lock` will succeed. Note though, that now you cannot install both `extra1` and -`extra2` at the same time: - -```console -$ uv sync --extra extra1 --extra extra2 -Resolved 3 packages in 14ms -error: extra `extra1`, extra `extra2` are incompatible with the declared conflicts: {`myproject[extra1]`, `myproject[extra2]`} -``` - -This error occurs because installing both `extra1` and `extra2` would result in installing two -different versions of a package into the same environment. - -The above strategy for dealing with conflicting extras also works with dependency groups: +Or, to declare the development dependency groups `group1` and `group2` incompatible: ```toml title="pyproject.toml" -[dependency-groups] -group1 = ["numpy==2.1.2"] -group2 = ["numpy==2.0.0"] - [tool.uv] conflicts = [ [ @@ -467,4 +395,39 @@ conflicts = [ ] ``` -The only difference with conflicting extras is that you need to use `group` instead of `extra`. +See the [resolution documentation](../resolution.md#conflicting-dependencies) for more. + +## Limited resolution environments + +If your project supports a more limited set of platforms or Python versions, you can constrain the +set of solved platforms via the `environments` setting, which accepts a list of PEP 508 environment +markers. For example, to constrain the lockfile to macOS and Linux, and exclude Windows: + +```toml title="pyproject.toml" +[tool.uv] +environments = [ + "sys_platform == 'darwin'", + "sys_platform == 'linux'", +] +``` + +See the [resolution documentation](../resolution.md#limited-resolution-environments) for more. + +## Required environments + +If your project _must_ support a specific platform or Python version, you can mark that platform as +required via the `required-environments` setting. For example, to require that the project supports +Intel macOS: + +```toml title="pyproject.toml" +[tool.uv] +required-environments = [ + "sys_platform == 'darwin' and platform_machine == 'x86_64'", +] +``` + +The `required-environments` setting is only relevant for packages that do not publish a source +distribution (like PyTorch), as such packages can _only_ be installed on environments covered by the +set of pre-built binary distributions (wheels) published by that package. + +See the [resolution documentation](../resolution.md#required-environments) for more. diff --git a/docs/concepts/resolution.md b/docs/concepts/resolution.md index e857e7b1d..278289ea9 100644 --- a/docs/concepts/resolution.md +++ b/docs/concepts/resolution.md @@ -453,6 +453,77 @@ though only `name`, `version`, `requires-dist`, `requires-python`, and `provides uv. The `version` field is also considered optional. If omitted, the metadata will be used for all versions of the specified package. +## Conflicting dependencies + +uv requires that all optional dependencies ("extras") declared by the project are compatible with +each other and resolves all optional dependencies together when creating the lockfile. + +If optional dependencies declared in one extra are not compatible with those in another extra, uv +will fail to resolve the requirements of the project with an error. + +To work around this, uv supports declaring conflicting extras. For example, consider two sets of +optional dependencies that conflict with one another: + +```toml title="pyproject.toml" +[project.optional-dependencies] +extra1 = ["numpy==2.1.2"] +extra2 = ["numpy==2.0.0"] +``` + +If you run `uv lock` with the above dependencies, resolution will fail: + +```console +$ uv lock + x No solution found when resolving dependencies: + `-> Because myproject[extra2] depends on numpy==2.0.0 and myproject[extra1] depends on numpy==2.1.2, we can conclude that myproject[extra1] and + myproject[extra2] are incompatible. + And because your project requires myproject[extra1] and myproject[extra2], we can conclude that your projects's requirements are unsatisfiable. +``` + +But if you specify that `extra1` and `extra2` are conflicting, uv will resolve them separately. +Specify conflicts in the `tool.uv` section: + +```toml title="pyproject.toml" +[tool.uv] +conflicts = [ + [ + { extra = "extra1" }, + { extra = "extra2" }, + ], +] +``` + +Now, running `uv lock` will succeed. Note though, that now you cannot install both `extra1` and +`extra2` at the same time: + +```console +$ uv sync --extra extra1 --extra extra2 +Resolved 3 packages in 14ms +error: extra `extra1`, extra `extra2` are incompatible with the declared conflicts: {`myproject[extra1]`, `myproject[extra2]`} +``` + +This error occurs because installing both `extra1` and `extra2` would result in installing two +different versions of a package into the same environment. + +The above strategy for dealing with conflicting extras also works with dependency groups: + +```toml title="pyproject.toml" +[dependency-groups] +group1 = ["numpy==2.1.2"] +group2 = ["numpy==2.0.0"] + +[tool.uv] +conflicts = [ + [ + { group = "group1" }, + { group = "group2" }, + ], +] +``` + +The only difference from conflicting extras is that you need to use the `group` key instead of +`extra`. + ## Lower bounds By default, `uv add` adds lower bounds to dependencies and, when using uv to manage projects, uv @@ -513,11 +584,6 @@ reading and extracting archives in the following formats: - lzma tarball (`.tar.lzma`) - zip (`.zip`) -## Learn more - -For more details about the internals of the resolver, see the -[resolver reference](../reference/resolver-internals.md) documentation. - ## Lockfile versioning The `uv.lock` file uses a versioned schema. The schema version is included in the `version` field of @@ -539,3 +605,8 @@ other words, lockfiles may only be rejected across minor releases. The `revision` field of the lockfile is used to track backwards compatible changes to the lockfile. For example, adding a new field to distributions. Changes to the revision will not cause older versions of uv to error. + +## Learn more + +For more details about the internals of the resolver, see the +[resolver reference](../reference/resolver-internals.md) documentation. From 861f7a1c42e366eec0529ef98eeed06665ccba6c Mon Sep 17 00:00:00 2001 From: Gilles Peiffer Date: Wed, 16 Jul 2025 15:44:29 +0200 Subject: [PATCH 221/349] docs: add missing backtick (#14654) Subject is message :) --- docs/pip/packages.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pip/packages.md b/docs/pip/packages.md index a47b1aa0e..cdce527b6 100644 --- a/docs/pip/packages.md +++ b/docs/pip/packages.md @@ -128,7 +128,7 @@ $ uv pip install --group some/path/pyproject.toml:foo --group other/pyproject.to !!! note - As in pip, `--group` flags do not apply to other sources specified with flags like `-r` or -e`. + As in pip, `--group` flags do not apply to other sources specified with flags like `-r` or `-e`. For instance, `uv pip install -r some/path/pyproject.toml --group foo` sources `foo` from `./pyproject.toml` and **not** `some/path/pyproject.toml`. From 03de6c36e34032a754fd49ecbd300246954d8042 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Wed, 16 Jul 2025 09:48:16 -0400 Subject: [PATCH 222/349] Warn on invalid `uv.toml` when provided via direct path (#14653) ## Summary We validate the `uv.toml` when it's discovered automatically, but not when provided via `--config-file`. The same limitations exist, though -- I think the lack of enforcement is just an oversight. Closes https://github.com/astral-sh/uv/issues/14650. --- crates/uv-settings/src/lib.rs | 7 ++++++- crates/uv/tests/it/pip_install.rs | 26 +++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/crates/uv-settings/src/lib.rs b/crates/uv-settings/src/lib.rs index 54ae4e261..d676cc060 100644 --- a/crates/uv-settings/src/lib.rs +++ b/crates/uv-settings/src/lib.rs @@ -170,7 +170,12 @@ impl FilesystemOptions { /// Load a [`FilesystemOptions`] from a `uv.toml` file. pub fn from_file(path: impl AsRef) -> Result { - Ok(Self(read_file(path.as_ref())?)) + let path = path.as_ref(); + tracing::debug!("Reading user configuration from: `{}`", path.display()); + + let options = read_file(path)?; + validate_uv_toml(path, &options)?; + Ok(Self(options)) } } diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index bc27228c7..123d9066b 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -267,7 +267,7 @@ fn invalid_toml_filename() -> Result<()> { } #[test] -fn invalid_uv_toml_option_disallowed() -> Result<()> { +fn invalid_uv_toml_option_disallowed_automatic_discovery() -> Result<()> { let context = TestContext::new("3.12"); let uv_toml = context.temp_dir.child("uv.toml"); uv_toml.write_str(indoc! {r" @@ -288,6 +288,30 @@ fn invalid_uv_toml_option_disallowed() -> Result<()> { Ok(()) } +#[test] +fn invalid_uv_toml_option_disallowed_command_line() -> Result<()> { + let context = TestContext::new("3.12"); + let uv_toml = context.temp_dir.child("foo.toml"); + uv_toml.write_str(indoc! {r" + managed = true + "})?; + + uv_snapshot!(context.pip_install() + .arg("iniconfig") + .arg("--config-file") + .arg("foo.toml"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Failed to parse: `foo.toml`. The `managed` field is not allowed in a `uv.toml` file. `managed` is only applicable in the context of a project, and should be placed in a `pyproject.toml` file instead. + " + ); + + Ok(()) +} + #[test] fn cache_uv_toml_credentials() -> Result<()> { let context = TestContext::new("3.12"); From e547527587fc47d3c83b200afa0d6bd15897081a Mon Sep 17 00:00:00 2001 From: Nathan Cain <13713501+nathanscain@users.noreply.github.com> Date: Wed, 16 Jul 2025 08:52:17 -0500 Subject: [PATCH 223/349] Add UV_LIBC to allow libc selection in multi-libc environment (#14646) Closes #14262 ## Description Adds `UV_LIBC` environment variable and implements check within `Libc::from_env` as recommended here: https://github.com/astral-sh/uv/issues/14262#issuecomment-3014600313 Gave this a few passes to make sure I follow dev practices within uv as best I am able. Feel free to call out anything that could be improved. ## Test Plan Planned to simply run existing test suite. Open to adding more tests once implementation is validated due to my limited Rust experience. --- crates/uv-python/src/platform.rs | 42 +++++++++++++++++++++----------- crates/uv-static/src/env_vars.rs | 4 +++ docs/reference/environment.md | 5 ++++ 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/crates/uv-python/src/platform.rs b/crates/uv-python/src/platform.rs index ce8620ae2..606e05e28 100644 --- a/crates/uv-python/src/platform.rs +++ b/crates/uv-python/src/platform.rs @@ -5,6 +5,8 @@ use std::ops::Deref; use std::{fmt, str::FromStr}; use thiserror::Error; +use uv_static::EnvVars; + #[derive(Error, Debug)] pub enum Error { #[error("Unknown operating system: {0}")] @@ -15,6 +17,8 @@ pub enum Error { UnknownLibc(String), #[error("Unsupported variant `{0}` for architecture `{1}`")] UnsupportedVariant(String, String), + #[error(transparent)] + LibcDetectionError(#[from] LibcDetectionError), } /// Architecture variants, e.g., with support for different instruction sets @@ -95,22 +99,32 @@ pub enum Libc { } impl Libc { - pub(crate) fn from_env() -> Result { + pub(crate) fn from_env() -> Result { match std::env::consts::OS { - "linux" => Ok(Self::Some(match detect_linux_libc()? { - LibcVersion::Manylinux { .. } => match std::env::consts::ARCH { - // Checks if the CPU supports hardware floating-point operations. - // Depending on the result, it selects either the `gnueabihf` (hard-float) or `gnueabi` (soft-float) environment. - // download-metadata.json only includes armv7. - "arm" | "armv5te" | "armv7" => match detect_hardware_floating_point_support() { - Ok(true) => target_lexicon::Environment::Gnueabihf, - Ok(false) => target_lexicon::Environment::Gnueabi, - Err(_) => target_lexicon::Environment::Gnu, + "linux" => { + if let Ok(libc) = std::env::var(EnvVars::UV_LIBC) { + if !libc.is_empty() { + return Self::from_str(&libc); + } + } + + Ok(Self::Some(match detect_linux_libc()? { + LibcVersion::Manylinux { .. } => match std::env::consts::ARCH { + // Checks if the CPU supports hardware floating-point operations. + // Depending on the result, it selects either the `gnueabihf` (hard-float) or `gnueabi` (soft-float) environment. + // download-metadata.json only includes armv7. + "arm" | "armv5te" | "armv7" => { + match detect_hardware_floating_point_support() { + Ok(true) => target_lexicon::Environment::Gnueabihf, + Ok(false) => target_lexicon::Environment::Gnueabi, + Err(_) => target_lexicon::Environment::Gnu, + } + } + _ => target_lexicon::Environment::Gnu, }, - _ => target_lexicon::Environment::Gnu, - }, - LibcVersion::Musllinux { .. } => target_lexicon::Environment::Musl, - })), + LibcVersion::Musllinux { .. } => target_lexicon::Environment::Musl, + })) + } "windows" | "macos" => Ok(Self::None), // Use `None` on platforms without explicit support. _ => Ok(Self::None), diff --git a/crates/uv-static/src/env_vars.rs b/crates/uv-static/src/env_vars.rs index 5b91fccea..ae981cac3 100644 --- a/crates/uv-static/src/env_vars.rs +++ b/crates/uv-static/src/env_vars.rs @@ -154,6 +154,10 @@ impl EnvVars { /// `--no-python-downloads` option. Whether uv should allow Python downloads. pub const UV_PYTHON_DOWNLOADS: &'static str = "UV_PYTHON_DOWNLOADS"; + /// Overrides the environment-determined libc on linux systems when filling in the current platform + /// within Python version requests. Options are: `gnu`, `gnueabi`, `gnueabihf`, `musl`, and `none`. + pub const UV_LIBC: &'static str = "UV_LIBC"; + /// Equivalent to the `--compile-bytecode` command-line argument. If set, uv /// will compile Python source files to bytecode after installation. pub const UV_COMPILE_BYTECODE: &'static str = "UV_COMPILE_BYTECODE"; diff --git a/docs/reference/environment.md b/docs/reference/environment.md index bf8bf29ec..47e4d8db9 100644 --- a/docs/reference/environment.md +++ b/docs/reference/environment.md @@ -167,6 +167,11 @@ Defaults to `~/.local/bin`. Equivalent to the `--keyring-provider` command-line argument. If set, uv will use this value as the keyring provider. +### `UV_LIBC` + +Overrides the environment-determined libc on linux systems when filling in the current platform +within Python version requests. Options are: `gnu`, `gnueabi`, `gnueabihf`, `musl`, and `none`. + ### `UV_LINK_MODE` Equivalent to the `--link-mode` command-line argument. If set, uv will use this as From 0cf5ecf8413c54d7607acdf67cc41f8285f291ed Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 16 Jul 2025 09:04:58 -0500 Subject: [PATCH 224/349] Request arm64 Python in aarch64-windows smoke test (#14655) The Python interpreter selected by `py` recently changed to x64 instead of arm64. Closes https://github.com/astral-sh/uv/pull/14652 See https://github.com/astral-sh/uv/pull/14652 --- .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 0ccc9ea4e..bb357f4a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2261,7 +2261,7 @@ jobs: name: uv-windows-aarch64-${{ github.sha }} - name: "Validate global Python install" - run: py -3.13 ./scripts/check_system_python.py --uv ./uv.exe + run: py -3.13-arm64 ./scripts/check_system_python.py --uv ./uv.exe # Test our PEP 514 integration that installs Python into the Windows registry. system-test-windows-registry: From 1b2f212e8b2f91069b858cb7f5905589c9d15add Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 16 Jul 2025 09:05:10 -0500 Subject: [PATCH 225/349] Use `[PYTHON]` placeholder in filtered Python names (#14640) We should never replace with a non-placeholder, it is very confusing when trying to understand test behavior --- crates/uv/tests/it/common/mod.rs | 15 ++++- crates/uv/tests/it/pip_sync.rs | 50 ++++++++--------- crates/uv/tests/it/python_find.rs | 82 ++++++++++++++-------------- crates/uv/tests/it/python_install.rs | 12 ++-- crates/uv/tests/it/python_list.rs | 4 +- crates/uv/tests/it/run.rs | 40 +++++++------- crates/uv/tests/it/sync.rs | 28 ++++------ crates/uv/tests/it/tool_list.rs | 6 +- 8 files changed, 121 insertions(+), 116 deletions(-) diff --git a/crates/uv/tests/it/common/mod.rs b/crates/uv/tests/it/common/mod.rs index 2dc72fa1d..d4a73f953 100644 --- a/crates/uv/tests/it/common/mod.rs +++ b/crates/uv/tests/it/common/mod.rs @@ -210,12 +210,14 @@ impl TestContext { pub fn with_filtered_python_names(mut self) -> Self { if cfg!(windows) { self.filters - .push((r"python\.exe".to_string(), "python".to_string())); + .push((r"python\.exe".to_string(), "[PYTHON]".to_string())); } else { self.filters - .push((r"python\d.\d\d".to_string(), "python".to_string())); + .push((r"python\d.\d\d".to_string(), "[PYTHON]".to_string())); self.filters - .push((r"python\d".to_string(), "python".to_string())); + .push((r"python\d".to_string(), "[PYTHON]".to_string())); + self.filters + .push((r"/python".to_string(), "/[PYTHON]".to_string())); } self } @@ -224,6 +226,13 @@ impl TestContext { /// `Scripts` on Windows and `bin` on Unix. #[must_use] pub fn with_filtered_virtualenv_bin(mut self) -> Self { + self.filters.push(( + format!( + r"[\\/]{}[\\/]", + venv_bin_path(PathBuf::new()).to_string_lossy() + ), + "/[BIN]/".to_string(), + )); self.filters.push(( format!(r"[\\/]{}", venv_bin_path(PathBuf::new()).to_string_lossy()), "/[BIN]".to_string(), diff --git a/crates/uv/tests/it/pip_sync.rs b/crates/uv/tests/it/pip_sync.rs index 43cbc26c7..537c5dff2 100644 --- a/crates/uv/tests/it/pip_sync.rs +++ b/crates/uv/tests/it/pip_sync.rs @@ -43,15 +43,15 @@ fn missing_venv() -> Result<()> { requirements.write_str("anyio")?; fs::remove_dir_all(&context.venv)?; - uv_snapshot!(context.filters(), context.pip_sync().arg("requirements.txt"), @r###" + uv_snapshot!(context.filters(), context.pip_sync().arg("requirements.txt"), @r" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- - error: Failed to inspect Python interpreter from active virtual environment at `.venv/[BIN]/python` - Caused by: Python interpreter not found at `[VENV]/[BIN]/python` - "###); + error: Failed to inspect Python interpreter from active virtual environment at `.venv/[BIN]/[PYTHON]` + Caused by: Python interpreter not found at `[VENV]/[BIN]/[PYTHON]` + "); assert!(predicates::path::missing().eval(&context.venv)); @@ -5191,18 +5191,18 @@ fn target_built_distribution() -> Result<()> { uv_snapshot!(context.filters(), context.pip_sync() .arg("requirements.in") .arg("--target") - .arg("target"), @r###" + .arg("target"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - Using CPython 3.12.[X] interpreter at: .venv/[BIN]/python + Using CPython 3.12.[X] interpreter at: .venv/[BIN]/[PYTHON] Resolved 1 package in [TIME] Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); // Ensure that the package is present in the target directory. assert!(context.temp_dir.child("target").child("iniconfig").is_dir()); @@ -5227,20 +5227,20 @@ fn target_built_distribution() -> Result<()> { uv_snapshot!(context.filters(), context.pip_sync() .arg("requirements.in") .arg("--target") - .arg("target"), @r###" + .arg("target"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - Using CPython 3.12.[X] interpreter at: .venv/[BIN]/python + Using CPython 3.12.[X] interpreter at: .venv/[BIN]/[PYTHON] Resolved 1 package in [TIME] Prepared 1 package in [TIME] Uninstalled 1 package in [TIME] Installed 1 package in [TIME] - iniconfig==2.0.0 + iniconfig==1.1.1 - "###); + "); // Remove it, and replace with `flask`, which includes a binary. let requirements_in = context.temp_dir.child("requirements.in"); @@ -5249,20 +5249,20 @@ fn target_built_distribution() -> Result<()> { uv_snapshot!(context.filters(), context.pip_sync() .arg("requirements.in") .arg("--target") - .arg("target"), @r###" + .arg("target"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - Using CPython 3.12.[X] interpreter at: .venv/[BIN]/python + Using CPython 3.12.[X] interpreter at: .venv/[BIN]/[PYTHON] Resolved 1 package in [TIME] Prepared 1 package in [TIME] Uninstalled 1 package in [TIME] Installed 1 package in [TIME] + flask==3.0.2 - iniconfig==1.1.1 - "###); + "); // Ensure that the binary is present in the target directory. assert!( context @@ -5293,18 +5293,18 @@ fn target_source_distribution() -> Result<()> { .arg("--no-binary") .arg("iniconfig") .arg("--target") - .arg("target"), @r###" + .arg("target"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - Using CPython 3.12.[X] interpreter at: .venv/[BIN]/python + Using CPython 3.12.[X] interpreter at: .venv/[BIN]/[PYTHON] Resolved 1 package in [TIME] Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); // Ensure that the build requirements are not present in the target directory. assert!(!context.temp_dir.child("target").child("hatchling").is_dir()); @@ -5364,18 +5364,18 @@ fn target_no_build_isolation() -> Result<()> { .arg("--no-binary") .arg("wheel") .arg("--target") - .arg("target"), @r###" + .arg("target"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - Using CPython 3.12.[X] interpreter at: .venv/[BIN]/python + Using CPython 3.12.[X] interpreter at: .venv/[BIN]/[PYTHON] Resolved 1 package in [TIME] Prepared 1 package in [TIME] Installed 1 package in [TIME] + wheel==0.43.0 - "###); + "); // Ensure that the build requirements are not present in the target directory. assert!(!context.temp_dir.child("target").child("flit_core").is_dir()); @@ -5447,18 +5447,18 @@ fn prefix() -> Result<()> { uv_snapshot!(context.filters(), context.pip_sync() .arg("requirements.in") .arg("--prefix") - .arg(prefix.path()), @r###" + .arg(prefix.path()), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - Using CPython 3.12.[X] interpreter at: .venv/[BIN]/python + Using CPython 3.12.[X] interpreter at: .venv/[BIN]/[PYTHON] Resolved 1 package in [TIME] Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); // Ensure that we can't import the package. context.assert_command("import iniconfig").failure(); @@ -5483,20 +5483,20 @@ fn prefix() -> Result<()> { uv_snapshot!(context.filters(), context.pip_sync() .arg("requirements.in") .arg("--prefix") - .arg(prefix.path()), @r###" + .arg(prefix.path()), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - Using CPython 3.12.[X] interpreter at: .venv/[BIN]/python + Using CPython 3.12.[X] interpreter at: .venv/[BIN]/[PYTHON] Resolved 1 package in [TIME] Prepared 1 package in [TIME] Uninstalled 1 package in [TIME] Installed 1 package in [TIME] - iniconfig==2.0.0 + iniconfig==1.1.1 - "###); + "); Ok(()) } diff --git a/crates/uv/tests/it/python_find.rs b/crates/uv/tests/it/python_find.rs index b8b42d61b..49e60c068 100644 --- a/crates/uv/tests/it/python_find.rs +++ b/crates/uv/tests/it/python_find.rs @@ -425,25 +425,25 @@ fn python_find_venv() { // is super annoying and requires some changes to how we represent working directories in the // test context to resolve. #[cfg(not(windows))] - uv_snapshot!(context.filters(), context.python_find(), @r###" + uv_snapshot!(context.filters(), context.python_find(), @r" success: true exit_code: 0 ----- stdout ----- - [VENV]/[BIN]/python + [VENV]/[BIN]/[PYTHON] ----- stderr ----- - "###); + "); // Even if the `VIRTUAL_ENV` is not set (the test context includes this by default) #[cfg(not(windows))] - uv_snapshot!(context.filters(), context.python_find().env_remove(EnvVars::VIRTUAL_ENV), @r###" + uv_snapshot!(context.filters(), context.python_find().env_remove(EnvVars::VIRTUAL_ENV), @r" success: true exit_code: 0 ----- stdout ----- - [VENV]/[BIN]/python + [VENV]/[BIN]/[PYTHON] ----- stderr ----- - "###); + "); let child_dir = context.temp_dir.child("child"); child_dir.create_dir_all().unwrap(); @@ -485,14 +485,14 @@ fn python_find_venv() { // We should find virtual environments from a child directory #[cfg(not(windows))] - uv_snapshot!(context.filters(), context.python_find().current_dir(&child_dir).env_remove(EnvVars::VIRTUAL_ENV), @r###" + uv_snapshot!(context.filters(), context.python_find().current_dir(&child_dir).env_remove(EnvVars::VIRTUAL_ENV), @r" success: true exit_code: 0 ----- stdout ----- - [VENV]/[BIN]/python + [VENV]/[BIN]/[PYTHON] ----- stderr ----- - "###); + "); // A virtual environment in the child directory takes precedence over the parent uv_snapshot!(context.filters(), context.venv().arg("--python").arg("3.11").arg("-q").current_dir(&child_dir), @r###" @@ -504,14 +504,14 @@ fn python_find_venv() { "###); #[cfg(not(windows))] - uv_snapshot!(context.filters(), context.python_find().current_dir(&child_dir).env_remove(EnvVars::VIRTUAL_ENV), @r###" + uv_snapshot!(context.filters(), context.python_find().current_dir(&child_dir).env_remove(EnvVars::VIRTUAL_ENV), @r" success: true exit_code: 0 ----- stdout ----- - [TEMP_DIR]/child/.venv/[BIN]/python + [TEMP_DIR]/child/.venv/[BIN]/[PYTHON] ----- stderr ----- - "###); + "); // But if we delete the parent virtual environment fs_err::remove_dir_all(context.temp_dir.child(".venv")).unwrap(); @@ -528,36 +528,36 @@ fn python_find_venv() { // Unless, it is requested by path #[cfg(not(windows))] - uv_snapshot!(context.filters(), context.python_find().arg("child/.venv"), @r###" + uv_snapshot!(context.filters(), context.python_find().arg("child/.venv"), @r" success: true exit_code: 0 ----- stdout ----- - [TEMP_DIR]/child/.venv/[BIN]/python + [TEMP_DIR]/child/.venv/[BIN]/[PYTHON] ----- stderr ----- - "###); + "); // Or activated via `VIRTUAL_ENV` #[cfg(not(windows))] - uv_snapshot!(context.filters(), context.python_find().env(EnvVars::VIRTUAL_ENV, child_dir.join(".venv").as_os_str()), @r###" + uv_snapshot!(context.filters(), context.python_find().env(EnvVars::VIRTUAL_ENV, child_dir.join(".venv").as_os_str()), @r" success: true exit_code: 0 ----- stdout ----- - [TEMP_DIR]/child/.venv/[BIN]/python + [TEMP_DIR]/child/.venv/[BIN]/[PYTHON] ----- stderr ----- - "###); + "); // 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()), @r###" + uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_TEST_PYTHON_PATH, child_dir.join(".venv").join("bin").as_os_str()), @r" success: true exit_code: 0 ----- stdout ----- - [TEMP_DIR]/child/.venv/[BIN]/python + [TEMP_DIR]/child/.venv/[BIN]/[PYTHON] ----- stderr ----- - "###); + "); // This holds even if there are other directories before it in the path, as long as they do // not contain a Python executable @@ -569,14 +569,14 @@ fn python_find_venv() { ]) .unwrap(); - uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_TEST_PYTHON_PATH, path.as_os_str()), @r###" + uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_TEST_PYTHON_PATH, path.as_os_str()), @r" success: true exit_code: 0 ----- stdout ----- - [TEMP_DIR]/child/.venv/[BIN]/python + [TEMP_DIR]/child/.venv/[BIN]/[PYTHON] ----- stderr ----- - "###); + "); } // But, if there's an executable _before_ the virtual environment — we prefer that @@ -678,33 +678,32 @@ fn python_find_unsupported_version() { #[test] fn python_find_venv_invalid() { let context: TestContext = TestContext::new("3.12") - // Enable additional filters for Windows compatibility - .with_filtered_exe_suffix() .with_filtered_python_names() - .with_filtered_virtualenv_bin(); + .with_filtered_virtualenv_bin() + .with_filtered_exe_suffix(); // We find the virtual environment - uv_snapshot!(context.filters(), context.python_find().env(EnvVars::VIRTUAL_ENV, context.venv.as_os_str()), @r###" + uv_snapshot!(context.filters(), context.python_find().env(EnvVars::VIRTUAL_ENV, context.venv.as_os_str()), @r" success: true exit_code: 0 ----- stdout ----- - [VENV]/[BIN]/python + [VENV]/[BIN]/[PYTHON] ----- stderr ----- - "###); + "); // If the binaries are missing from a virtual environment, we fail fs_err::remove_dir_all(venv_bin_path(&context.venv)).unwrap(); - uv_snapshot!(context.filters(), context.python_find().env(EnvVars::VIRTUAL_ENV, context.venv.as_os_str()), @r###" + uv_snapshot!(context.filters(), context.python_find().env(EnvVars::VIRTUAL_ENV, context.venv.as_os_str()), @r" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- - error: Failed to inspect Python interpreter from active virtual environment at `.venv/[BIN]/python` - Caused by: Python interpreter not found at `[VENV]/[BIN]/python` - "###); + error: Failed to inspect Python interpreter from active virtual environment at `.venv/[BIN]/[PYTHON]` + Caused by: Python interpreter not found at `[VENV]/[BIN]/[PYTHON]` + "); // Unless the virtual environment is not active uv_snapshot!(context.filters(), context.python_find(), @r###" @@ -783,9 +782,10 @@ fn python_required_python_major_minor() { #[test] fn python_find_script() { let context = TestContext::new("3.13") - .with_filtered_exe_suffix() .with_filtered_virtualenv_bin() - .with_filtered_python_names(); + .with_filtered_python_names() + .with_filtered_exe_suffix(); + let filters = context .filters() .into_iter() @@ -819,7 +819,7 @@ fn python_find_script() { success: true exit_code: 0 ----- stdout ----- - [CACHE_DIR]/environments-v2/[HASHEDNAME]/[BIN]/python + [CACHE_DIR]/environments-v2/[HASHEDNAME]/[BIN]/[PYTHON] ----- stderr ----- "); @@ -828,9 +828,9 @@ fn python_find_script() { #[test] fn python_find_script_no_environment() { let context = TestContext::new("3.13") - .with_filtered_exe_suffix() .with_filtered_virtualenv_bin() - .with_filtered_python_names(); + .with_filtered_python_names() + .with_filtered_exe_suffix(); let script = context.temp_dir.child("foo.py"); @@ -846,7 +846,7 @@ fn python_find_script_no_environment() { success: true exit_code: 0 ----- stdout ----- - [VENV]/[BIN]/python + [VENV]/[BIN]/[PYTHON] ----- stderr ----- "); @@ -881,9 +881,9 @@ fn python_find_script_python_not_found() { #[test] fn python_find_script_no_such_version() { let context = TestContext::new("3.13") - .with_filtered_exe_suffix() .with_filtered_virtualenv_bin() .with_filtered_python_names() + .with_filtered_exe_suffix() .with_filtered_python_sources(); let filters = context .filters() diff --git a/crates/uv/tests/it/python_install.rs b/crates/uv/tests/it/python_install.rs index 0cb952054..62b3254b8 100644 --- a/crates/uv/tests/it/python_install.rs +++ b/crates/uv/tests/it/python_install.rs @@ -1499,10 +1499,10 @@ fn python_install_patch_dylib() { fn python_install_314() { let context: TestContext = TestContext::new_with_versions(&[]) .with_filtered_python_keys() - .with_filtered_exe_suffix() .with_managed_python_dirs() + .with_filtered_python_install_bin() .with_filtered_python_names() - .with_filtered_python_install_bin(); + .with_filtered_exe_suffix(); // Install 3.14 // For now, this provides test coverage of pre-release handling @@ -1533,7 +1533,7 @@ fn python_install_314() { success: true exit_code: 0 ----- stdout ----- - [TEMP_DIR]/managed/cpython-3.14.0b4-[PLATFORM]/[INSTALL-BIN]/python + [TEMP_DIR]/managed/cpython-3.14.0b4-[PLATFORM]/[INSTALL-BIN]/[PYTHON] ----- stderr ----- "); @@ -1543,7 +1543,7 @@ fn python_install_314() { success: true exit_code: 0 ----- stdout ----- - [TEMP_DIR]/managed/cpython-3.14.0b4-[PLATFORM]/[INSTALL-BIN]/python + [TEMP_DIR]/managed/cpython-3.14.0b4-[PLATFORM]/[INSTALL-BIN]/[PYTHON] ----- stderr ----- "); @@ -1552,7 +1552,7 @@ fn python_install_314() { success: true exit_code: 0 ----- stdout ----- - [TEMP_DIR]/managed/cpython-3.14.0b4-[PLATFORM]/[INSTALL-BIN]/python + [TEMP_DIR]/managed/cpython-3.14.0b4-[PLATFORM]/[INSTALL-BIN]/[PYTHON] ----- stderr ----- "); @@ -1572,7 +1572,7 @@ fn python_install_314() { success: true exit_code: 0 ----- stdout ----- - [TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/[INSTALL-BIN]/python + [TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/[INSTALL-BIN]/[PYTHON] ----- stderr ----- "); diff --git a/crates/uv/tests/it/python_list.rs b/crates/uv/tests/it/python_list.rs index 959ebdd80..11472baec 100644 --- a/crates/uv/tests/it/python_list.rs +++ b/crates/uv/tests/it/python_list.rs @@ -411,8 +411,8 @@ fn python_list_downloads_installed() { let context: TestContext = TestContext::new_with_versions(&[]) .with_filtered_python_keys() - .with_filtered_python_names() .with_filtered_python_install_bin() + .with_filtered_python_names() .with_managed_python_dirs(); // We do not test showing all interpreters — as it differs per platform @@ -450,7 +450,7 @@ fn python_list_downloads_installed() { success: true exit_code: 0 ----- stdout ----- - cpython-3.10.18-[PLATFORM] managed/cpython-3.10.18-[PLATFORM]/[INSTALL-BIN]/python + cpython-3.10.18-[PLATFORM] managed/cpython-3.10.18-[PLATFORM]/[INSTALL-BIN]/[PYTHON] pypy-3.10.16-[PLATFORM] graalpy-3.10.0-[PLATFORM] diff --git a/crates/uv/tests/it/run.rs b/crates/uv/tests/it/run.rs index 98c2adbfe..93420cca0 100644 --- a/crates/uv/tests/it/run.rs +++ b/crates/uv/tests/it/run.rs @@ -2851,11 +2851,11 @@ fn run_no_project() -> Result<()> { init.touch()?; // `run` should run in the context of the project. - uv_snapshot!(context.filters(), context.run().arg("python").arg("-c").arg("import sys; print(sys.executable)"), @r###" + uv_snapshot!(context.filters(), context.run().arg("python").arg("-c").arg("import sys; print(sys.executable)"), @r" success: true exit_code: 0 ----- stdout ----- - [VENV]/[BIN]/python + [VENV]/[BIN]/[PYTHON] ----- stderr ----- Resolved 6 packages in [TIME] @@ -2865,50 +2865,50 @@ fn run_no_project() -> Result<()> { + foo==1.0.0 (from file://[TEMP_DIR]/) + idna==3.6 + sniffio==1.3.1 - "###); + "); // `run --no-project` should not (but it should still run in the same environment, as it would // if there were no project at all). - uv_snapshot!(context.filters(), context.run().arg("--no-project").arg("python").arg("-c").arg("import sys; print(sys.executable)"), @r###" + uv_snapshot!(context.filters(), context.run().arg("--no-project").arg("python").arg("-c").arg("import sys; print(sys.executable)"), @r" success: true exit_code: 0 ----- stdout ----- - [VENV]/[BIN]/python + [VENV]/[BIN]/[PYTHON] ----- stderr ----- - "###); + "); // `run --no-project --isolated` should run in an entirely isolated environment. - uv_snapshot!(context.filters(), context.run().arg("--no-project").arg("--isolated").arg("python").arg("-c").arg("import sys; print(sys.executable)"), @r###" + uv_snapshot!(context.filters(), context.run().arg("--no-project").arg("--isolated").arg("python").arg("-c").arg("import sys; print(sys.executable)"), @r" success: true exit_code: 0 ----- stdout ----- - [CACHE_DIR]/builds-v0/[TMP]/python + [CACHE_DIR]/builds-v0/[TMP]/[PYTHON] ----- stderr ----- - "###); + "); // `run --no-project` should not (but it should still run in the same environment, as it would // if there were no project at all). - uv_snapshot!(context.filters(), context.run().arg("--no-project").arg("python").arg("-c").arg("import sys; print(sys.executable)"), @r###" + uv_snapshot!(context.filters(), context.run().arg("--no-project").arg("python").arg("-c").arg("import sys; print(sys.executable)"), @r" success: true exit_code: 0 ----- stdout ----- - [VENV]/[BIN]/python + [VENV]/[BIN]/[PYTHON] ----- stderr ----- - "###); + "); // `run --no-project --locked` should fail. - uv_snapshot!(context.filters(), context.run().arg("--no-project").arg("--locked").arg("python").arg("-c").arg("import sys; print(sys.executable)"), @r###" + uv_snapshot!(context.filters(), context.run().arg("--no-project").arg("--locked").arg("python").arg("-c").arg("import sys; print(sys.executable)"), @r" success: true exit_code: 0 ----- stdout ----- - [VENV]/[BIN]/python + [VENV]/[BIN]/[PYTHON] ----- stderr ----- warning: `--locked` has no effect when used alongside `--no-project` - "###); + "); Ok(()) } @@ -3092,14 +3092,14 @@ fn run_project_toml_error() -> Result<()> { "###); // `run --no-project` should not - uv_snapshot!(context.filters(), context.run().arg("--no-project").arg("python").arg("-c").arg("import sys; print(sys.executable)"), @r###" + uv_snapshot!(context.filters(), context.run().arg("--no-project").arg("python").arg("-c").arg("import sys; print(sys.executable)"), @r" success: true exit_code: 0 ----- stdout ----- - [VENV]/[BIN]/python + [VENV]/[BIN]/[PYTHON] ----- stderr ----- - "###); + "); Ok(()) } @@ -3691,7 +3691,7 @@ fn run_linked_environment_path() -> Result<()> { exit_code: 0 ----- stdout ----- [TEMP_DIR]/target - [TEMP_DIR]/target/[BIN]/python + [TEMP_DIR]/target/[BIN]/[PYTHON] ----- stderr ----- Resolved 8 packages in [TIME] @@ -3705,7 +3705,7 @@ fn run_linked_environment_path() -> Result<()> { }, { assert_snapshot!( black_entrypoint, @r##" - #![TEMP_DIR]/target/[BIN]/python + #![TEMP_DIR]/target/[BIN]/[PYTHON] # -*- coding: utf-8 -*- import sys from black import patched_main diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index 7063035f9..9fecd50b0 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -306,7 +306,7 @@ fn sync_json() -> Result<()> { "environment": { "path": "[VENV]/", "python": { - "path": "[VENV]/[BIN]/python", + "path": "[VENV]/[BIN]/[PYTHON]", "version": "3.12.[X]", "implementation": "cpython" } @@ -350,7 +350,7 @@ fn sync_json() -> Result<()> { "environment": { "path": "[VENV]/", "python": { - "path": "[VENV]/[BIN]/python", + "path": "[VENV]/[BIN]/[PYTHON]", "version": "3.12.[X]", "implementation": "cpython" } @@ -389,7 +389,7 @@ fn sync_json() -> Result<()> { "environment": { "path": "[VENV]/", "python": { - "path": "[VENV]/[BIN]/python", + "path": "[VENV]/[BIN]/[PYTHON]", "version": "3.12.[X]", "implementation": "cpython" } @@ -475,7 +475,7 @@ fn sync_dry_json() -> Result<()> { "environment": { "path": "[VENV]/", "python": { - "path": "[VENV]/[BIN]/python", + "path": "[VENV]/[BIN]/[PYTHON]", "version": "3.12.[X]", "implementation": "cpython" } @@ -4884,14 +4884,10 @@ fn sync_active_script_environment_json() -> Result<()> { let filters = context .filters() .into_iter() - .chain(vec![ - ( - r"environments-v2/script-[a-z0-9]+", - "environments-v2/script-[HASH]", - ), - ("bin/python3", "[PYTHON]"), - ("Scripts/python.exe", "[PYTHON]"), - ]) + .chain(vec![( + r"environments-v2/script-[a-z0-9]+", + "environments-v2/script-[HASH]", + )]) .collect::>(); // Running `uv sync --script` with `VIRTUAL_ENV` should warn @@ -4914,7 +4910,7 @@ fn sync_active_script_environment_json() -> Result<()> { "environment": { "path": "[CACHE_DIR]/environments-v2/script-[HASH]", "python": { - "path": "[CACHE_DIR]/environments-v2/script-[HASH]/[BIN]/python", + "path": "[CACHE_DIR]/environments-v2/script-[HASH]/[BIN]/[PYTHON]", "version": "3.11.[X]", "implementation": "cpython" } @@ -4960,7 +4956,7 @@ fn sync_active_script_environment_json() -> Result<()> { "environment": { "path": "[TEMP_DIR]/foo", "python": { - "path": "[TEMP_DIR]/foo/[BIN]/python", + "path": "[TEMP_DIR]/foo/[BIN]/[PYTHON]", "version": "3.11.[X]", "implementation": "cpython" } @@ -5019,7 +5015,7 @@ fn sync_active_script_environment_json() -> Result<()> { "environment": { "path": "[TEMP_DIR]/foo", "python": { - "path": "[TEMP_DIR]/foo/[BIN]/python", + "path": "[TEMP_DIR]/foo/[BIN]/[PYTHON]", "version": "3.12.[X]", "implementation": "cpython" } @@ -6558,7 +6554,7 @@ fn sync_invalid_environment() -> Result<()> { ----- stdout ----- ----- stderr ----- - warning: Ignoring existing virtual environment linked to non-existent Python interpreter: .venv/[BIN]/python -> python + warning: Ignoring existing virtual environment linked to non-existent Python interpreter: .venv/[BIN]/[PYTHON] -> python Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] Removed virtual environment at: .venv Creating virtual environment at: .venv diff --git a/crates/uv/tests/it/tool_list.rs b/crates/uv/tests/it/tool_list.rs index 93dd5756e..9268118ca 100644 --- a/crates/uv/tests/it/tool_list.rs +++ b/crates/uv/tests/it/tool_list.rs @@ -180,7 +180,7 @@ fn tool_list_bad_environment() -> Result<()> { .tool_list() .env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str()) .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), - @r###" + @r" success: true exit_code: 0 ----- stdout ----- @@ -188,8 +188,8 @@ fn tool_list_bad_environment() -> Result<()> { - ruff ----- stderr ----- - warning: Invalid environment at `tools/black`: missing Python executable at `tools/black/[BIN]/python` (run `uv tool install black --reinstall` to reinstall) - "### + warning: Invalid environment at `tools/black`: missing Python executable at `tools/black/[BIN]/[PYTHON]` (run `uv tool install black --reinstall` to reinstall) + " ); Ok(()) From eaff96e5dce946ec91ee632025f4dbd8ae67f173 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 16 Jul 2025 09:06:06 -0500 Subject: [PATCH 226/349] Sync latest Python releases (#14643) Automated update for Python releases. Co-authored-by: zanieb <2586601+zanieb@users.noreply.github.com> --- crates/uv-python/download-metadata.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/uv-python/download-metadata.json b/crates/uv-python/download-metadata.json index 8c7ffec4c..540a3c8a0 100644 --- a/crates/uv-python/download-metadata.json +++ b/crates/uv-python/download-metadata.json @@ -35771,8 +35771,8 @@ "minor": 11, "patch": 0, "prerelease": "", - "url": "https://github.com/oracle/graalpython/releases/download/graal-24.2.1/graalpy-24.2.1-macos-aarch64.tar.gz", - "sha256": "61e11d5176d5bb709b919979ef3525f4db1e39c404b59aa54d887f56bf8fab44", + "url": "https://github.com/oracle/graalpython/releases/download/graal-24.2.2/graalpy-24.2.2-macos-aarch64.tar.gz", + "sha256": "f4a2ae01bae0fa53ec0d19f86d73c6dcc2a162d245552030183b84bfdd8f7635", "variant": null }, "graalpy-3.11.0-darwin-x86_64-none": { @@ -35787,8 +35787,8 @@ "minor": 11, "patch": 0, "prerelease": "", - "url": "https://github.com/oracle/graalpython/releases/download/graal-24.2.1/graalpy-24.2.1-macos-amd64.tar.gz", - "sha256": "4bc42b36117c9ab09c4f411ec5a7a85ed58521dd20b529d971bb0ed3d0b7c363", + "url": "https://github.com/oracle/graalpython/releases/download/graal-24.2.2/graalpy-24.2.2-macos-amd64.tar.gz", + "sha256": "2f4d5e7dbdf90e38778dfcb8ca3e1ec7eee257ef726b1937d5bc91b54cdddf9b", "variant": null }, "graalpy-3.11.0-linux-aarch64-gnu": { @@ -35803,8 +35803,8 @@ "minor": 11, "patch": 0, "prerelease": "", - "url": "https://github.com/oracle/graalpython/releases/download/graal-24.2.1/graalpy-24.2.1-linux-aarch64.tar.gz", - "sha256": "2a80800a76ee6b737d6458ba9ab30ce386dfdd5b2b2bec3ee6bc51fd8e51e7c2", + "url": "https://github.com/oracle/graalpython/releases/download/graal-24.2.2/graalpy-24.2.2-linux-aarch64.tar.gz", + "sha256": "c9be459ab9479892b88dd63f8f88cbc7b1067f4cb27ff17f4761b36de6bd73af", "variant": null }, "graalpy-3.11.0-linux-x86_64-gnu": { @@ -35819,8 +35819,8 @@ "minor": 11, "patch": 0, "prerelease": "", - "url": "https://github.com/oracle/graalpython/releases/download/graal-24.2.1/graalpy-24.2.1-linux-amd64.tar.gz", - "sha256": "55872af24819cb99efa2338db057aeda0c8f9dd412a4a6f5ea19b256ee82fd9e", + "url": "https://github.com/oracle/graalpython/releases/download/graal-24.2.2/graalpy-24.2.2-linux-amd64.tar.gz", + "sha256": "604b7abf6c58038a30866e52da43818af63bcd97909af8b1a96523c7f0e01414", "variant": null }, "graalpy-3.11.0-windows-x86_64-none": { @@ -35835,8 +35835,8 @@ "minor": 11, "patch": 0, "prerelease": "", - "url": "https://github.com/oracle/graalpython/releases/download/graal-24.2.1/graalpy-24.2.1-windows-amd64.zip", - "sha256": "bad923fb64fa2fc71bb424818aac8dcfe0cc9554abef5235d7c08e597ed778ae", + "url": "https://github.com/oracle/graalpython/releases/download/graal-24.2.2/graalpy-24.2.2-windows-amd64.zip", + "sha256": "9606134284d4d95b2f9d69c3087cd3e9e488f46355b419f5e66588a3281df6a3", "variant": null }, "graalpy-3.10.0-darwin-aarch64-none": { From 1f49fbd53cdef47582cfdd45f31f077fbf75210d Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 16 Jul 2025 09:17:01 -0500 Subject: [PATCH 227/349] Display `sys.executable` names in check system jobs (#14656) Cherry-picked from https://github.com/astral-sh/uv/pull/14652 This is useful for debugging --- scripts/check_system_python.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/check_system_python.py b/scripts/check_system_python.py index 565518e50..fbfc5557e 100755 --- a/scripts/check_system_python.py +++ b/scripts/check_system_python.py @@ -24,7 +24,7 @@ def install_package(*, uv: str, package: str): check=True, ) - logging.info(f"Checking that `{package}` can be imported.") + logging.info(f"Checking that `{package}` can be imported with `{sys.executable}`.") code = subprocess.run( [sys.executable, "-c", f"import {package}"], cwd=temp_dir, @@ -82,7 +82,9 @@ if __name__ == "__main__": ) # Ensure that the package (`pylint`) is installed. - logging.info("Checking that `pylint` is installed.") + logging.info( + f"Checking that `pylint` is installed with `{sys.executable} -m pip`." + ) code = subprocess.run( [sys.executable, "-m", "pip", "show", "pylint"], cwd=temp_dir, From 8b29ec0bfd4d34141bfcfb8f5424cbc737dba0b8 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 16 Jul 2025 09:20:25 -0500 Subject: [PATCH 228/349] Use `astral.sh` instead of `example.com` in `lock_unique_named_index` (#14657) This test flakes a lot, maybe using a different domain will help Closes https://github.com/astral-sh/uv/issues/14542 --- crates/uv/tests/it/lock.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index d5757b6ef..f91870762 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -16412,7 +16412,7 @@ fn lock_unique_named_index() -> Result<()> { [[tool.uv.index]] name = "example" - url = "https://example.com" + url = "https://astral.sh" "#, )?; From 7fece9b90a07ab8479ba9445f90cedc3b08f61a8 Mon Sep 17 00:00:00 2001 From: konsti Date: Wed, 16 Jul 2025 15:21:22 +0100 Subject: [PATCH 229/349] Remove marker from `Edge` (#14649) It seems that this field is unused. --- crates/uv-distribution-types/src/dist_error.rs | 4 ++-- crates/uv-distribution-types/src/resolution.rs | 9 ++++----- crates/uv-resolver/src/lock/export/pylock_toml.rs | 2 +- crates/uv-resolver/src/lock/installable.rs | 13 ++++++------- crates/uv-resolver/src/resolution/output.rs | 13 ++++--------- 5 files changed, 17 insertions(+), 24 deletions(-) diff --git a/crates/uv-distribution-types/src/dist_error.rs b/crates/uv-distribution-types/src/dist_error.rs index a452ce663..d2cfee16d 100644 --- a/crates/uv-distribution-types/src/dist_error.rs +++ b/crates/uv-distribution-types/src/dist_error.rs @@ -131,11 +131,11 @@ impl DerivationChain { )); let target = edge.source(); let extra = match edge.weight() { - Edge::Optional(extra, ..) => Some(extra.clone()), + Edge::Optional(extra) => Some(extra.clone()), _ => None, }; let group = match edge.weight() { - Edge::Dev(group, ..) => Some(group.clone()), + Edge::Dev(group) => Some(group.clone()), _ => None, }; queue.push_back((target, extra, group, path)); diff --git a/crates/uv-distribution-types/src/resolution.rs b/crates/uv-distribution-types/src/resolution.rs index 5ff34adf5..e690b8693 100644 --- a/crates/uv-distribution-types/src/resolution.rs +++ b/crates/uv-distribution-types/src/resolution.rs @@ -1,6 +1,5 @@ use uv_distribution_filename::DistExtension; use uv_normalize::{ExtraName, GroupName, PackageName}; -use uv_pep508::MarkerTree; use uv_pypi_types::{HashDigest, HashDigests}; use crate::{ @@ -202,12 +201,12 @@ impl Node { } } -/// An edge in the resolution graph, along with the marker that must be satisfied to traverse it. +/// An edge in the resolution graph. #[derive(Debug, Clone)] pub enum Edge { - Prod(MarkerTree), - Optional(ExtraName, MarkerTree), - Dev(GroupName, MarkerTree), + Prod, + Optional(ExtraName), + Dev(GroupName), } impl From<&ResolvedDist> for RequirementSource { diff --git a/crates/uv-resolver/src/lock/export/pylock_toml.rs b/crates/uv-resolver/src/lock/export/pylock_toml.rs index d2c2383a5..8a53fd8f7 100644 --- a/crates/uv-resolver/src/lock/export/pylock_toml.rs +++ b/crates/uv-resolver/src/lock/export/pylock_toml.rs @@ -1152,7 +1152,7 @@ impl<'lock> PylockToml { }; let index = graph.add_node(dist); - graph.add_edge(root, index, Edge::Prod(package.marker)); + graph.add_edge(root, index, Edge::Prod); } Ok(Resolution::new(graph)) diff --git a/crates/uv-resolver/src/lock/installable.rs b/crates/uv-resolver/src/lock/installable.rs index e3cdbf019..4851306da 100644 --- a/crates/uv-resolver/src/lock/installable.rs +++ b/crates/uv-resolver/src/lock/installable.rs @@ -13,7 +13,6 @@ use uv_configuration::ExtrasSpecificationWithDefaults; use uv_configuration::{BuildOptions, DependencyGroupsWithDefaults, InstallOptions}; use uv_distribution_types::{Edge, Node, Resolution, ResolvedDist}; use uv_normalize::{ExtraName, GroupName, PackageName}; -use uv_pep508::MarkerTree; use uv_platform_tags::Tags; use uv_pypi_types::ResolverMarkerEnvironment; @@ -113,7 +112,7 @@ pub trait Installable<'lock> { inverse.insert(&dist.id, index); // Add an edge from the root. - petgraph.add_edge(root, index, Edge::Prod(MarkerTree::TRUE)); + petgraph.add_edge(root, index, Edge::Prod); // Push the package onto the queue. roots.push((dist, index)); @@ -189,7 +188,7 @@ pub trait Installable<'lock> { // a specific marker environment and set of extras/groups. // So at this point, we know the extras/groups have been // satisfied, so we can safely drop the conflict marker. - Edge::Dev(group.clone(), dep.complexified_marker.pep508()), + Edge::Dev(group.clone()), ); // Push its dependencies on the queue. @@ -231,7 +230,7 @@ pub trait Installable<'lock> { inverse.insert(&dist.id, index); // Add the edge. - petgraph.add_edge(root, index, Edge::Prod(dependency.marker)); + petgraph.add_edge(root, index, Edge::Prod); // Push its dependencies on the queue. if seen.insert((&dist.id, None)) { @@ -300,7 +299,7 @@ pub trait Installable<'lock> { }; // Add the edge. - petgraph.add_edge(root, index, Edge::Dev(group.clone(), dependency.marker)); + petgraph.add_edge(root, index, Edge::Dev(group.clone())); // Push its dependencies on the queue. if seen.insert((&dist.id, None)) { @@ -484,9 +483,9 @@ pub trait Installable<'lock> { index, dep_index, if let Some(extra) = extra { - Edge::Optional(extra.clone(), dep.complexified_marker.pep508()) + Edge::Optional(extra.clone()) } else { - Edge::Prod(dep.complexified_marker.pep508()) + Edge::Prod }, ); diff --git a/crates/uv-resolver/src/resolution/output.rs b/crates/uv-resolver/src/resolution/output.rs index 928b9c605..dd2b3388f 100644 --- a/crates/uv-resolver/src/resolution/output.rs +++ b/crates/uv-resolver/src/resolution/output.rs @@ -894,16 +894,11 @@ impl From for uv_distribution_types::Resolution { // Re-add the edges to the reduced graph. for edge in graph.edge_indices() { let (source, target) = graph.edge_endpoints(edge).unwrap(); - // OK to ignore conflicting marker because we've asserted - // above that we aren't in universal mode. If we aren't in - // universal mode, then there can be no conflicts since - // conflicts imply forks and forks imply universal mode. - let marker = graph[edge].pep508(); match (&graph[source], &graph[target]) { (ResolutionGraphNode::Root, ResolutionGraphNode::Dist(target_dist)) => { let target = inverse[&target_dist.name()]; - transformed.update_edge(root, target, Edge::Prod(marker)); + transformed.update_edge(root, target, Edge::Prod); } ( ResolutionGraphNode::Dist(source_dist), @@ -913,11 +908,11 @@ impl From for uv_distribution_types::Resolution { let target = inverse[&target_dist.name()]; let edge = if let Some(extra) = source_dist.extra.as_ref() { - Edge::Optional(extra.clone(), marker) + Edge::Optional(extra.clone()) } else if let Some(dev) = source_dist.dev.as_ref() { - Edge::Dev(dev.clone(), marker) + Edge::Dev(dev.clone()) } else { - Edge::Prod(marker) + Edge::Prod }; transformed.add_edge(source, target, edge); From 052a74c45110d5ddad9943ff33c2c3c332b96e0f Mon Sep 17 00:00:00 2001 From: konsti Date: Wed, 16 Jul 2025 15:56:32 +0100 Subject: [PATCH 230/349] Fix doctests (#14658) `cargo nextest run` doesn't run them, but `cargo insta test --test-runner nextest` does, which surfaced those failures. --- crates/uv-pep508/src/lib.rs | 2 +- crates/uv-requirements-txt/src/shquote.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/uv-pep508/src/lib.rs b/crates/uv-pep508/src/lib.rs index e2945743b..f63d46206 100644 --- a/crates/uv-pep508/src/lib.rs +++ b/crates/uv-pep508/src/lib.rs @@ -11,7 +11,7 @@ //! let marker = r#"requests [security,tests] >= 2.8.1, == 2.8.* ; python_version > "3.8""#; //! let dependency_specification = Requirement::::from_str(marker).unwrap(); //! assert_eq!(dependency_specification.name.as_ref(), "requests"); -//! assert_eq!(dependency_specification.extras, vec![ExtraName::from_str("security").unwrap(), ExtraName::from_str("tests").unwrap()]); +//! assert_eq!(dependency_specification.extras, vec![ExtraName::from_str("security").unwrap(), ExtraName::from_str("tests").unwrap()].into()); //! ``` #![warn(missing_docs)] diff --git a/crates/uv-requirements-txt/src/shquote.rs b/crates/uv-requirements-txt/src/shquote.rs index d30b4bc5b..180a62496 100644 --- a/crates/uv-requirements-txt/src/shquote.rs +++ b/crates/uv-requirements-txt/src/shquote.rs @@ -146,8 +146,8 @@ fn unquote_open_escape(acc: &mut String, cursor: &mut std::iter::Enumerate Result, UnquoteError> { // If the string does not contain any single-quotes, double-quotes, or escape sequences, it From 7cdc1f62ee9e1dbf6b9cbb9967c3e7e75813bd99 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Wed, 16 Jul 2025 12:02:29 -0400 Subject: [PATCH 231/349] Suggest `uv cache clean` prior to `--reinstall` (#14659) ## Summary Closes https://github.com/astral-sh/uv/issues/14479. --- docs/concepts/cache.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/concepts/cache.md b/docs/concepts/cache.md index 6610ccb55..189465ac4 100644 --- a/docs/concepts/cache.md +++ b/docs/concepts/cache.md @@ -19,12 +19,17 @@ The specifics of uv's caching semantics vary based on the nature of the dependen If you're running into caching issues, uv includes a few escape hatches: +- To clear the cache entirely, run `uv cache clean`. To clear the cache for a specific package, run + `uv cache clean `. For example, `uv cache clean ruff` will clear the cache for the + `ruff` package. - To force uv to revalidate cached data for all dependencies, pass `--refresh` to any command (e.g., `uv sync --refresh` or `uv pip install --refresh ...`). - To force uv to revalidate cached data for a specific dependency pass `--refresh-package` to any - command (e.g., `uv sync --refresh-package flask` or `uv pip install --refresh-package flask ...`). + command (e.g., `uv sync --refresh-package ruff` or `uv pip install --refresh-package ruff ...`). - To force uv to ignore existing installed versions, pass `--reinstall` to any installation command - (e.g., `uv sync --reinstall` or `uv pip install --reinstall ...`). + (e.g., `uv sync --reinstall` or `uv pip install --reinstall ...`). (Consider running + `uv cache clean ` first, to ensure that the cache is cleared prior to + reinstallation.) As a special case, uv will always rebuild and reinstall any local directory dependencies passed explicitly on the command-line (e.g., `uv pip install .`). From a8bb7be52b15ac0b8bced1d58b4044191491ecce Mon Sep 17 00:00:00 2001 From: Geoffrey Thomas Date: Wed, 16 Jul 2025 21:39:21 -0400 Subject: [PATCH 232/349] windows_exception: Improve async signal safety (#14619) It's not as bad as I feared to bypass libsys's stderr. (There's still a lock in libsys's backtrace, which might also not be too bad to bypass.) --- Cargo.lock | 2 + Cargo.toml | 3 +- crates/uv/Cargo.toml | 2 + crates/uv/src/windows_exception.rs | 299 +++++++++++++++++++++++------ 4 files changed, 247 insertions(+), 59 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c43f4872d..3ff7ad6d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4637,6 +4637,7 @@ version = "0.7.21" dependencies = [ "anstream", "anyhow", + "arrayvec", "assert_cmd", "assert_fs", "axoupdater", @@ -4735,6 +4736,7 @@ dependencies = [ "which", "whoami", "windows 0.59.0", + "windows-result 0.3.4", "wiremock", "zip", ] diff --git a/Cargo.toml b/Cargo.toml index 752955223..2c32ce8d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,6 +75,7 @@ uv-workspace = { path = "crates/uv-workspace" } anstream = { version = "0.6.15" } anyhow = { version = "1.0.89" } arcstr = { version = "1.2.0" } +arrayvec = { version = "0.7.6" } astral-tokio-tar = { version = "0.5.1" } async-channel = { version = "2.3.1" } async-compression = { version = "0.4.12", features = ["bzip2", "gzip", "xz", "zstd"] } @@ -184,7 +185,7 @@ url = { version = "2.5.2", features = ["serde"] } version-ranges = { git = "https://github.com/astral-sh/pubgrub", rev = "06ec5a5f59ffaeb6cf5079c6cb184467da06c9db" } walkdir = { version = "2.5.0" } which = { version = "8.0.0", features = ["regex"] } -windows = { version = "0.59.0", features = ["Win32_System_Kernel", "Win32_System_Diagnostics_Debug", "Win32_Storage_FileSystem"] } +windows = { version = "0.59.0", features = ["Win32_Globalization", "Win32_System_Console", "Win32_System_Kernel", "Win32_System_Diagnostics_Debug", "Win32_Storage_FileSystem"] } windows-core = { version = "0.59.0" } windows-registry = { version = "0.5.0" } windows-result = { version = "0.3.0" } diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml index fe2f2200c..d72035467 100644 --- a/crates/uv/Cargo.toml +++ b/crates/uv/Cargo.toml @@ -107,8 +107,10 @@ which = { workspace = true } zip = { workspace = true } [target.'cfg(target_os = "windows")'.dependencies] +arrayvec = { workspace = true } self-replace = { workspace = true } windows = { workspace = true } +windows-result = { workspace = true } [dev-dependencies] assert_cmd = { version = "2.0.16" } diff --git a/crates/uv/src/windows_exception.rs b/crates/uv/src/windows_exception.rs index e96075f96..2e40e89cc 100644 --- a/crates/uv/src/windows_exception.rs +++ b/crates/uv/src/windows_exception.rs @@ -9,121 +9,304 @@ //! implementation and also displays some minimal information from the exception itself. #![allow(unsafe_code)] -#![allow(clippy::print_stderr)] +// Usually we want fs_err over std::fs, but there's no advantage here, we don't +// report errors encountered while reporting an exception. +#![allow(clippy::disallowed_types)] +use std::fmt::Write; +use std::fs::File; +use std::mem::ManuallyDrop; +use std::os::windows::io::FromRawHandle; + +use arrayvec::ArrayVec; use windows::Win32::{ Foundation, + Globalization::CP_UTF8, + System::Console::{ + CONSOLE_MODE, GetConsoleMode, GetConsoleOutputCP, GetStdHandle, STD_ERROR_HANDLE, + WriteConsoleW, + }, System::Diagnostics::Debug::{ CONTEXT, EXCEPTION_CONTINUE_SEARCH, EXCEPTION_POINTERS, SetUnhandledExceptionFilter, }, }; -fn display_exception_info(name: &str, info: &[usize; 15]) { - match info[0] { - 0 => eprintln!("{name} reading {:#x}", info[1]), - 1 => eprintln!("{name} writing {:#x}", info[1]), - 8 => eprintln!("{name} executing {:#x}", info[1]), - _ => eprintln!("{name} from operation {} at {:#x}", info[0], info[1]), +/// A write target for standard error that can be safely used in an exception handler. +/// +/// The exception handler can be called at any point in the execution of machine code, perhaps +/// halfway through a Rust operation. It needs to be robust to operating with unknown program +/// state, a concept that the UNIX world calls "async signal safety." In particular, we can't +/// write to `std::io::stderr()` because that takes a lock, and we could be called in the middle of +/// code that is holding that lock. +enum ExceptionSafeStderr { + // This is a simplified version of the logic in Rust std::sys::stdio::windows, on the + // assumption that we're only writing strs, not bytes (so we do not need to care about + // incomplete or invalid UTF-8) and we don't care about Windows 7 or every drop of + // performance. + // - If stderr is a non-UTF-8 console, we need to write UTF-16 with WriteConsoleW, and we + // convert with encode_utf16(). + // - If stderr is not a console, we cannot use WriteConsole and must use NtWriteFile, which + // takes (UTF-8) bytes. + // - If stderr is a UTF-8 console, we can do either. std uses NtWriteFile. + // Note that we do not want to close stderr at any point, hence ManuallyDrop. + WriteConsole(Foundation::HANDLE), + NtWriteFile(ManuallyDrop), +} + +impl ExceptionSafeStderr { + fn new() -> Result { + // SAFETY: winapi call, no interesting parameters + let handle = unsafe { GetStdHandle(STD_ERROR_HANDLE) }?; + if handle.is_invalid() { + return Err(windows_result::Error::empty()); + } + let mut mode = CONSOLE_MODE::default(); + // SAFETY: winapi calls, no interesting parameters + if unsafe { + GetConsoleMode(handle, &raw mut mode).is_ok() && GetConsoleOutputCP() != CP_UTF8 + } { + Ok(Self::WriteConsole(handle)) + } else { + // SAFETY: winapi call, we just got this handle from the OS and checked it + let file = unsafe { File::from_raw_handle(handle.0) }; + Ok(Self::NtWriteFile(ManuallyDrop::new(file))) + } + } + + fn write_winerror(&mut self, s: &str) -> Result<(), windows_result::Error> { + match self { + Self::WriteConsole(handle) => { + // According to comments in the ReactOS source, NT's behavior is that writes of 80 + // bytes or fewer are passed in-line in the message to the console server and + // longer writes allocate out of a shared heap with CSRSS. In an attempt to avoid + // allocations, write in 80-byte chunks. + let mut buf = ArrayVec::::new(); + for c in s.encode_utf16() { + if buf.try_push(c).is_err() { + // SAFETY: winapi call, arrayvec guarantees the slice is valid + unsafe { WriteConsoleW(*handle, &buf, None, None) }?; + buf.clear(); + buf.push(c); + } + } + if !buf.is_empty() { + // SAFETY: winapi call, arrayvec guarantees the slice is valid + unsafe { WriteConsoleW(*handle, &buf, None, None) }?; + } + } + Self::NtWriteFile(file) => { + use std::io::Write; + file.write_all(s.as_bytes())?; + } + } + Ok(()) } } +impl Write for ExceptionSafeStderr { + fn write_str(&mut self, s: &str) -> std::fmt::Result { + self.write_winerror(s).map_err(|_| std::fmt::Error) + } +} + +fn display_exception_info( + e: &mut ExceptionSafeStderr, + name: &str, + info: &[usize; 15], +) -> std::fmt::Result { + match info[0] { + 0 => writeln!(e, "{name} reading {:#x}", info[1])?, + 1 => writeln!(e, "{name} writing {:#x}", info[1])?, + 8 => writeln!(e, "{name} executing {:#x}", info[1])?, + _ => writeln!(e, "{name} from operation {} at {:#x}", info[0], info[1])?, + } + Ok(()) +} + #[cfg(target_arch = "x86")] -fn dump_regs(c: &CONTEXT) { - eprintln!( - "eax={:08x} ebx={:08x} ecx={:08x} edx={:08x} esi={:08x} edi={:08x}", - c.Eax, c.Ebx, c.Ecx, c.Edx, c.Esi, c.Edi - ); - eprintln!( - "eip={:08x} ebp={:08x} esp={:08x} eflags={:08x}", - c.Eip, c.Ebp, c.Esp, c.EFlags - ); +fn dump_regs(e: &mut ExceptionSafeStderr, c: &CONTEXT) -> std::fmt::Result { + let CONTEXT { + Eax, + Ebx, + Ecx, + Edx, + Esi, + Edi, + Eip, + Ebp, + Esp, + EFlags, + .. + } = c; + writeln!( + e, + "eax={Eax:08x} ebx={Ebx:08x} ecx={Ecx:08x} edx={Edx:08x} esi={Esi:08x} edi={Edi:08x}" + )?; + writeln!( + e, + "eip={Eip:08x} ebp={Ebp:08x} esp={Esp:08x} eflags={EFlags:08x}" + )?; + Ok(()) } #[cfg(target_arch = "x86_64")] -fn dump_regs(c: &CONTEXT) { - eprintln!("rax={:016x} rbx={:016x} rcx={:016x}", c.Rax, c.Rbx, c.Rcx); - eprintln!("rdx={:016x} rsx={:016x} rdi={:016x}", c.Rdx, c.Rsi, c.Rdi); - eprintln!("rsp={:016x} rbp={:016x} r8={:016x}", c.Rsp, c.Rbp, c.R8); - eprintln!(" r9={:016x} r10={:016x} r11={:016x}", c.R9, c.R10, c.R11); - eprintln!("r12={:016x} r13={:016x} r14={:016x}", c.R12, c.R13, c.R14); - eprintln!( - "r15={:016x} rip={:016x} eflags={:016x}", - c.R15, c.Rip, c.EFlags - ); +fn dump_regs(e: &mut ExceptionSafeStderr, c: &CONTEXT) -> std::fmt::Result { + let CONTEXT { + Rax, + Rbx, + Rcx, + Rdx, + Rsi, + Rdi, + Rsp, + Rbp, + R8, + R9, + R10, + R11, + R12, + R13, + R14, + R15, + Rip, + EFlags, + .. + } = c; + writeln!(e, "rax={Rax:016x} rbx={Rbx:016x} rcx={Rcx:016x}")?; + writeln!(e, "rdx={Rdx:016x} rsi={Rsi:016x} rdi={Rdi:016x}")?; + writeln!(e, "rsp={Rsp:016x} rbp={Rbp:016x} r8={R8 :016x}")?; + writeln!(e, " r9={R9 :016x} r10={R10:016x} r11={R11:016x}")?; + writeln!(e, "r12={R12:016x} r13={R13:016x} r14={R14:016x}")?; + writeln!(e, "r15={R15:016x} rip={Rip:016x} eflags={EFlags:016x}")?; + Ok(()) } #[cfg(target_arch = "aarch64")] -fn dump_regs(c: &CONTEXT) { +fn dump_regs(e: &mut ExceptionSafeStderr, c: &CONTEXT) -> std::fmt::Result { + let CONTEXT { Cpsr, Sp, Pc, .. } = c; // SAFETY: The two variants of this anonymous union are equivalent, // one's an array and one has named registers. - let r = unsafe { c.Anonymous.Anonymous }; - eprintln!("cpsr={:016x} sp={:016x} pc={:016x}", c.Cpsr, c.Sp, c.Pc); - eprintln!(" x0={:016x} x1={:016x} x2={:016x}", r.X0, r.X1, r.X2); - eprintln!(" x3={:016x} x4={:016x} x5={:016x}", r.X3, r.X4, r.X5); - eprintln!(" x6={:016x} x7={:016x} x8={:016x}", r.X6, r.X7, r.X8); - eprintln!(" x9={:016x} x10={:016x} x11={:016x}", r.X9, r.X10, r.X11); - eprintln!(" x12={:016x} x13={:016x} x14={:016x}", r.X12, r.X13, r.X14); - eprintln!(" x15={:016x} x16={:016x} x17={:016x}", r.X15, r.X16, r.X17); - eprintln!(" x18={:016x} x19={:016x} x20={:016x}", r.X18, r.X19, r.X20); - eprintln!(" x21={:016x} x22={:016x} x23={:016x}", r.X21, r.X22, r.X23); - eprintln!(" x24={:016x} x25={:016x} x26={:016x}", r.X24, r.X25, r.X26); - eprintln!(" x27={:016x} x28={:016x}", r.X27, r.X28); - eprintln!(" fp={:016x} lr={:016x}", r.Fp, r.Lr); + let regs = unsafe { c.Anonymous.Anonymous }; + let Windows::Win32::System::Diagnostics::Debug::CONTEXT_0_0 { + X0, + X1, + X2, + X3, + X4, + X5, + X6, + X7, + X8, + X9, + X10, + X11, + X12, + X13, + X14, + X15, + X16, + X17, + X18, + X19, + X20, + X21, + X22, + X23, + X24, + X25, + X26, + X27, + X28, + Fp, + Lr, + } = regs; + writeln!(e, "cpsr={Cpsr:016x} sp={Sp :016x} pc={Pc :016x}")?; + writeln!(e, " x0={X0 :016x} x1={X1 :016x} x2={X2 :016x}")?; + writeln!(e, " x3={X3 :016x} x4={X4 :016x} x5={X5 :016x}")?; + writeln!(e, " x6={X6 :016x} x7={X7 :016x} x8={X8 :016x}")?; + writeln!(e, " x9={X9 :016x} x10={X10:016x} x11={X11:016x}")?; + writeln!(e, " x12={X12 :016x} x13={X13:016x} x14={X14:016x}")?; + writeln!(e, " x15={X15 :016x} x16={X16:016x} x17={X17:016x}")?; + writeln!(e, " x18={X18 :016x} x19={X19:016x} x20={X20:016x}")?; + writeln!(e, " x21={X21 :016x} x22={X22:016x} x23={X23:016x}")?; + writeln!(e, " x24={X24 :016x} x25={X25:016x} x26={X26:016x}")?; + writeln!(e, " x27={X27 :016x} x28={X28:016x}")?; + writeln!(e, " fp={Fp :016x} lr={Lr :016x}")?; + Ok(()) } -unsafe extern "system" fn unhandled_exception_filter( - exception_info: *const EXCEPTION_POINTERS, -) -> i32 { - // TODO: Really we should not be using eprintln here because Stderr is not async-signal-safe. - // Probably we should be calling the console APIs directly. - eprintln!("error: unhandled exception in uv, please report a bug:"); +fn dump_exception(exception_info: *const EXCEPTION_POINTERS) -> std::fmt::Result { + let mut e = ExceptionSafeStderr::new().map_err(|_| std::fmt::Error)?; + writeln!(e, "error: unhandled exception in uv, please report a bug:")?; let mut context = None; // SAFETY: Pointer comes from the OS if let Some(info) = unsafe { exception_info.as_ref() } { // SAFETY: Pointer comes from the OS if let Some(exc) = unsafe { info.ExceptionRecord.as_ref() } { - eprintln!( + writeln!( + e, "code {:#X} at address {:?}", exc.ExceptionCode.0, exc.ExceptionAddress - ); + )?; match exc.ExceptionCode { Foundation::EXCEPTION_ACCESS_VIOLATION => { - display_exception_info("EXCEPTION_ACCESS_VIOLATION", &exc.ExceptionInformation); + display_exception_info( + &mut e, + "EXCEPTION_ACCESS_VIOLATION", + &exc.ExceptionInformation, + )?; } Foundation::EXCEPTION_IN_PAGE_ERROR => { - display_exception_info("EXCEPTION_IN_PAGE_ERROR", &exc.ExceptionInformation); + display_exception_info( + &mut e, + "EXCEPTION_IN_PAGE_ERROR", + &exc.ExceptionInformation, + )?; } Foundation::EXCEPTION_ILLEGAL_INSTRUCTION => { - eprintln!("EXCEPTION_ILLEGAL_INSTRUCTION"); + writeln!(e, "EXCEPTION_ILLEGAL_INSTRUCTION")?; } Foundation::EXCEPTION_STACK_OVERFLOW => { - eprintln!("EXCEPTION_STACK_OVERFLOW"); + writeln!(e, "EXCEPTION_STACK_OVERFLOW")?; } _ => {} } } else { - eprintln!("(ExceptionRecord is NULL)"); + writeln!(e, "(ExceptionRecord is NULL)")?; } // SAFETY: Pointer comes from the OS context = unsafe { info.ContextRecord.as_ref() }; } else { - eprintln!("(ExceptionInfo is NULL)"); + writeln!(e, "(ExceptionInfo is NULL)")?; } + // TODO: std::backtrace does a lot of allocations, so we are no longer async-signal-safe at + // this point, but hopefully we got a useful error message on screen already. We could do a + // better job by using backtrace-rs directly + arrayvec. let backtrace = std::backtrace::Backtrace::capture(); if backtrace.status() == std::backtrace::BacktraceStatus::Disabled { - eprintln!("note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace"); + writeln!( + e, + "note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace" + )?; } else { if let Some(context) = context { - dump_regs(context); + dump_regs(&mut e, context)?; } - eprintln!("stack backtrace:\n{backtrace:#}"); + writeln!(e, "stack backtrace:\n{backtrace:#}")?; } + Ok(()) +} + +unsafe extern "system" fn unhandled_exception_filter( + exception_info: *const EXCEPTION_POINTERS, +) -> i32 { + let _ = dump_exception(exception_info); EXCEPTION_CONTINUE_SEARCH } /// Set up our handler for unhandled exceptions. pub(crate) fn setup() { - // SAFETY: winapi call + // SAFETY: winapi call, argument is a mostly async-signal-safe function unsafe { SetUnhandledExceptionFilter(Some(Some(unhandled_exception_filter))); } From b3df1c2401e71eea05f2dba3ff39d48a1a5d4ded Mon Sep 17 00:00:00 2001 From: Geoffrey Thomas Date: Thu, 17 Jul 2025 08:29:41 -0400 Subject: [PATCH 233/349] Fix typo in #14619 (#14677) --- crates/uv/src/windows_exception.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/uv/src/windows_exception.rs b/crates/uv/src/windows_exception.rs index 2e40e89cc..048eaa1ba 100644 --- a/crates/uv/src/windows_exception.rs +++ b/crates/uv/src/windows_exception.rs @@ -187,7 +187,7 @@ fn dump_regs(e: &mut ExceptionSafeStderr, c: &CONTEXT) -> std::fmt::Result { // SAFETY: The two variants of this anonymous union are equivalent, // one's an array and one has named registers. let regs = unsafe { c.Anonymous.Anonymous }; - let Windows::Win32::System::Diagnostics::Debug::CONTEXT_0_0 { + let windows::Win32::System::Diagnostics::Debug::CONTEXT_0_0 { X0, X1, X2, From 09fc943cca0789e83c6afa16fc17d1d6f54b7978 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 17 Jul 2025 07:38:12 -0500 Subject: [PATCH 234/349] Rename msrv build job for consistency with other binary builds (#14679) --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bb357f4a3..e9beddcc5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -654,8 +654,8 @@ jobs: ${{ env.UV_WORKSPACE }}/target/aarch64-pc-windows-msvc/debug/uvx.exe retention-days: 1 - cargo-build-msrv: - name: "cargo build (msrv)" + build-binary-msrv: + name: "build binary | msrv" needs: determine_changes if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }} runs-on: github-ubuntu-24.04-x86_64-8 From bdb8c2646a0f4ce64574b048e9b60246193a9ffc Mon Sep 17 00:00:00 2001 From: adisbladis Date: Fri, 18 Jul 2025 01:11:32 +1200 Subject: [PATCH 235/349] Add UV_COMPILE_BYTECODE_TIMEOUT environment variable (#14369) ## Summary When installing packages on _very_ slow/overloaded systems it'spossible to trigger bytecode compilation timeouts, which tends to happen in environments such as Qemu (especially without KVM/virtio), but also on systems that are simply overloaded. I've seen this in my Nix builds if I for example am compiling a Linux kernel at the same time as a few other concurrent builds. By making the bytecode compilation timeout adjustable you can work around such issues. I plan to set `UV_COMPILE_BYTECODE_TIMEOUT=0` in the [pyproject.nix builders](https://pyproject-nix.github.io/pyproject.nix/build.html) to make them more reliable. - Related issues * https://github.com/astral-sh/uv/issues/6105 ## Test Plan Only manual testing was applied in this instance. There is no existing automated tests for bytecode compilation timeout afaict. --- crates/uv-installer/src/compile.rs | 61 ++++++++++++++++++++++++------ crates/uv-static/src/env_vars.rs | 3 ++ docs/reference/environment.md | 4 ++ 3 files changed, 56 insertions(+), 12 deletions(-) diff --git a/crates/uv-installer/src/compile.rs b/crates/uv-installer/src/compile.rs index 7dd167b4a..4ee74f40d 100644 --- a/crates/uv-installer/src/compile.rs +++ b/crates/uv-installer/src/compile.rs @@ -2,7 +2,7 @@ use std::panic::AssertUnwindSafe; use std::path::{Path, PathBuf}; use std::process::Stdio; use std::time::Duration; -use std::{io, panic}; +use std::{env, io, panic}; use async_channel::{Receiver, SendError}; use tempfile::tempdir_in; @@ -20,7 +20,7 @@ use uv_warnings::warn_user; const COMPILEALL_SCRIPT: &str = include_str!("pip_compileall.py"); /// This is longer than any compilation should ever take. -const COMPILE_TIMEOUT: Duration = Duration::from_secs(60); +const DEFAULT_COMPILE_TIMEOUT: Duration = Duration::from_secs(60); #[derive(Debug, Error)] pub enum CompileError { @@ -55,6 +55,8 @@ pub enum CompileError { }, #[error("Python startup timed out ({}s)", _0.as_secs_f32())] StartupTimeout(Duration), + #[error("Got invalid value from environment for {var}: {message}.")] + EnvironmentError { var: &'static str, message: String }, } /// Bytecode compile all file in `dir` using a pool of Python interpreters running a Python script @@ -88,6 +90,29 @@ pub async fn compile_tree( let tempdir = tempdir_in(cache).map_err(CompileError::TempFile)?; let pip_compileall_py = tempdir.path().join("pip_compileall.py"); + let timeout: Option = match env::var(EnvVars::UV_COMPILE_BYTECODE_TIMEOUT) { + Ok(value) => { + if value == "0" { + debug!("Disabling bytecode compilation timeout"); + None + } else { + if let Ok(duration) = value.parse::().map(Duration::from_secs) { + debug!( + "Using bytecode compilation timeout of {}s", + duration.as_secs() + ); + Some(duration) + } else { + return Err(CompileError::EnvironmentError { + var: "UV_COMPILE_BYTECODE_TIMEOUT", + message: format!("Expected an integer number of seconds, got \"{value}\""), + }); + } + } + } + Err(_) => Some(DEFAULT_COMPILE_TIMEOUT), + }; + debug!("Starting {} bytecode compilation workers", worker_count); let mut worker_handles = Vec::new(); for _ in 0..worker_count { @@ -98,6 +123,7 @@ pub async fn compile_tree( python_executable.to_path_buf(), pip_compileall_py.clone(), receiver.clone(), + timeout, ); // Spawn each worker on a dedicated thread. @@ -189,6 +215,7 @@ async fn worker( interpreter: PathBuf, pip_compileall_py: PathBuf, receiver: Receiver, + timeout: Option, ) -> Result<(), CompileError> { fs_err::tokio::write(&pip_compileall_py, COMPILEALL_SCRIPT) .await @@ -208,12 +235,17 @@ async fn worker( } } }; + // Handle a broken `python` by using a timeout, one that's higher than any compilation // should ever take. let (mut bytecode_compiler, child_stdin, mut child_stdout, mut child_stderr) = - tokio::time::timeout(COMPILE_TIMEOUT, wait_until_ready) - .await - .map_err(|_| CompileError::StartupTimeout(COMPILE_TIMEOUT))??; + if let Some(duration) = timeout { + tokio::time::timeout(duration, wait_until_ready) + .await + .map_err(|_| CompileError::StartupTimeout(timeout.unwrap()))?? + } else { + wait_until_ready.await? + }; let stderr_reader = tokio::task::spawn(async move { let mut child_stderr_collected: Vec = Vec::new(); @@ -223,7 +255,7 @@ async fn worker( Ok(child_stderr_collected) }); - let result = worker_main_loop(receiver, child_stdin, &mut child_stdout).await; + let result = worker_main_loop(receiver, child_stdin, &mut child_stdout, timeout).await; // Reap the process to avoid zombies. let _ = bytecode_compiler.kill().await; @@ -340,6 +372,7 @@ async fn worker_main_loop( receiver: Receiver, mut child_stdin: ChildStdin, child_stdout: &mut BufReader, + timeout: Option, ) -> Result<(), CompileError> { let mut out_line = String::new(); while let Ok(source_file) = receiver.recv().await { @@ -372,12 +405,16 @@ async fn worker_main_loop( // Handle a broken `python` by using a timeout, one that's higher than any compilation // should ever take. - tokio::time::timeout(COMPILE_TIMEOUT, python_handle) - .await - .map_err(|_| CompileError::CompileTimeout { - elapsed: COMPILE_TIMEOUT, - source_file: source_file.clone(), - })??; + if let Some(duration) = timeout { + tokio::time::timeout(duration, python_handle) + .await + .map_err(|_| CompileError::CompileTimeout { + elapsed: duration, + source_file: source_file.clone(), + })??; + } else { + python_handle.await?; + } // This is a sanity check, if we don't get the path back something has gone wrong, e.g. // we're not actually running a python interpreter. diff --git a/crates/uv-static/src/env_vars.rs b/crates/uv-static/src/env_vars.rs index ae981cac3..216228ff2 100644 --- a/crates/uv-static/src/env_vars.rs +++ b/crates/uv-static/src/env_vars.rs @@ -162,6 +162,9 @@ impl EnvVars { /// will compile Python source files to bytecode after installation. pub const UV_COMPILE_BYTECODE: &'static str = "UV_COMPILE_BYTECODE"; + /// Timeout (in seconds) for bytecode compilation. + pub const UV_COMPILE_BYTECODE_TIMEOUT: &'static str = "UV_COMPILE_BYTECODE_TIMEOUT"; + /// Equivalent to the `--no-editable` command-line argument. If set, uv /// installs any editable dependencies, including the project and any workspace members, as /// non-editable diff --git a/docs/reference/environment.md b/docs/reference/environment.md index 47e4d8db9..5f06cfd3f 100644 --- a/docs/reference/environment.md +++ b/docs/reference/environment.md @@ -26,6 +26,10 @@ directory for caching instead of the default cache directory. Equivalent to the `--compile-bytecode` command-line argument. If set, uv will compile Python source files to bytecode after installation. +### `UV_COMPILE_BYTECODE_TIMEOUT` + +Timeout (in seconds) for bytecode compilation. + ### `UV_CONCURRENT_BUILDS` Sets the maximum number of source distributions that uv will build From 3884ab5715937fdf01dbc1c6bfb91cacf00e20ce Mon Sep 17 00:00:00 2001 From: adisbladis Date: Fri, 18 Jul 2025 01:35:25 +1200 Subject: [PATCH 236/349] Fix bytecode compilation debug message introduced by #14369 (#14682) ## Summary When refactoring the addition PR I accidentally introduced a bug where the debug message would not be output if the default value is used. cc @zanieb --- crates/uv-installer/src/compile.rs | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/crates/uv-installer/src/compile.rs b/crates/uv-installer/src/compile.rs index 4ee74f40d..8704d9542 100644 --- a/crates/uv-installer/src/compile.rs +++ b/crates/uv-installer/src/compile.rs @@ -91,27 +91,28 @@ pub async fn compile_tree( let pip_compileall_py = tempdir.path().join("pip_compileall.py"); let timeout: Option = match env::var(EnvVars::UV_COMPILE_BYTECODE_TIMEOUT) { - Ok(value) => { - if value == "0" { - debug!("Disabling bytecode compilation timeout"); - None - } else { - if let Ok(duration) = value.parse::().map(Duration::from_secs) { - debug!( - "Using bytecode compilation timeout of {}s", - duration.as_secs() - ); - Some(duration) - } else { + Ok(value) => match value.as_str() { + "0" => None, + _ => match value.parse::().map(Duration::from_secs) { + Ok(duration) => Some(duration), + Err(_) => { return Err(CompileError::EnvironmentError { var: "UV_COMPILE_BYTECODE_TIMEOUT", message: format!("Expected an integer number of seconds, got \"{value}\""), }); } - } - } + }, + }, Err(_) => Some(DEFAULT_COMPILE_TIMEOUT), }; + if let Some(duration) = timeout { + debug!( + "Using bytecode compilation timeout of {}s", + duration.as_secs() + ); + } else { + debug!("Disabling bytecode compilation timeout"); + } debug!("Starting {} bytecode compilation workers", worker_count); let mut worker_handles = Vec::new(); From 78d6d1134a50705d336ba209055eb0076feb017d Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 17 Jul 2025 11:27:15 -0500 Subject: [PATCH 237/349] Bump version to 0.7.22 (#14685) --- CHANGELOG.md | 34 +++++++++++++++++++++++++++ Cargo.lock | 6 ++--- crates/uv-build/Cargo.toml | 2 +- crates/uv-build/pyproject.toml | 2 +- crates/uv-version/Cargo.toml | 2 +- crates/uv/Cargo.toml | 2 +- docs/concepts/build-backend.md | 2 +- 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/pre-commit.md | 10 ++++---- pyproject.toml | 2 +- 13 files changed, 58 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38be00d2d..87cf0c9e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,40 @@ +## 0.7.22 + +### Python + +- Upgrade GraalPy to 24.2.2 + +See the [GraalPy release notes](https://github.com/oracle/graalpython/releases/tag/graal-24.2.2) for more details. + +### Configuration + +- Add `UV_COMPILE_BYTECODE_TIMEOUT` environment variable ([#14369](https://github.com/astral-sh/uv/pull/14369)) +- Allow users to override index `cache-control` headers ([#14620](https://github.com/astral-sh/uv/pull/14620)) +- Add `UV_LIBC` to override libc selection in multi-libc environment ([#14646](https://github.com/astral-sh/uv/pull/14646)) + +### Bug fixes + +- Fix `--all-arches` when paired with `--only-downloads` ([#14629](https://github.com/astral-sh/uv/pull/14629)) +- Skip Windows Python interpreters that return a broken MSIX package code ([#14636](https://github.com/astral-sh/uv/pull/14636)) +- Warn on invalid `uv.toml` when provided via direct path ([#14653](https://github.com/astral-sh/uv/pull/14653)) +- Improve async signal safety in Windows exception handler ([#14619](https://github.com/astral-sh/uv/pull/14619)) + +### Documentation + +- Mention the `revision` in the lockfile versioning doc ([#14634](https://github.com/astral-sh/uv/pull/14634)) +- Move "Conflicting dependencies" to the "Resolution" page ([#14633](https://github.com/astral-sh/uv/pull/14633)) +- Rename "Dependency specifiers" section to exclude PEP 508 reference ([#14631](https://github.com/astral-sh/uv/pull/14631)) +- Suggest `uv cache clean` prior to `--reinstall` ([#14659](https://github.com/astral-sh/uv/pull/14659)) + +### Preview features + +- Make preview Python registration on Windows non-fatal ([#14614](https://github.com/astral-sh/uv/pull/14614)) +- Update preview installation of Python executables to be non-fatal ([#14612](https://github.com/astral-sh/uv/pull/14612)) +- Add `uv python update-shell` ([#14627](https://github.com/astral-sh/uv/pull/14627)) + ## 0.7.21 ### Python diff --git a/Cargo.lock b/Cargo.lock index 3ff7ad6d0..8a95f655d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4633,7 +4633,7 @@ dependencies = [ [[package]] name = "uv" -version = "0.7.21" +version = "0.7.22" dependencies = [ "anstream", "anyhow", @@ -4800,7 +4800,7 @@ dependencies = [ [[package]] name = "uv-build" -version = "0.7.21" +version = "0.7.22" dependencies = [ "anyhow", "uv-build-backend", @@ -5993,7 +5993,7 @@ dependencies = [ [[package]] name = "uv-version" -version = "0.7.21" +version = "0.7.22" [[package]] name = "uv-virtualenv" diff --git a/crates/uv-build/Cargo.toml b/crates/uv-build/Cargo.toml index f943010ae..8014fa445 100644 --- a/crates/uv-build/Cargo.toml +++ b/crates/uv-build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-build" -version = "0.7.21" +version = "0.7.22" edition.workspace = true rust-version.workspace = true homepage.workspace = true diff --git a/crates/uv-build/pyproject.toml b/crates/uv-build/pyproject.toml index 5a2209155..1a78d34dc 100644 --- a/crates/uv-build/pyproject.toml +++ b/crates/uv-build/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uv-build" -version = "0.7.21" +version = "0.7.22" description = "The uv build backend" authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }] requires-python = ">=3.8" diff --git a/crates/uv-version/Cargo.toml b/crates/uv-version/Cargo.toml index a9fe788a5..e1a424af8 100644 --- a/crates/uv-version/Cargo.toml +++ b/crates/uv-version/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-version" -version = "0.7.21" +version = "0.7.22" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml index d72035467..975495904 100644 --- a/crates/uv/Cargo.toml +++ b/crates/uv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv" -version = "0.7.21" +version = "0.7.22" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } diff --git a/docs/concepts/build-backend.md b/docs/concepts/build-backend.md index 69694f317..5f52463bf 100644 --- a/docs/concepts/build-backend.md +++ b/docs/concepts/build-backend.md @@ -36,7 +36,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.7.21,<0.8.0"] +requires = ["uv_build>=0.7.22,<0.8.0"] build-backend = "uv_build" ``` diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index fa68d210a..3e31a5003 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.7.21/install.sh | sh + $ curl -LsSf https://astral.sh/uv/0.7.22/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.7.21/install.ps1 | iex" + PS> powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/0.7.22/install.ps1 | iex" ``` !!! tip diff --git a/docs/guides/integration/aws-lambda.md b/docs/guides/integration/aws-lambda.md index 4cdb75b7a..14224b3fe 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.7.21 AS uv +FROM ghcr.io/astral-sh/uv:0.7.22 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.7.21 AS uv +FROM ghcr.io/astral-sh/uv:0.7.22 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 bbea9b264..2ea14c9b0 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.7.21` +- `ghcr.io/astral-sh/uv:{major}.{minor}.{patch}`, e.g., `ghcr.io/astral-sh/uv:0.7.22` - `ghcr.io/astral-sh/uv:{major}.{minor}`, e.g., `ghcr.io/astral-sh/uv:0.7` (the latest patch version) @@ -75,7 +75,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.7.21-alpine`. +`ghcr.io/astral-sh/uv:{major}.{minor}-{base}`, e.g., `ghcr.io/astral-sh/uv:0.7.22-alpine`. For more details, see the [GitHub Container](https://github.com/astral-sh/uv/pkgs/container/uv) page. @@ -113,7 +113,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.7.21 /uv /uvx /bin/ +COPY --from=ghcr.io/astral-sh/uv:0.7.22 /uv /uvx /bin/ ``` !!! tip @@ -131,7 +131,7 @@ COPY --from=ghcr.io/astral-sh/uv:0.7.21 /uv /uvx /bin/ Or, with the installer: ```dockerfile -ADD https://astral.sh/uv/0.7.21/install.sh /uv-installer.sh +ADD https://astral.sh/uv/0.7.22/install.sh /uv-installer.sh ``` ### Installing a project @@ -557,5 +557,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.7.21`, or (even better) the specific image digest, + version tag, e.g., `ghcr.io/astral-sh/uv:0.7.22`, 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 d206febd1..956b47660 100644 --- a/docs/guides/integration/github.md +++ b/docs/guides/integration/github.md @@ -47,7 +47,7 @@ jobs: uses: astral-sh/setup-uv@v6 with: # Install a specific version of uv. - version: "0.7.21" + version: "0.7.22" ``` ## Setting up Python diff --git a/docs/guides/integration/pre-commit.md b/docs/guides/integration/pre-commit.md index 912ff0213..d2598fed8 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.7.21 + rev: 0.7.22 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.7.21 + rev: 0.7.22 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.7.21 + rev: 0.7.22 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.7.21 + rev: 0.7.22 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.7.21 + rev: 0.7.22 hooks: # Compile requirements - id: pip-compile diff --git a/pyproject.toml b/pyproject.toml index f3c9c4f64..a079d53b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "uv" -version = "0.7.21" +version = "0.7.22" 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" From 868ecd7b3a4855b3b84ec121a826fb218b843084 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 17 Jul 2025 12:33:43 -0500 Subject: [PATCH 238/349] Add support for toggling Python bin and registry install options via env vars (#14662) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds environment variables for https://github.com/astral-sh/uv/pull/14612 and https://github.com/astral-sh/uv/pull/14614 We can't use the Clap `BoolishValueParser` here, and the reasoning is a little hard to explain. If we used `UV_PYTHON_INSTALL_NO_BIN`, as is our typical pattern, it'd work, but here we allow opt-in to hard errors with `UV_PYTHON_INSTALL_BIN=1` and I don't think we should have both `UV_PYTHON_INSTALL_BIN` and `UV_PYTHON_INSTALL_NO_BIN`. Consequently, this pull request introduces a new `EnvironmentOptions` abstraction which allows us to express semantics that Clap cannot — which we probably want anyway because we have an increasing number of environment variables we're parsing downstream, e.g., #14544 and #14369. --- crates/uv-cli/src/lib.rs | 8 +++ crates/uv-settings/src/lib.rs | 82 ++++++++++++++++++++++++++++ crates/uv-static/src/env_vars.rs | 6 ++ crates/uv/src/lib.rs | 7 ++- crates/uv/src/settings.rs | 15 +++-- crates/uv/tests/it/help.rs | 8 ++- crates/uv/tests/it/python_install.rs | 20 ++++++- docs/reference/cli.md | 6 +- docs/reference/environment.md | 8 +++ 9 files changed, 147 insertions(+), 13 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index a846aec59..94b79558d 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -4959,11 +4959,15 @@ pub struct PythonInstallArgs { /// This is the default behavior. If this flag is provided explicitly, uv will error if the /// executable cannot be installed. /// + /// This can also be set with `UV_PYTHON_INSTALL_BIN=1`. + /// /// See `UV_PYTHON_BIN_DIR` to customize the target directory. #[arg(long, overrides_with("no_bin"), hide = true)] pub bin: bool, /// Do not install a Python executable into the `bin` directory. + /// + /// This can also be set with `UV_PYTHON_INSTALL_BIN=0`. #[arg(long, overrides_with("bin"), conflicts_with("default"))] pub no_bin: bool, @@ -4971,10 +4975,14 @@ pub struct PythonInstallArgs { /// /// This is the default behavior on Windows. If this flag is provided explicitly, uv will error if the /// registry entry cannot be created. + /// + /// This can also be set with `UV_PYTHON_INSTALL_REGISTRY=1`. #[arg(long, overrides_with("no_registry"), hide = true)] pub registry: bool, /// Do not register the Python installation in the Windows registry. + /// + /// This can also be set with `UV_PYTHON_INSTALL_REGISTRY=0`. #[arg(long, overrides_with("registry"))] pub no_registry: bool, diff --git a/crates/uv-settings/src/lib.rs b/crates/uv-settings/src/lib.rs index d676cc060..cad600cfc 100644 --- a/crates/uv-settings/src/lib.rs +++ b/crates/uv-settings/src/lib.rs @@ -3,6 +3,7 @@ use std::path::{Path, PathBuf}; use uv_dirs::{system_config_file, user_config_dir}; use uv_fs::Simplified; +use uv_static::EnvVars; use uv_warnings::warn_user; pub use crate::combine::*; @@ -246,4 +247,85 @@ pub enum Error { #[error("Failed to parse: `{}`. The `{}` field is not allowed in a `uv.toml` file. `{}` is only applicable in the context of a project, and should be placed in a `pyproject.toml` file instead.", _0.user_display(), _1, _1)] PyprojectOnlyField(PathBuf, &'static str), + + #[error("Failed to parse environment variable `{name}` with invalid value `{value}`: {err}")] + InvalidEnvironmentVariable { + name: String, + value: String, + err: String, + }, +} + +/// Options loaded from environment variables. +/// +/// This is currently a subset of all respected environment variables, most are parsed via Clap at +/// the CLI level, however there are limited semantics in that context. +#[derive(Debug, Clone)] +pub struct EnvironmentOptions { + pub python_install_bin: Option, + pub python_install_registry: Option, +} + +impl EnvironmentOptions { + /// Create a new [`EnvironmentOptions`] from environment variables. + pub fn new() -> Result { + Ok(Self { + python_install_bin: parse_boolish_environment_variable(EnvVars::UV_PYTHON_INSTALL_BIN)?, + python_install_registry: parse_boolish_environment_variable( + EnvVars::UV_PYTHON_INSTALL_REGISTRY, + )?, + }) + } +} + +/// Parse a boolean environment variable. +/// +/// Adapted from Clap's `BoolishValueParser` which is dual licensed under the MIT and Apache-2.0. +fn parse_boolish_environment_variable(name: &'static str) -> Result, Error> { + // See `clap_builder/src/util/str_to_bool.rs` + // We want to match Clap's accepted values + + // True values are `y`, `yes`, `t`, `true`, `on`, and `1`. + const TRUE_LITERALS: [&str; 6] = ["y", "yes", "t", "true", "on", "1"]; + + // False values are `n`, `no`, `f`, `false`, `off`, and `0`. + const FALSE_LITERALS: [&str; 6] = ["n", "no", "f", "false", "off", "0"]; + + // Converts a string literal representation of truth to true or false. + // + // `false` values are `n`, `no`, `f`, `false`, `off`, and `0` (case insensitive). + // + // Any other value will be considered as `true`. + fn str_to_bool(val: impl AsRef) -> Option { + let pat: &str = &val.as_ref().to_lowercase(); + if TRUE_LITERALS.contains(&pat) { + Some(true) + } else if FALSE_LITERALS.contains(&pat) { + Some(false) + } else { + None + } + } + + let Some(value) = std::env::var_os(name) else { + return Ok(None); + }; + + let Some(value) = value.to_str() else { + return Err(Error::InvalidEnvironmentVariable { + name: name.to_string(), + value: value.to_string_lossy().to_string(), + err: "expected a valid UTF-8 string".to_string(), + }); + }; + + let Some(value) = str_to_bool(value) else { + return Err(Error::InvalidEnvironmentVariable { + name: name.to_string(), + value: value.to_string(), + err: "expected a boolish value".to_string(), + }); + }; + + Ok(Some(value)) } diff --git a/crates/uv-static/src/env_vars.rs b/crates/uv-static/src/env_vars.rs index 216228ff2..58458e8ca 100644 --- a/crates/uv-static/src/env_vars.rs +++ b/crates/uv-static/src/env_vars.rs @@ -269,6 +269,12 @@ impl EnvVars { /// Specifies the directory for storing managed Python installations. pub const UV_PYTHON_INSTALL_DIR: &'static str = "UV_PYTHON_INSTALL_DIR"; + /// Whether to install the Python executable into the `UV_PYTHON_BIN_DIR` directory. + pub const UV_PYTHON_INSTALL_BIN: &'static str = "UV_PYTHON_INSTALL_BIN"; + + /// Whether to install the Python executable into the Windows registry. + pub const UV_PYTHON_INSTALL_REGISTRY: &'static str = "UV_PYTHON_INSTALL_REGISTRY"; + /// Managed Python installations information is hardcoded in the `uv` binary. /// /// This variable can be set to a URL pointing to JSON to use as a list for Python installations. diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 384f48ac4..995738638 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -39,7 +39,7 @@ use uv_python::PythonRequest; use uv_requirements::RequirementsSource; use uv_requirements_txt::RequirementsTxtRequirement; use uv_scripts::{Pep723Error, Pep723Item, Pep723ItemRef, Pep723Metadata, Pep723Script}; -use uv_settings::{Combine, FilesystemOptions, Options}; +use uv_settings::{Combine, EnvironmentOptions, FilesystemOptions, Options}; use uv_static::EnvVars; use uv_warnings::{warn_user, warn_user_once}; use uv_workspace::{DiscoveryOptions, Workspace, WorkspaceCache}; @@ -304,6 +304,9 @@ async fn run(mut cli: Cli) -> Result { .map(FilesystemOptions::from) .combine(filesystem); + // Load environment variables not handled by Clap + let environment = EnvironmentOptions::new()?; + // Resolve the global settings. let globals = GlobalSettings::resolve(&cli.top_level.global_args, filesystem.as_ref()); @@ -1391,7 +1394,7 @@ async fn run(mut cli: Cli) -> Result { command: PythonCommand::Install(args), }) => { // Resolve the settings from the command-line arguments and workspace configuration. - let args = settings::PythonInstallSettings::resolve(args, filesystem); + let args = settings::PythonInstallSettings::resolve(args, filesystem, environment); show_settings!(args); // TODO(john): If we later want to support `--upgrade`, we need to replace this. let upgrade = false; diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index b221f0f5d..b246f228f 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -38,8 +38,8 @@ use uv_resolver::{ AnnotationStyle, DependencyMode, ExcludeNewer, ForkStrategy, PrereleaseMode, ResolutionMode, }; use uv_settings::{ - Combine, FilesystemOptions, Options, PipOptions, PublishOptions, PythonInstallMirrors, - ResolverInstallerOptions, ResolverOptions, + Combine, EnvironmentOptions, FilesystemOptions, Options, PipOptions, PublishOptions, + PythonInstallMirrors, ResolverInstallerOptions, ResolverOptions, }; use uv_static::EnvVars; use uv_torch::TorchMode; @@ -944,7 +944,11 @@ pub(crate) struct PythonInstallSettings { impl PythonInstallSettings { /// Resolve the [`PythonInstallSettings`] from the CLI and filesystem configuration. #[allow(clippy::needless_pass_by_value)] - pub(crate) fn resolve(args: PythonInstallArgs, filesystem: Option) -> Self { + pub(crate) fn resolve( + args: PythonInstallArgs, + filesystem: Option, + environment: EnvironmentOptions, + ) -> Self { let options = filesystem.map(FilesystemOptions::into_options); let (python_mirror, pypy_mirror, python_downloads_json_url) = match options { Some(options) => ( @@ -979,8 +983,9 @@ impl PythonInstallSettings { targets, reinstall, force, - bin: flag(bin, no_bin, "bin"), - registry: flag(registry, no_registry, "registry"), + bin: flag(bin, no_bin, "bin").or(environment.python_install_bin), + registry: flag(registry, no_registry, "registry") + .or(environment.python_install_registry), python_install_mirror: python_mirror, pypy_install_mirror: pypy_mirror, python_downloads_json_url, diff --git a/crates/uv/tests/it/help.rs b/crates/uv/tests/it/help.rs index 39de4c6f9..d9353f7c3 100644 --- a/crates/uv/tests/it/help.rs +++ b/crates/uv/tests/it/help.rs @@ -506,10 +506,14 @@ fn help_subsubcommand() { [env: UV_PYTHON_INSTALL_DIR=] --no-bin - Do not install a Python executable into the `bin` directory + Do not install a Python executable into the `bin` directory. + + This can also be set with `UV_PYTHON_INSTALL_BIN=0`. --no-registry - Do not register the Python installation in the Windows registry + Do not register the Python installation in the Windows registry. + + This can also be set with `UV_PYTHON_INSTALL_REGISTRY=0`. --mirror Set the URL to use as the source for downloading Python installations. diff --git a/crates/uv/tests/it/python_install.rs b/crates/uv/tests/it/python_install.rs index 62b3254b8..50b0b3cf5 100644 --- a/crates/uv/tests/it/python_install.rs +++ b/crates/uv/tests/it/python_install.rs @@ -445,6 +445,15 @@ fn python_install_preview() { exit_code: 1 ----- stdout ----- + ----- stderr ----- + error: Failed to install executable for cpython-3.13.5-[PLATFORM] + Caused by: Executable already exists at `[BIN]/python3.13` but is not managed by uv; use `--force` to replace it + "); + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.13").env(EnvVars::UV_PYTHON_INSTALL_BIN, "1"), @r" + success: false + exit_code: 1 + ----- stdout ----- + ----- stderr ----- error: Failed to install executable for cpython-3.13.5-[PLATFORM] Caused by: Executable already exists at `[BIN]/python3.13` but is not managed by uv; use `--force` to replace it @@ -456,6 +465,13 @@ fn python_install_preview() { exit_code: 0 ----- stdout ----- + ----- stderr ----- + "); + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.13").env(EnvVars::UV_PYTHON_INSTALL_BIN, "0"), @r" + success: true + exit_code: 0 + ----- stdout ----- + ----- stderr ----- "); @@ -643,7 +659,7 @@ fn python_install_preview_upgrade() { .child(format!("python3.12{}", std::env::consts::EXE_SUFFIX)); // Install 3.12.5 - uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.12.5"), @r###" + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.12.5"), @r" success: true exit_code: 0 ----- stdout ----- @@ -651,7 +667,7 @@ fn python_install_preview_upgrade() { ----- stderr ----- Installed Python 3.12.5 in [TIME] + cpython-3.12.5-[PLATFORM] (python3.12) - "###); + "); // Installing with a patch version should cause the link to be to the patch installation. if cfg!(unix) { diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 66c46ae0c..5aea00f32 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -2796,7 +2796,8 @@ uv python install [OPTIONS] [TARGETS]...

    May also be set with the UV_PYTHON_INSTALL_MIRROR environment variable.

    --native-tls

    Whether to load TLS certificates from the platform's native certificate store.

    By default, uv loads certificates from the bundled webpki-roots crate. The webpki-roots are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).

    However, in some cases, you may want to use the platform's native certificate store, especially if you're relying on a corporate trust root (e.g., for a mandatory proxy) that's included in your system's certificate store.

    -

    May also be set with the UV_NATIVE_TLS environment variable.

    --no-bin

    Do not install a Python executable into the bin directory

    +

    May also be set with the UV_NATIVE_TLS environment variable.

    --no-bin

    Do not install a Python executable into the bin directory.

    +

    This can also be set with UV_PYTHON_INSTALL_BIN=0.

    --no-cache, --no-cache-dir, -n

    Avoid reading from or writing to the cache, instead using a temporary directory for the duration of the operation

    May also be set with the UV_NO_CACHE environment variable.

    --no-config

    Avoid discovering configuration files (pyproject.toml, uv.toml).

    Normally, configuration files are discovered in the current directory, parent directories, or user configuration directories.

    @@ -2805,7 +2806,8 @@ uv python install [OPTIONS] [TARGETS]...

    May also be set with the UV_NO_MANAGED_PYTHON environment variable.

    --no-progress

    Hide all progress outputs.

    For example, spinners or progress bars.

    May also be set with the UV_NO_PROGRESS environment variable.

    --no-python-downloads

    Disable automatic downloads of Python.

    -
    --no-registry

    Do not register the Python installation in the Windows registry

    +
    --no-registry

    Do not register the Python installation in the Windows registry.

    +

    This can also be set with UV_PYTHON_INSTALL_REGISTRY=0.

    --offline

    Disable network access.

    When disabled, uv will only use locally cached data and locally available files.

    May also be set with the UV_OFFLINE environment variable.

    --project project

    Run the command within the given project directory.

    diff --git a/docs/reference/environment.md b/docs/reference/environment.md index 5f06cfd3f..a64869edb 100644 --- a/docs/reference/environment.md +++ b/docs/reference/environment.md @@ -376,6 +376,10 @@ This will allow for setting each property of the Python installation, mostly the Note that currently, only local paths are supported. +### `UV_PYTHON_INSTALL_BIN` + +Whether to install the Python executable into the `UV_PYTHON_BIN_DIR` directory. + ### `UV_PYTHON_INSTALL_DIR` Specifies the directory for storing managed Python installations. @@ -390,6 +394,10 @@ The provided URL will replace `https://github.com/astral-sh/python-build-standal `https://github.com/astral-sh/python-build-standalone/releases/download/20240713/cpython-3.12.4%2B20240713-aarch64-apple-darwin-install_only.tar.gz`. Distributions can be read from a local directory by using the `file://` URL scheme. +### `UV_PYTHON_INSTALL_REGISTRY` + +Whether to install the Python executable into the Windows registry. + ### `UV_PYTHON_PREFERENCE` Whether uv should prefer system or managed Python versions. From 35e2f67b5e4529050e615f6e812d0d727afc20d3 Mon Sep 17 00:00:00 2001 From: samypr100 <3933065+samypr100@users.noreply.github.com> Date: Sat, 21 Jun 2025 15:42:28 -0400 Subject: [PATCH 239/349] feat(docker): set default `UV_TOOL_BIN_DIR` on docker images (#13391) Closes #13057 Sets `UV_TOOL_BIN_DIR` to `/usr/local/bin` for all derived images to allow `uv tool install` to work out of the box. Note, when the default image user is overwritten (e.g. `USER `) by a less privileged one, an alternative writable location would now need to be set by downstream consumers to prevent issues, hence I'm labeling this as a breaking change for 0.8.x release. Relates to https://github.com/astral-sh/uv-docker-example/pull/55 Each image was tested to work with uv tool with `UV_TOOL_BIN_DIR` set to `/usr/local/bin` with the default root user and alternative non-root users to confirm breaking nature of the change. --- .github/workflows/build-docker.yml | 1 + docs/guides/integration/docker.md | 3 +++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 843ee8dfb..3c080b63f 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -225,6 +225,7 @@ jobs: cat < Dockerfile FROM ${BASE_IMAGE} COPY --from=${{ env.UV_GHCR_IMAGE }}:latest /uv /uvx /usr/local/bin/ + ENV UV_TOOL_BIN_DIR="/usr/local/bin" ENTRYPOINT [] CMD ["/usr/local/bin/uv"] EOF diff --git a/docs/guides/integration/docker.md b/docs/guides/integration/docker.md index 2ea14c9b0..a75228723 100644 --- a/docs/guides/integration/docker.md +++ b/docs/guides/integration/docker.md @@ -77,6 +77,9 @@ As with the distroless image, each derived image is published with uv version ta `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.7.22-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. + For more details, see the [GitHub Container](https://github.com/astral-sh/uv/pkgs/container/uv) page. From c8925e2541ae451148cdd2d4b12fa7904004dceb Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 26 Jun 2025 12:22:38 -0500 Subject: [PATCH 240/349] Require `--global` for removal of the global Python pin (#14169) While reviewing https://github.com/astral-sh/uv/pull/14107, @oconnor663 pointed out a bug where we allow `uv python pin --rm` to delete the global pin without the `--global` flag. I think that shouldn't be allowed? I'm not 100% certain though. --- Cargo.lock | 1 - crates/uv-python/src/version_files.rs | 16 +++++++++++++ crates/uv/Cargo.toml | 1 - crates/uv/src/commands/python/pin.rs | 22 ++++++++++++------ crates/uv/tests/it/python_pin.rs | 33 +++++++++++++++++++++++++-- 5 files changed, 62 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8a95f655d..2963b6374 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4696,7 +4696,6 @@ dependencies = [ "uv-client", "uv-configuration", "uv-console", - "uv-dirs", "uv-dispatch", "uv-distribution", "uv-distribution-filename", diff --git a/crates/uv-python/src/version_files.rs b/crates/uv-python/src/version_files.rs index a9cd05b7e..595a18f0f 100644 --- a/crates/uv-python/src/version_files.rs +++ b/crates/uv-python/src/version_files.rs @@ -217,6 +217,19 @@ impl PythonVersionFile { } } + /// Create a new representation of a global Python version file. + /// + /// Returns [`None`] if the user configuration directory cannot be determined. + pub fn global() -> Option { + let path = user_uv_config_dir()?.join(PYTHON_VERSION_FILENAME); + Some(Self::new(path)) + } + + /// Returns `true` if the version file is a global version file. + pub fn is_global(&self) -> bool { + PythonVersionFile::global().is_some_and(|global| self.path() == global.path()) + } + /// Return the first request declared in the file, if any. pub fn version(&self) -> Option<&PythonRequest> { self.versions.first() @@ -260,6 +273,9 @@ impl PythonVersionFile { /// Update the version file on the file system. pub async fn write(&self) -> Result<(), std::io::Error> { debug!("Writing Python versions to `{}`", self.path.display()); + if let Some(parent) = self.path.parent() { + fs_err::tokio::create_dir_all(parent).await?; + } fs::tokio::write( &self.path, self.versions diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml index 975495904..ff389f033 100644 --- a/crates/uv/Cargo.toml +++ b/crates/uv/Cargo.toml @@ -24,7 +24,6 @@ uv-cli = { workspace = true } uv-client = { workspace = true } uv-configuration = { workspace = true } uv-console = { workspace = true } -uv-dirs = { workspace = true } uv-dispatch = { workspace = true } uv-distribution = { workspace = true } uv-distribution-filename = { workspace = true } diff --git a/crates/uv/src/commands/python/pin.rs b/crates/uv/src/commands/python/pin.rs index f4d10cdfa..0e78e6b5c 100644 --- a/crates/uv/src/commands/python/pin.rs +++ b/crates/uv/src/commands/python/pin.rs @@ -9,7 +9,6 @@ use tracing::debug; use uv_cache::Cache; use uv_client::BaseClientBuilder; use uv_configuration::{DependencyGroupsWithDefaults, PreviewMode}; -use uv_dirs::user_uv_config_dir; use uv_fs::Simplified; use uv_python::{ EnvironmentPreference, PYTHON_VERSION_FILENAME, PythonDownloads, PythonInstallation, @@ -72,10 +71,20 @@ pub(crate) async fn pin( } bail!("No Python version file found"); }; + + if !global && file.is_global() { + bail!("No Python version file found; use `--rm --global` to remove the global pin"); + } + fs_err::tokio::remove_file(file.path()).await?; writeln!( printer.stdout(), - "Removed Python version file at `{}`", + "Removed {} at `{}`", + if global { + "global Python pin" + } else { + "Python version file" + }, file.path().user_display() )?; return Ok(ExitStatus::Success); @@ -194,12 +203,11 @@ pub(crate) async fn pin( let existing = version_file.ok().flatten(); // TODO(zanieb): Allow updating the discovered version file with an `--update` flag. let new = if global { - let Some(config_dir) = user_uv_config_dir() else { - return Err(anyhow::anyhow!("No user-level config directory found.")); + let Some(new) = PythonVersionFile::global() else { + // TODO(zanieb): We should find a nice way to surface that as an error + bail!("Failed to determine directory for global Python pin"); }; - fs_err::tokio::create_dir_all(&config_dir).await?; - PythonVersionFile::new(config_dir.join(PYTHON_VERSION_FILENAME)) - .with_versions(vec![request]) + new.with_versions(vec![request]) } else { PythonVersionFile::new(project_dir.join(PYTHON_VERSION_FILENAME)) .with_versions(vec![request]) diff --git a/crates/uv/tests/it/python_pin.rs b/crates/uv/tests/it/python_pin.rs index cf8849f42..97093831c 100644 --- a/crates/uv/tests/it/python_pin.rs +++ b/crates/uv/tests/it/python_pin.rs @@ -847,7 +847,7 @@ fn python_pin_rm() { error: No Python version file found "); - // Remove the local pin + // Create and remove a local pin context.python_pin().arg("3.12").assert().success(); uv_snapshot!(context.filters(), context.python_pin().arg("--rm"), @r" success: true @@ -884,12 +884,41 @@ fn python_pin_rm() { .arg("--global") .assert() .success(); + uv_snapshot!(context.filters(), context.python_pin().arg("--rm").arg("--global"), @r" success: true exit_code: 0 ----- stdout ----- - Removed Python version file at `[UV_USER_CONFIG_DIR]/.python-version` + Removed global Python pin at `[UV_USER_CONFIG_DIR]/.python-version` ----- stderr ----- "); + + // Add the global pin again + context + .python_pin() + .arg("3.12") + .arg("--global") + .assert() + .success(); + + // Remove the local pin + uv_snapshot!(context.filters(), context.python_pin().arg("--rm"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Removed Python version file at `.python-version` + + ----- stderr ----- + "); + + // The global pin should not be removed without `--global` + uv_snapshot!(context.filters(), context.python_pin().arg("--rm"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: No Python version file found; use `--rm --global` to remove the global pin + "); } From e4c04af32d297693801582999178775b7627f9d8 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 26 Jun 2025 22:45:45 -0400 Subject: [PATCH 241/349] Bump `--python-platform linux` to `manylinux_2_28` (#14300) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Right now, `--python-platform linux` to defaults to `manylinux_2_17`. Defaulting to `manylinux_2_17` causes some problems for users, since it means we can't use (e.g.) `manylinux_2_28` wheels, and end up having to build from source. cibuildwheel made `manylinux_2_28` their default in https://github.com/pypa/cibuildwheel/pull/1988, and there's a lot of discussion in https://github.com/pypa/cibuildwheel/issues/1772 and https://github.com/pypa/cibuildwheel/issues/2047. In short, the `manylinux_2014` image is EOL, and the vast majority of consumers now run at least glibc 2.28 (https://mayeut.github.io/manylinux-timeline/): ![Screenshot 2025-06-26 at 7 47 23 PM](https://github.com/user-attachments/assets/2672d91b-f9eb-4442-b680-7e4cd7cade91) Note that this only changes the _default_. Users can still compile against `manylinux_2_17` by specifying it. --- crates/uv-configuration/src/target_triple.rs | 8 ++++---- crates/uv/tests/it/pip_compile.rs | 2 +- docs/reference/cli.md | 16 ++++++++-------- uv.schema.json | 4 ++-- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/crates/uv-configuration/src/target_triple.rs b/crates/uv-configuration/src/target_triple.rs index 81499deff..842fb39a7 100644 --- a/crates/uv-configuration/src/target_triple.rs +++ b/crates/uv-configuration/src/target_triple.rs @@ -33,7 +33,7 @@ pub enum TargetTriple { #[serde(rename = "i686-pc-windows-msvc")] I686PcWindowsMsvc, - /// An x86 Linux target. Equivalent to `x86_64-manylinux_2_17`. + /// An x86 Linux target. Equivalent to `x86_64-manylinux_2_28`. #[cfg_attr(feature = "clap", value(name = "x86_64-unknown-linux-gnu"))] #[serde(rename = "x86_64-unknown-linux-gnu")] #[serde(alias = "x8664-unknown-linux-gnu")] @@ -56,7 +56,7 @@ pub enum TargetTriple { #[serde(alias = "x8664-apple-darwin")] X8664AppleDarwin, - /// An ARM64 Linux target. Equivalent to `aarch64-manylinux_2_17`. + /// An ARM64 Linux target. Equivalent to `aarch64-manylinux_2_28`. #[cfg_attr(feature = "clap", value(name = "aarch64-unknown-linux-gnu"))] #[serde(rename = "aarch64-unknown-linux-gnu")] Aarch64UnknownLinuxGnu, @@ -240,7 +240,7 @@ impl TargetTriple { Self::Linux | Self::X8664UnknownLinuxGnu => Platform::new( Os::Manylinux { major: 2, - minor: 17, + minor: 28, }, Arch::X86_64, ), @@ -262,7 +262,7 @@ impl TargetTriple { Self::Aarch64UnknownLinuxGnu => Platform::new( Os::Manylinux { major: 2, - minor: 17, + minor: 28, }, Arch::Aarch64, ), diff --git a/crates/uv/tests/it/pip_compile.rs b/crates/uv/tests/it/pip_compile.rs index b99be1296..f04c16b86 100644 --- a/crates/uv/tests/it/pip_compile.rs +++ b/crates/uv/tests/it/pip_compile.rs @@ -14728,7 +14728,7 @@ fn invalid_platform() -> Result<()> { uv_snapshot!(context .pip_compile() .arg("--python-platform") - .arg("linux") + .arg("x86_64-manylinux_2_17") .arg("requirements.in"), @r" success: false exit_code: 1 diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 5aea00f32..aa6213eff 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -1736,10 +1736,10 @@ interpreter. Use --universal to display the tree for all platforms,
  • macos: An alias for aarch64-apple-darwin, the default target for macOS
  • x86_64-pc-windows-msvc: A 64-bit x86 Windows target
  • i686-pc-windows-msvc: A 32-bit x86 Windows target
  • -
  • x86_64-unknown-linux-gnu: An x86 Linux target. Equivalent to x86_64-manylinux_2_17
  • +
  • x86_64-unknown-linux-gnu: An x86 Linux target. Equivalent to x86_64-manylinux_2_28
  • aarch64-apple-darwin: An ARM-based macOS target, as seen on Apple Silicon devices
  • x86_64-apple-darwin: An x86 macOS target
  • -
  • aarch64-unknown-linux-gnu: An ARM64 Linux target. Equivalent to aarch64-manylinux_2_17
  • +
  • aarch64-unknown-linux-gnu: An ARM64 Linux target. Equivalent to aarch64-manylinux_2_28
  • aarch64-unknown-linux-musl: An ARM64 Linux target
  • x86_64-unknown-linux-musl: An x86_64 Linux target
  • x86_64-manylinux2014: An x86_64 target for the manylinux2014 platform. Equivalent to x86_64-manylinux_2_17
  • @@ -3490,10 +3490,10 @@ by --python-version.

  • macos: An alias for aarch64-apple-darwin, the default target for macOS
  • x86_64-pc-windows-msvc: A 64-bit x86 Windows target
  • i686-pc-windows-msvc: A 32-bit x86 Windows target
  • -
  • x86_64-unknown-linux-gnu: An x86 Linux target. Equivalent to x86_64-manylinux_2_17
  • +
  • x86_64-unknown-linux-gnu: An x86 Linux target. Equivalent to x86_64-manylinux_2_28
  • aarch64-apple-darwin: An ARM-based macOS target, as seen on Apple Silicon devices
  • x86_64-apple-darwin: An x86 macOS target
  • -
  • aarch64-unknown-linux-gnu: An ARM64 Linux target. Equivalent to aarch64-manylinux_2_17
  • +
  • aarch64-unknown-linux-gnu: An ARM64 Linux target. Equivalent to aarch64-manylinux_2_28
  • aarch64-unknown-linux-musl: An ARM64 Linux target
  • x86_64-unknown-linux-musl: An x86_64 Linux target
  • x86_64-manylinux2014: An x86_64 target for the manylinux2014 platform. Equivalent to x86_64-manylinux_2_17
  • @@ -3747,10 +3747,10 @@ be used with caution, as it can modify the system Python installation.

  • macos: An alias for aarch64-apple-darwin, the default target for macOS
  • x86_64-pc-windows-msvc: A 64-bit x86 Windows target
  • i686-pc-windows-msvc: A 32-bit x86 Windows target
  • -
  • x86_64-unknown-linux-gnu: An x86 Linux target. Equivalent to x86_64-manylinux_2_17
  • +
  • x86_64-unknown-linux-gnu: An x86 Linux target. Equivalent to x86_64-manylinux_2_28
  • aarch64-apple-darwin: An ARM-based macOS target, as seen on Apple Silicon devices
  • x86_64-apple-darwin: An x86 macOS target
  • -
  • aarch64-unknown-linux-gnu: An ARM64 Linux target. Equivalent to aarch64-manylinux_2_17
  • +
  • aarch64-unknown-linux-gnu: An ARM64 Linux target. Equivalent to aarch64-manylinux_2_28
  • aarch64-unknown-linux-musl: An ARM64 Linux target
  • x86_64-unknown-linux-musl: An x86_64 Linux target
  • x86_64-manylinux2014: An x86_64 target for the manylinux2014 platform. Equivalent to x86_64-manylinux_2_17
  • @@ -4029,10 +4029,10 @@ should be used with caution, as it can modify the system Python installation.

    macos: An alias for aarch64-apple-darwin, the default target for macOS
  • x86_64-pc-windows-msvc: A 64-bit x86 Windows target
  • i686-pc-windows-msvc: A 32-bit x86 Windows target
  • -
  • x86_64-unknown-linux-gnu: An x86 Linux target. Equivalent to x86_64-manylinux_2_17
  • +
  • x86_64-unknown-linux-gnu: An x86 Linux target. Equivalent to x86_64-manylinux_2_28
  • aarch64-apple-darwin: An ARM-based macOS target, as seen on Apple Silicon devices
  • x86_64-apple-darwin: An x86 macOS target
  • -
  • aarch64-unknown-linux-gnu: An ARM64 Linux target. Equivalent to aarch64-manylinux_2_17
  • +
  • aarch64-unknown-linux-gnu: An ARM64 Linux target. Equivalent to aarch64-manylinux_2_28
  • aarch64-unknown-linux-musl: An ARM64 Linux target
  • x86_64-unknown-linux-musl: An x86_64 Linux target
  • x86_64-manylinux2014: An x86_64 target for the manylinux2014 platform. Equivalent to x86_64-manylinux_2_17
  • diff --git a/uv.schema.json b/uv.schema.json index e418f37f0..ba89f65f4 100644 --- a/uv.schema.json +++ b/uv.schema.json @@ -2114,7 +2114,7 @@ "const": "i686-pc-windows-msvc" }, { - "description": "An x86 Linux target. Equivalent to `x86_64-manylinux_2_17`.", + "description": "An x86 Linux target. Equivalent to `x86_64-manylinux_2_28`.", "type": "string", "const": "x86_64-unknown-linux-gnu" }, @@ -2129,7 +2129,7 @@ "const": "x86_64-apple-darwin" }, { - "description": "An ARM64 Linux target. Equivalent to `aarch64-manylinux_2_17`.", + "description": "An ARM64 Linux target. Equivalent to `aarch64-manylinux_2_28`.", "type": "string", "const": "aarch64-unknown-linux-gnu" }, From c3d7d3899c435d528d34f242a3750aeed1bb8c50 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 10 Jul 2025 22:05:49 -0400 Subject: [PATCH 242/349] Default to `--workspace` when adding subdirectories (#14529) If `--workspace` is provided, we add all paths as workspace members. If `--no-workspace` is provided, we add all paths as direct path dependencies. If neither is provided, then we add any paths that are under the workspace root as workspace members, and the rest as direct path dependencies. Closes #14524. --- crates/uv-cli/src/lib.rs | 15 +- crates/uv/src/commands/project/add.rs | 80 +++-- crates/uv/src/settings.rs | 5 +- crates/uv/tests/it/edit.rs | 454 +++++++++++++++++++++++++- docs/reference/cli.md | 10 +- 5 files changed, 522 insertions(+), 42 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 94b79558d..4c01fd780 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -3726,10 +3726,19 @@ pub struct AddArgs { /// Add the dependency as a workspace member. /// - /// When used with a path dependency, the package will be added to the workspace's `members` - /// list in the root `pyproject.toml` file. - #[arg(long)] + /// By default, uv will add path dependencies that are within the workspace directory + /// as workspace members. When used with a path dependency, the package will be added + /// to the workspace's `members` list in the root `pyproject.toml` file. + #[arg(long, overrides_with = "no_workspace")] pub workspace: bool, + + /// Don't add the dependency as a workspace member. + /// + /// By default, when adding a dependency that's a local path and is within the workspace + /// directory, uv will add it as a workspace member; pass `--no-workspace` to add the package + /// as direct path dependency instead. + #[arg(long, overrides_with = "workspace")] + pub no_workspace: bool, } #[derive(Args)] diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index d65866483..28cc2dcd5 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -83,7 +83,7 @@ pub(crate) async fn add( extras_of_dependency: Vec, package: Option, python: Option, - workspace: bool, + workspace: Option, install_mirrors: PythonInstallMirrors, settings: ResolverInstallerSettings, network_settings: NetworkSettings, @@ -497,16 +497,41 @@ pub(crate) async fn add( // Track modification status, for reverts. let mut modified = false; - // If `--workspace` is provided, add any members to the `workspace` section of the + // Determine whether to use workspace mode. + let use_workspace = match workspace { + Some(workspace) => workspace, + None => { + // Check if we're in a project (not a script), and if any requirements are path + // dependencies within the workspace. + if let AddTarget::Project(ref project, _) = target { + let workspace_root = project.workspace().install_path(); + requirements.iter().any(|req| { + if let RequirementSource::Directory { install_path, .. } = &req.source { + let absolute_path = if install_path.is_absolute() { + install_path.to_path_buf() + } else { + project.root().join(install_path) + }; + absolute_path.starts_with(workspace_root) + } else { + false + } + }) + } else { + false + } + } + }; + + // If workspace mode is enabled, add any members to the `workspace` section of the // `pyproject.toml` file. - if workspace { + if use_workspace { let AddTarget::Project(project, python_target) = target else { unreachable!("`--workspace` and `--script` are conflicting options"); }; - let workspace = project.workspace(); let mut toml = PyProjectTomlMut::from_toml( - &workspace.pyproject_toml().raw, + &project.workspace().pyproject_toml().raw, DependencyTarget::PyProjectToml, )?; @@ -519,21 +544,32 @@ pub(crate) async fn add( project.root().join(install_path) }; - // Check if the path is not already included in the workspace. - if !workspace.includes(&absolute_path)? { - let relative_path = absolute_path - .strip_prefix(workspace.install_path()) - .unwrap_or(&absolute_path); - - toml.add_workspace(relative_path)?; - modified |= true; - - writeln!( - printer.stderr(), - "Added `{}` to workspace members", - relative_path.user_display().cyan() - )?; + // Either `--workspace` was provided explicitly, or it was omitted but the path is + // within the workspace root. + let use_workspace = workspace.unwrap_or_else(|| { + absolute_path.starts_with(project.workspace().install_path()) + }); + if !use_workspace { + continue; } + + // If the project is already a member of the workspace, skip it. + if project.workspace().includes(&absolute_path)? { + continue; + } + + let relative_path = absolute_path + .strip_prefix(project.workspace().install_path()) + .unwrap_or(&absolute_path); + + toml.add_workspace(relative_path)?; + modified |= true; + + writeln!( + printer.stderr(), + "Added `{}` to workspace members", + relative_path.user_display().cyan() + )?; } } @@ -542,7 +578,7 @@ pub(crate) async fn add( target = if modified { let workspace_content = toml.to_string(); fs_err::write( - workspace.install_path().join("pyproject.toml"), + project.workspace().install_path().join("pyproject.toml"), &workspace_content, )?; @@ -747,13 +783,13 @@ fn edits( .and_then(|tool| tool.uv.as_ref()) .and_then(|uv| uv.sources.as_ref()) .map(ToolUvSources::inner); - let workspace = project + let is_workspace_member = project .workspace() .packages() .contains_key(&requirement.name); resolve_requirement( requirement, - workspace, + is_workspace_member, editable, index.cloned(), rev.map(ToString::to_string), diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index b246f228f..bf3bca4a4 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -1351,7 +1351,7 @@ pub(crate) struct AddSettings { pub(crate) package: Option, pub(crate) script: Option, pub(crate) python: Option, - pub(crate) workspace: bool, + pub(crate) workspace: Option, pub(crate) install_mirrors: PythonInstallMirrors, pub(crate) refresh: Refresh, pub(crate) indexes: Vec, @@ -1390,6 +1390,7 @@ impl AddSettings { script, python, workspace, + no_workspace, } = args; let dependency_type = if let Some(extra) = optional { @@ -1490,7 +1491,7 @@ impl AddSettings { package, script, python: python.and_then(Maybe::into_option), - workspace, + workspace: flag(workspace, no_workspace, "workspace"), editable: flag(editable, no_editable, "editable"), extras: extra.unwrap_or_default(), refresh: Refresh::from(refresh), diff --git a/crates/uv/tests/it/edit.rs b/crates/uv/tests/it/edit.rs index ddaed434f..ccc0cabf2 100644 --- a/crates/uv/tests/it/edit.rs +++ b/crates/uv/tests/it/edit.rs @@ -2491,9 +2491,9 @@ fn add_workspace_path() -> Result<()> { Ok(()) } -/// Add a path dependency. +/// Add a path dependency, which should be implicitly added to the workspace. #[test] -fn add_path() -> Result<()> { +fn add_path_implicit_workspace() -> Result<()> { let context = TestContext::new("3.12"); let workspace = context.temp_dir.child("workspace"); @@ -2533,6 +2533,7 @@ fn add_path() -> Result<()> { ----- stderr ----- Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] Creating virtual environment at: .venv + Added `packages/child` to workspace members Resolved 2 packages in [TIME] Prepared 1 package in [TIME] Installed 1 package in [TIME] @@ -2545,7 +2546,134 @@ fn add_path() -> Result<()> { filters => context.filters(), }, { assert_snapshot!( - pyproject_toml, @r###" + pyproject_toml, @r#" + [project] + name = "parent" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [ + "child", + ] + + [tool.uv.workspace] + members = [ + "packages/child", + ] + + [tool.uv.sources] + child = { workspace = true } + "# + ); + }); + + // `uv add` implies a full lock and sync, including development dependencies. + let lock = fs_err::read_to_string(workspace.join("uv.lock"))?; + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + lock, @r#" + version = 1 + revision = 2 + requires-python = ">=3.12" + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [manifest] + members = [ + "child", + "parent", + ] + + [[package]] + name = "child" + version = "0.1.0" + source = { editable = "packages/child" } + + [[package]] + name = "parent" + version = "0.1.0" + source = { virtual = "." } + dependencies = [ + { name = "child" }, + ] + + [package.metadata] + requires-dist = [{ name = "child", editable = "packages/child" }] + "# + ); + }); + + // Install from the lockfile. + uv_snapshot!(context.filters(), context.sync().arg("--frozen").current_dir(workspace.path()), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Audited 1 package in [TIME] + "); + + Ok(()) +} + +/// Add a path dependency with `--no-workspace`, which should not be added to the workspace. +#[test] +fn add_path_no_workspace() -> Result<()> { + let context = TestContext::new("3.12"); + + let workspace = context.temp_dir.child("workspace"); + workspace.child("pyproject.toml").write_str(indoc! {r#" + [project] + name = "parent" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + "#})?; + + let child = workspace.child("packages").child("child"); + child.child("pyproject.toml").write_str(indoc! {r#" + [project] + name = "child" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + + [build-system] + requires = ["hatchling"] + build-backend = "hatchling.build" + "#})?; + workspace + .child("packages") + .child("child") + .child("src") + .child("child") + .child("__init__.py") + .touch()?; + + uv_snapshot!(context.filters(), context.add().arg(Path::new("packages").join("child")).current_dir(workspace.path()).arg("--no-workspace"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + Creating virtual environment at: .venv + Resolved 2 packages in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + child==0.1.0 (from file://[TEMP_DIR]/workspace/packages/child) + "); + + let pyproject_toml = fs_err::read_to_string(workspace.join("pyproject.toml"))?; + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + pyproject_toml, @r#" [project] name = "parent" version = "0.1.0" @@ -2556,7 +2684,7 @@ fn add_path() -> Result<()> { [tool.uv.sources] child = { path = "packages/child" } - "### + "# ); }); @@ -2607,6 +2735,110 @@ fn add_path() -> Result<()> { Ok(()) } +/// Add a path dependency in an adjacent directory, which should not be added to the workspace. +#[test] +fn add_path_adjacent_directory() -> Result<()> { + let context = TestContext::new("3.12"); + + let project = context.temp_dir.child("project"); + project.child("pyproject.toml").write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + "#})?; + + let dependency = context.temp_dir.child("dependency"); + dependency.child("pyproject.toml").write_str(indoc! {r#" + [project] + name = "dependency" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + + [build-system] + requires = ["hatchling"] + build-backend = "hatchling.build" + "#})?; + dependency + .child("src") + .child("dependency") + .child("__init__.py") + .touch()?; + + uv_snapshot!(context.filters(), context.add().arg(dependency.path()).current_dir(project.path()), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + Creating virtual environment at: .venv + Resolved 2 packages in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + dependency==0.1.0 (from file://[TEMP_DIR]/dependency) + "); + + let pyproject_toml = fs_err::read_to_string(project.join("pyproject.toml"))?; + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + pyproject_toml, @r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [ + "dependency", + ] + + [tool.uv.sources] + dependency = { path = "../dependency" } + "# + ); + }); + + // `uv add` implies a full lock and sync, including development dependencies. + let lock = fs_err::read_to_string(project.join("uv.lock"))?; + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + lock, @r#" + version = 1 + revision = 2 + requires-python = ">=3.12" + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [[package]] + name = "dependency" + version = "0.1.0" + source = { directory = "../dependency" } + + [[package]] + name = "project" + version = "0.1.0" + source = { virtual = "." } + dependencies = [ + { name = "dependency" }, + ] + + [package.metadata] + requires-dist = [{ name = "dependency", directory = "../dependency" }] + "# + ); + }); + + Ok(()) +} + /// Update a requirement, modifying the source and extras. #[test] #[cfg(feature = "git")] @@ -7249,7 +7481,7 @@ fn fail_to_add_revert_project() -> Result<()> { .child("setup.py") .write_str("1/0")?; - uv_snapshot!(context.filters(), context.add().arg("./child"), @r#" + uv_snapshot!(context.filters(), context.add().arg("./child").arg("--no-workspace"), @r#" success: false exit_code: 1 ----- stdout ----- @@ -7351,7 +7583,7 @@ fn fail_to_edit_revert_project() -> Result<()> { .child("setup.py") .write_str("1/0")?; - uv_snapshot!(context.filters(), context.add().arg("./child"), @r#" + uv_snapshot!(context.filters(), context.add().arg("./child").arg("--no-workspace"), @r#" success: false exit_code: 1 ----- stdout ----- @@ -7460,7 +7692,7 @@ fn fail_to_add_revert_workspace_root() -> Result<()> { .child("setup.py") .write_str("1/0")?; - uv_snapshot!(context.filters(), context.add().arg("--workspace").arg("./broken"), @r#" + uv_snapshot!(context.filters(), context.add().arg("./broken"), @r#" success: false exit_code: 1 ----- stdout ----- @@ -7575,7 +7807,7 @@ fn fail_to_add_revert_workspace_member() -> Result<()> { .child("setup.py") .write_str("1/0")?; - uv_snapshot!(context.filters(), context.add().current_dir(&project).arg("--workspace").arg("../broken"), @r#" + uv_snapshot!(context.filters(), context.add().current_dir(&project).arg("../broken"), @r#" success: false exit_code: 1 ----- stdout ----- @@ -12928,12 +13160,12 @@ fn add_path_with_existing_workspace() -> Result<()> { dependencies = [] "#})?; - // Add the dependency with `--workspace` flag from the project directory. + // Add the dependency from the project directory. It should automatically be added as a + // workspace member, since it's in the same directory as the workspace. uv_snapshot!(context.filters(), context .add() .current_dir(&project_dir) - .arg("../dep") - .arg("--workspace"), @r" + .arg("../dep"), @r" success: true exit_code: 0 ----- stdout ----- @@ -13044,3 +13276,203 @@ fn add_path_with_workspace() -> Result<()> { Ok(()) } + +/// Add a path dependency within the workspace directory without --workspace flag. +/// It should automatically be added as a workspace member. +#[test] +fn add_path_within_workspace_defaults_to_workspace() -> Result<()> { + let context = TestContext::new("3.12"); + + let workspace_toml = context.temp_dir.child("pyproject.toml"); + workspace_toml.write_str(indoc! {r#" + [project] + name = "parent" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + + [tool.uv.workspace] + members = [] + "#})?; + + let dep_dir = context.temp_dir.child("dep"); + dep_dir.child("pyproject.toml").write_str(indoc! {r#" + [project] + name = "dep" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + "#})?; + + // Add the dependency without --workspace flag - it should still be added as workspace member + // since it's within the workspace directory. + uv_snapshot!(context.filters(), context + .add() + .arg("./dep"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Added `dep` to workspace members + Resolved 2 packages in [TIME] + Audited in [TIME] + "); + + let pyproject_toml = context.read("pyproject.toml"); + assert_snapshot!( + pyproject_toml, @r#" + [project] + name = "parent" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [ + "dep", + ] + + [tool.uv.workspace] + members = [ + "dep", + ] + + [tool.uv.sources] + dep = { workspace = true } + "# + ); + + Ok(()) +} + +/// Add a path dependency within the workspace directory with --no-workspace flag. +/// It should be added as a direct path dependency. +#[test] +fn add_path_with_no_workspace() -> Result<()> { + let context = TestContext::new("3.12"); + + let workspace_toml = context.temp_dir.child("pyproject.toml"); + workspace_toml.write_str(indoc! {r#" + [project] + name = "parent" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + + [tool.uv.workspace] + members = [] + "#})?; + + let dep_dir = context.temp_dir.child("dep"); + dep_dir.child("pyproject.toml").write_str(indoc! {r#" + [project] + name = "dep" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + "#})?; + + // Add the dependency with --no-workspace flag - it should be added as direct path dependency. + uv_snapshot!(context.filters(), context + .add() + .arg("./dep") + .arg("--no-workspace"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + Audited in [TIME] + "); + + let pyproject_toml = context.read("pyproject.toml"); + assert_snapshot!( + pyproject_toml, @r#" + [project] + name = "parent" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [ + "dep", + ] + + [tool.uv.workspace] + members = [] + + [tool.uv.sources] + dep = { path = "dep" } + "# + ); + + Ok(()) +} + +/// Add a path dependency outside the workspace directory. +/// It should be added as a direct path dependency, not a workspace member. +#[test] +fn add_path_outside_workspace_no_default() -> Result<()> { + let context = TestContext::new("3.12"); + + // Create a workspace directory + let workspace_dir = context.temp_dir.child("workspace"); + workspace_dir.create_dir_all()?; + + let workspace_toml = workspace_dir.child("pyproject.toml"); + workspace_toml.write_str(indoc! {r#" + [project] + name = "parent" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + + [tool.uv.workspace] + members = [] + "#})?; + + // Create a dependency outside the workspace + let dep_dir = context.temp_dir.child("external_dep"); + dep_dir.child("pyproject.toml").write_str(indoc! {r#" + [project] + name = "dep" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + "#})?; + + // Add the dependency without --workspace flag - it should be a direct path dependency + // since it's outside the workspace directory. + uv_snapshot!(context.filters(), context + .add() + .current_dir(&workspace_dir) + .arg("../external_dep"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + Creating virtual environment at: .venv + Resolved 2 packages in [TIME] + Audited in [TIME] + "); + + let pyproject_toml = fs_err::read_to_string(workspace_toml)?; + assert_snapshot!( + pyproject_toml, @r#" + [project] + name = "parent" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [ + "dep", + ] + + [tool.uv.workspace] + members = [] + + [tool.uv.sources] + dep = { path = "../external_dep" } + "# + ); + + Ok(()) +} diff --git a/docs/reference/cli.md b/docs/reference/cli.md index aa6213eff..881c96697 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -535,7 +535,9 @@ uv add [OPTIONS] >

    May also be set with the UV_NO_PROGRESS environment variable.

    --no-python-downloads

    Disable automatic downloads of Python.

    --no-sources

    Ignore the tool.uv.sources table when resolving dependencies. Used to lock against the standards-compliant, publishable package metadata, as opposed to using any workspace, Git, URL, or local path sources

    --no-sync

    Avoid syncing the virtual environment

    -

    May also be set with the UV_NO_SYNC environment variable.

    --offline

    Disable network access.

    +

    May also be set with the UV_NO_SYNC environment variable.

    --no-workspace

    Don't add the dependency as a workspace member.

    +

    By default, when adding a dependency that's a local path and is within the workspace directory, uv will add it as a workspace member; pass --no-workspace to add the package as direct path dependency instead.

    +
    --offline

    Disable network access.

    When disabled, uv will only use locally cached data and locally available files.

    May also be set with the UV_OFFLINE environment variable.

    --optional optional

    Add the requirements to the package's optional dependencies for the specified extra.

    The group may then be activated when installing the project with the --extra flag.

    @@ -583,7 +585,7 @@ uv add [OPTIONS] >
    --verbose, -v

    Use verbose output.

    You can configure fine-grained logging using the RUST_LOG environment variable. (https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives)

    --workspace

    Add the dependency as a workspace member.

    -

    When used with a path dependency, the package will be added to the workspace's members list in the root pyproject.toml file.

    +

    By default, uv will add path dependencies that are within the workspace directory as workspace members. When used with a path dependency, the package will be added to the workspace's members list in the root pyproject.toml file.

    ## uv remove @@ -1154,10 +1156,10 @@ environment in the project.

  • macos: An alias for aarch64-apple-darwin, the default target for macOS
  • x86_64-pc-windows-msvc: A 64-bit x86 Windows target
  • i686-pc-windows-msvc: A 32-bit x86 Windows target
  • -
  • x86_64-unknown-linux-gnu: An x86 Linux target. Equivalent to x86_64-manylinux_2_17
  • +
  • x86_64-unknown-linux-gnu: An x86 Linux target. Equivalent to x86_64-manylinux_2_28
  • aarch64-apple-darwin: An ARM-based macOS target, as seen on Apple Silicon devices
  • x86_64-apple-darwin: An x86 macOS target
  • -
  • aarch64-unknown-linux-gnu: An ARM64 Linux target. Equivalent to aarch64-manylinux_2_17
  • +
  • aarch64-unknown-linux-gnu: An ARM64 Linux target. Equivalent to aarch64-manylinux_2_28
  • aarch64-unknown-linux-musl: An ARM64 Linux target
  • x86_64-unknown-linux-musl: An x86_64 Linux target
  • x86_64-manylinux2014: An x86_64 target for the manylinux2014 platform. Equivalent to x86_64-manylinux_2_17
  • From dff9ced40ab2633d32f7e9bcdcb6484500caf621 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 10 Jul 2025 22:20:01 -0400 Subject: [PATCH 243/349] Support conflicting editable settings across groups (#14197) If a user specifies `-e /path/to/dir` and `/path/to/dir` in a `uv pip install` command, we want the editable to "win" (rather than erroring due to conflicting URLs). Unfortunately, this behavior meant that when you requested a package as editable and non-editable in conflicting groups, the editable version was _always_ used. This PR modifies the requisite types to use `Option` rather than `bool` for the `editable` field, so we can determine whether a requirement was explicitly requested as editable, explicitly requested as non-editable, or not specified (as in the case of `/path/to/dir` in a `requirements.txt` file). In the latter case, we allow editables to override the "unspecified" requirement. If a project includes a path dependency twice, once with `editable = true` and once without any `editable` annotation, those are now considered conflicting URLs, and lead to an error, so I've marked this change as breaking. Closes https://github.com/astral-sh/uv/issues/14139. --- crates/uv-distribution-types/src/buildable.rs | 7 +- crates/uv-distribution-types/src/lib.rs | 12 +- .../uv-distribution-types/src/requirement.rs | 32 +- .../src/index/built_wheel_index.rs | 2 +- .../uv-distribution/src/metadata/lowering.rs | 20 +- crates/uv-distribution/src/source/mod.rs | 4 +- crates/uv-installer/src/satisfies.rs | 2 +- crates/uv-pypi-types/src/parsed_url.rs | 29 +- crates/uv-requirements-txt/src/lib.rs | 6 +- crates/uv-requirements-txt/src/requirement.rs | 4 +- ...ts_txt__test__parse-unix-bare-url.txt.snap | 24 +- ...ts_txt__test__parse-unix-editable.txt.snap | 48 ++- ...txt__test__parse-windows-bare-url.txt.snap | 24 +- ...txt__test__parse-windows-editable.txt.snap | 48 ++- crates/uv-requirements/src/source_tree.rs | 2 +- .../src/lock/export/pylock_toml.rs | 8 +- crates/uv-resolver/src/lock/mod.rs | 20 +- crates/uv-resolver/src/resolver/mod.rs | 1 + crates/uv-resolver/src/resolver/urls.rs | 9 +- crates/uv-workspace/src/workspace.rs | 16 +- crates/uv/src/commands/project/sync.rs | 10 +- crates/uv/tests/it/lock.rs | 86 +---- crates/uv/tests/it/sync.rs | 332 ++++++++++++++++++ 23 files changed, 530 insertions(+), 216 deletions(-) diff --git a/crates/uv-distribution-types/src/buildable.rs b/crates/uv-distribution-types/src/buildable.rs index c97bb362f..75997e406 100644 --- a/crates/uv-distribution-types/src/buildable.rs +++ b/crates/uv-distribution-types/src/buildable.rs @@ -124,7 +124,10 @@ impl SourceUrl<'_> { pub fn is_editable(&self) -> bool { matches!( self, - Self::Directory(DirectorySourceUrl { editable: true, .. }) + Self::Directory(DirectorySourceUrl { + editable: Some(true), + .. + }) ) } @@ -210,7 +213,7 @@ impl<'a> From<&'a PathSourceDist> for PathSourceUrl<'a> { pub struct DirectorySourceUrl<'a> { pub url: &'a DisplaySafeUrl, pub install_path: Cow<'a, Path>, - pub editable: bool, + pub editable: Option, } impl std::fmt::Display for DirectorySourceUrl<'_> { diff --git a/crates/uv-distribution-types/src/lib.rs b/crates/uv-distribution-types/src/lib.rs index 1e3ad7eba..0b25669b0 100644 --- a/crates/uv-distribution-types/src/lib.rs +++ b/crates/uv-distribution-types/src/lib.rs @@ -343,9 +343,9 @@ pub struct DirectorySourceDist { /// The absolute path to the distribution which we use for installing. pub install_path: Box, /// Whether the package should be installed in editable mode. - pub editable: bool, + pub editable: Option, /// Whether the package should be built and installed. - pub r#virtual: bool, + pub r#virtual: Option, /// The URL as it was provided by the user. pub url: VerbatimUrl, } @@ -452,8 +452,8 @@ impl Dist { name: PackageName, url: VerbatimUrl, install_path: &Path, - editable: bool, - r#virtual: bool, + editable: Option, + r#virtual: Option, ) -> Result { // Convert to an absolute path. let install_path = path::absolute(install_path)?; @@ -655,7 +655,7 @@ impl SourceDist { /// Returns `true` if the distribution is editable. pub fn is_editable(&self) -> bool { match self { - Self::Directory(DirectorySourceDist { editable, .. }) => *editable, + Self::Directory(DirectorySourceDist { editable, .. }) => editable.unwrap_or(false), _ => false, } } @@ -663,7 +663,7 @@ impl SourceDist { /// Returns `true` if the distribution is virtual. pub fn is_virtual(&self) -> bool { match self { - Self::Directory(DirectorySourceDist { r#virtual, .. }) => *r#virtual, + Self::Directory(DirectorySourceDist { r#virtual, .. }) => r#virtual.unwrap_or(false), _ => false, } } diff --git a/crates/uv-distribution-types/src/requirement.rs b/crates/uv-distribution-types/src/requirement.rs index 432cc4e12..104cf396c 100644 --- a/crates/uv-distribution-types/src/requirement.rs +++ b/crates/uv-distribution-types/src/requirement.rs @@ -429,9 +429,9 @@ pub enum RequirementSource { /// The absolute path to the distribution which we use for installing. install_path: Box, /// For a source tree (a directory), whether to install as an editable. - editable: bool, + editable: Option, /// For a source tree (a directory), whether the project should be built and installed. - r#virtual: bool, + r#virtual: Option, /// The PEP 508 style URL in the format /// `file:///#subdirectory=`. url: VerbatimUrl, @@ -545,7 +545,13 @@ impl RequirementSource { /// Returns `true` if the source is editable. pub fn is_editable(&self) -> bool { - matches!(self, Self::Directory { editable: true, .. }) + matches!( + self, + Self::Directory { + editable: Some(true), + .. + } + ) } /// Returns `true` if the source is empty. @@ -792,11 +798,11 @@ impl From for RequirementSourceWire { r#virtual, url: _, } => { - if editable { + if editable.unwrap_or(false) { Self::Editable { editable: PortablePathBuf::from(install_path), } - } else if r#virtual { + } else if r#virtual.unwrap_or(false) { Self::Virtual { r#virtual: PortablePathBuf::from(install_path), } @@ -908,8 +914,8 @@ impl TryFrom for RequirementSource { ))?; Ok(Self::Directory { install_path: directory, - editable: false, - r#virtual: false, + editable: Some(false), + r#virtual: Some(false), url, }) } @@ -920,8 +926,8 @@ impl TryFrom for RequirementSource { ))?; Ok(Self::Directory { install_path: editable, - editable: true, - r#virtual: false, + editable: Some(true), + r#virtual: Some(false), url, }) } @@ -932,8 +938,8 @@ impl TryFrom for RequirementSource { ))?; Ok(Self::Directory { install_path: r#virtual, - editable: false, - r#virtual: true, + editable: Some(false), + r#virtual: Some(true), url, }) } @@ -980,8 +986,8 @@ mod tests { marker: MarkerTree::TRUE, source: RequirementSource::Directory { install_path: PathBuf::from(path).into_boxed_path(), - editable: false, - r#virtual: false, + editable: Some(false), + r#virtual: Some(false), url: VerbatimUrl::from_absolute_path(path).unwrap(), }, origin: None, diff --git a/crates/uv-distribution/src/index/built_wheel_index.rs b/crates/uv-distribution/src/index/built_wheel_index.rs index fb376d1b4..9752e7e4f 100644 --- a/crates/uv-distribution/src/index/built_wheel_index.rs +++ b/crates/uv-distribution/src/index/built_wheel_index.rs @@ -119,7 +119,7 @@ impl<'a> BuiltWheelIndex<'a> { ) -> Result, Error> { let cache_shard = self.cache.shard( CacheBucket::SourceDistributions, - if source_dist.editable { + if source_dist.editable.unwrap_or(false) { WheelCache::Editable(&source_dist.url).root() } else { WheelCache::Path(&source_dist.url).root() diff --git a/crates/uv-distribution/src/metadata/lowering.rs b/crates/uv-distribution/src/metadata/lowering.rs index 330075842..54782c083 100644 --- a/crates/uv-distribution/src/metadata/lowering.rs +++ b/crates/uv-distribution/src/metadata/lowering.rs @@ -310,15 +310,15 @@ impl LoweredRequirement { RequirementSource::Directory { install_path: install_path.into_boxed_path(), url, - editable: true, - r#virtual: false, + editable: Some(true), + r#virtual: Some(false), } } else { RequirementSource::Directory { install_path: install_path.into_boxed_path(), url, - editable: false, - r#virtual: true, + editable: Some(false), + r#virtual: Some(true), } }; (source, marker) @@ -724,8 +724,8 @@ fn path_source( Ok(RequirementSource::Directory { install_path: install_path.into_boxed_path(), url, - editable: true, - r#virtual: false, + editable, + r#virtual: Some(false), }) } else { // Determine whether the project is a package or virtual. @@ -738,12 +738,14 @@ fn path_source( .unwrap_or(true) }); + // If the project is not a package, treat it as a virtual dependency. + let r#virtual = !is_package; + Ok(RequirementSource::Directory { install_path: install_path.into_boxed_path(), url, - editable: false, - // If a project is not a package, treat it as a virtual dependency. - r#virtual: !is_package, + editable: Some(false), + r#virtual: Some(r#virtual), }) } } else { diff --git a/crates/uv-distribution/src/source/mod.rs b/crates/uv-distribution/src/source/mod.rs index 92d83e6ce..1308e3d77 100644 --- a/crates/uv-distribution/src/source/mod.rs +++ b/crates/uv-distribution/src/source/mod.rs @@ -1060,7 +1060,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { let cache_shard = self.build_context.cache().shard( CacheBucket::SourceDistributions, - if resource.editable { + if resource.editable.unwrap_or(false) { WheelCache::Editable(resource.url).root() } else { WheelCache::Path(resource.url).root() @@ -1173,7 +1173,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { let cache_shard = self.build_context.cache().shard( CacheBucket::SourceDistributions, - if resource.editable { + if resource.editable.unwrap_or(false) { WheelCache::Editable(resource.url).root() } else { WheelCache::Path(resource.url).root() diff --git a/crates/uv-installer/src/satisfies.rs b/crates/uv-installer/src/satisfies.rs index a91676595..b7e824202 100644 --- a/crates/uv-installer/src/satisfies.rs +++ b/crates/uv-installer/src/satisfies.rs @@ -241,7 +241,7 @@ impl RequirementSatisfaction { return Self::Mismatch; }; - if *requested_editable != installed_editable.unwrap_or_default() { + if requested_editable != installed_editable { trace!( "Editable mismatch: {:?} vs. {:?}", *requested_editable, diff --git a/crates/uv-pypi-types/src/parsed_url.rs b/crates/uv-pypi-types/src/parsed_url.rs index 9517dfdc6..57afbcdf9 100644 --- a/crates/uv-pypi-types/src/parsed_url.rs +++ b/crates/uv-pypi-types/src/parsed_url.rs @@ -86,8 +86,8 @@ impl UnnamedRequirementUrl for VerbatimParsedUrl { ParsedUrl::Directory(ParsedDirectoryUrl { url, install_path, - editable: false, - r#virtual: false, + editable: None, + r#virtual: None, }) } else { ParsedUrl::Path(ParsedPathUrl { @@ -118,8 +118,8 @@ impl UnnamedRequirementUrl for VerbatimParsedUrl { ParsedUrl::Directory(ParsedDirectoryUrl { url, install_path, - editable: false, - r#virtual: false, + editable: None, + r#virtual: None, }) } else { ParsedUrl::Path(ParsedPathUrl { @@ -187,7 +187,10 @@ impl ParsedUrl { pub fn is_editable(&self) -> bool { matches!( self, - Self::Directory(ParsedDirectoryUrl { editable: true, .. }) + Self::Directory(ParsedDirectoryUrl { + editable: Some(true), + .. + }) ) } } @@ -226,16 +229,18 @@ pub struct ParsedDirectoryUrl { pub url: DisplaySafeUrl, /// The absolute path to the distribution which we use for installing. pub install_path: Box, - pub editable: bool, - pub r#virtual: bool, + /// Whether the project at the given URL should be installed in editable mode. + pub editable: Option, + /// Whether the project at the given URL should be treated as a virtual package. + pub r#virtual: Option, } impl ParsedDirectoryUrl { /// Construct a [`ParsedDirectoryUrl`] from a path requirement source. pub fn from_source( install_path: Box, - editable: bool, - r#virtual: bool, + editable: Option, + r#virtual: Option, url: DisplaySafeUrl, ) -> Self { Self { @@ -399,8 +404,8 @@ impl TryFrom for ParsedUrl { Ok(Self::Directory(ParsedDirectoryUrl { url, install_path: path.into_boxed_path(), - editable: false, - r#virtual: false, + editable: None, + r#virtual: None, })) } else { Ok(Self::Path(ParsedPathUrl { @@ -445,7 +450,7 @@ impl From<&ParsedDirectoryUrl> for DirectUrl { Self::LocalDirectory { url: value.url.to_string(), dir_info: DirInfo { - editable: value.editable.then_some(true), + editable: value.editable, }, subdirectory: None, } diff --git a/crates/uv-requirements-txt/src/lib.rs b/crates/uv-requirements-txt/src/lib.rs index b734bf8a2..b95875768 100644 --- a/crates/uv-requirements-txt/src/lib.rs +++ b/crates/uv-requirements-txt/src/lib.rs @@ -2064,8 +2064,10 @@ mod test { fragment: None, }, install_path: "/foo/bar", - editable: true, - virtual: false, + editable: Some( + true, + ), + virtual: None, }, ), verbatim: VerbatimUrl { diff --git a/crates/uv-requirements-txt/src/requirement.rs b/crates/uv-requirements-txt/src/requirement.rs index 285753ed8..6c7cf0b52 100644 --- a/crates/uv-requirements-txt/src/requirement.rs +++ b/crates/uv-requirements-txt/src/requirement.rs @@ -90,7 +90,7 @@ impl RequirementsTxtRequirement { version_or_url: Some(uv_pep508::VersionOrUrl::Url(VerbatimParsedUrl { verbatim: url.verbatim, parsed_url: ParsedUrl::Directory(ParsedDirectoryUrl { - editable: true, + editable: Some(true), ..parsed_url }), })), @@ -115,7 +115,7 @@ impl RequirementsTxtRequirement { url: VerbatimParsedUrl { verbatim: requirement.url.verbatim, parsed_url: ParsedUrl::Directory(ParsedDirectoryUrl { - editable: true, + editable: Some(true), ..parsed_url }), }, diff --git a/crates/uv-requirements-txt/src/snapshots/uv_requirements_txt__test__parse-unix-bare-url.txt.snap b/crates/uv-requirements-txt/src/snapshots/uv_requirements_txt__test__parse-unix-bare-url.txt.snap index f2187a1a2..dd03d09bf 100644 --- a/crates/uv-requirements-txt/src/snapshots/uv_requirements_txt__test__parse-unix-bare-url.txt.snap +++ b/crates/uv-requirements-txt/src/snapshots/uv_requirements_txt__test__parse-unix-bare-url.txt.snap @@ -22,8 +22,8 @@ RequirementsTxt { fragment: None, }, install_path: "/scripts/packages/black_editable", - editable: false, - virtual: false, + editable: None, + virtual: None, }, ), verbatim: VerbatimUrl { @@ -72,8 +72,8 @@ RequirementsTxt { fragment: None, }, install_path: "/scripts/packages/black_editable", - editable: false, - virtual: false, + editable: None, + virtual: None, }, ), verbatim: VerbatimUrl { @@ -126,8 +126,8 @@ RequirementsTxt { fragment: None, }, install_path: "/scripts/packages/black_editable", - editable: false, - virtual: false, + editable: None, + virtual: None, }, ), verbatim: VerbatimUrl { @@ -176,8 +176,8 @@ RequirementsTxt { fragment: None, }, install_path: "/scripts/packages/black editable", - editable: false, - virtual: false, + editable: None, + virtual: None, }, ), verbatim: VerbatimUrl { @@ -226,8 +226,8 @@ RequirementsTxt { fragment: None, }, install_path: "/scripts/packages/black editable", - editable: false, - virtual: false, + editable: None, + virtual: None, }, ), verbatim: VerbatimUrl { @@ -276,8 +276,8 @@ RequirementsTxt { fragment: None, }, install_path: "/scripts/packages/black editable", - editable: false, - virtual: false, + editable: None, + virtual: None, }, ), verbatim: VerbatimUrl { diff --git a/crates/uv-requirements-txt/src/snapshots/uv_requirements_txt__test__parse-unix-editable.txt.snap b/crates/uv-requirements-txt/src/snapshots/uv_requirements_txt__test__parse-unix-editable.txt.snap index 222ab6b10..39a4885dc 100644 --- a/crates/uv-requirements-txt/src/snapshots/uv_requirements_txt__test__parse-unix-editable.txt.snap +++ b/crates/uv-requirements-txt/src/snapshots/uv_requirements_txt__test__parse-unix-editable.txt.snap @@ -24,8 +24,10 @@ RequirementsTxt { fragment: None, }, install_path: "/editable", - editable: true, - virtual: false, + editable: Some( + true, + ), + virtual: None, }, ), verbatim: VerbatimUrl { @@ -81,8 +83,10 @@ RequirementsTxt { fragment: None, }, install_path: "/editable", - editable: true, - virtual: false, + editable: Some( + true, + ), + virtual: None, }, ), verbatim: VerbatimUrl { @@ -138,8 +142,10 @@ RequirementsTxt { fragment: None, }, install_path: "/editable", - editable: true, - virtual: false, + editable: Some( + true, + ), + virtual: None, }, ), verbatim: VerbatimUrl { @@ -195,8 +201,10 @@ RequirementsTxt { fragment: None, }, install_path: "/editable", - editable: true, - virtual: false, + editable: Some( + true, + ), + virtual: None, }, ), verbatim: VerbatimUrl { @@ -252,8 +260,10 @@ RequirementsTxt { fragment: None, }, install_path: "/editable", - editable: true, - virtual: false, + editable: Some( + true, + ), + virtual: None, }, ), verbatim: VerbatimUrl { @@ -302,8 +312,10 @@ RequirementsTxt { fragment: None, }, install_path: "/editable[d", - editable: true, - virtual: false, + editable: Some( + true, + ), + virtual: None, }, ), verbatim: VerbatimUrl { @@ -352,8 +364,10 @@ RequirementsTxt { fragment: None, }, install_path: "/editable", - editable: true, - virtual: false, + editable: Some( + true, + ), + virtual: None, }, ), verbatim: VerbatimUrl { @@ -402,8 +416,10 @@ RequirementsTxt { fragment: None, }, install_path: "/editable", - editable: true, - virtual: false, + editable: Some( + true, + ), + virtual: None, }, ), verbatim: VerbatimUrl { diff --git a/crates/uv-requirements-txt/src/snapshots/uv_requirements_txt__test__parse-windows-bare-url.txt.snap b/crates/uv-requirements-txt/src/snapshots/uv_requirements_txt__test__parse-windows-bare-url.txt.snap index 72e1c8635..be90c5c44 100644 --- a/crates/uv-requirements-txt/src/snapshots/uv_requirements_txt__test__parse-windows-bare-url.txt.snap +++ b/crates/uv-requirements-txt/src/snapshots/uv_requirements_txt__test__parse-windows-bare-url.txt.snap @@ -22,8 +22,8 @@ RequirementsTxt { fragment: None, }, install_path: "/scripts/packages/black_editable", - editable: false, - virtual: false, + editable: None, + virtual: None, }, ), verbatim: VerbatimUrl { @@ -72,8 +72,8 @@ RequirementsTxt { fragment: None, }, install_path: "/scripts/packages/black_editable", - editable: false, - virtual: false, + editable: None, + virtual: None, }, ), verbatim: VerbatimUrl { @@ -126,8 +126,8 @@ RequirementsTxt { fragment: None, }, install_path: "/scripts/packages/black_editable", - editable: false, - virtual: false, + editable: None, + virtual: None, }, ), verbatim: VerbatimUrl { @@ -176,8 +176,8 @@ RequirementsTxt { fragment: None, }, install_path: "/scripts/packages/black editable", - editable: false, - virtual: false, + editable: None, + virtual: None, }, ), verbatim: VerbatimUrl { @@ -226,8 +226,8 @@ RequirementsTxt { fragment: None, }, install_path: "/scripts/packages/black editable", - editable: false, - virtual: false, + editable: None, + virtual: None, }, ), verbatim: VerbatimUrl { @@ -276,8 +276,8 @@ RequirementsTxt { fragment: None, }, install_path: "/scripts/packages/black editable", - editable: false, - virtual: false, + editable: None, + virtual: None, }, ), verbatim: VerbatimUrl { diff --git a/crates/uv-requirements-txt/src/snapshots/uv_requirements_txt__test__parse-windows-editable.txt.snap b/crates/uv-requirements-txt/src/snapshots/uv_requirements_txt__test__parse-windows-editable.txt.snap index 84ae22816..dde16b40c 100644 --- a/crates/uv-requirements-txt/src/snapshots/uv_requirements_txt__test__parse-windows-editable.txt.snap +++ b/crates/uv-requirements-txt/src/snapshots/uv_requirements_txt__test__parse-windows-editable.txt.snap @@ -24,8 +24,10 @@ RequirementsTxt { fragment: None, }, install_path: "/editable", - editable: true, - virtual: false, + editable: Some( + true, + ), + virtual: None, }, ), verbatim: VerbatimUrl { @@ -81,8 +83,10 @@ RequirementsTxt { fragment: None, }, install_path: "/editable", - editable: true, - virtual: false, + editable: Some( + true, + ), + virtual: None, }, ), verbatim: VerbatimUrl { @@ -138,8 +142,10 @@ RequirementsTxt { fragment: None, }, install_path: "/editable", - editable: true, - virtual: false, + editable: Some( + true, + ), + virtual: None, }, ), verbatim: VerbatimUrl { @@ -195,8 +201,10 @@ RequirementsTxt { fragment: None, }, install_path: "/editable", - editable: true, - virtual: false, + editable: Some( + true, + ), + virtual: None, }, ), verbatim: VerbatimUrl { @@ -252,8 +260,10 @@ RequirementsTxt { fragment: None, }, install_path: "/editable", - editable: true, - virtual: false, + editable: Some( + true, + ), + virtual: None, }, ), verbatim: VerbatimUrl { @@ -302,8 +312,10 @@ RequirementsTxt { fragment: None, }, install_path: "/editable[d", - editable: true, - virtual: false, + editable: Some( + true, + ), + virtual: None, }, ), verbatim: VerbatimUrl { @@ -352,8 +364,10 @@ RequirementsTxt { fragment: None, }, install_path: "/editable", - editable: true, - virtual: false, + editable: Some( + true, + ), + virtual: None, }, ), verbatim: VerbatimUrl { @@ -402,8 +416,10 @@ RequirementsTxt { fragment: None, }, install_path: "/editable", - editable: true, - virtual: false, + editable: Some( + true, + ), + virtual: None, }, ), verbatim: VerbatimUrl { diff --git a/crates/uv-requirements/src/source_tree.rs b/crates/uv-requirements/src/source_tree.rs index 39fbe453b..a7a99c5a2 100644 --- a/crates/uv-requirements/src/source_tree.rs +++ b/crates/uv-requirements/src/source_tree.rs @@ -154,7 +154,7 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> { let source = SourceUrl::Directory(DirectorySourceUrl { url: &url, install_path: Cow::Borrowed(source_tree), - editable: false, + editable: None, }); // Determine the hash policy. Since we don't have a package name, we perform a diff --git a/crates/uv-resolver/src/lock/export/pylock_toml.rs b/crates/uv-resolver/src/lock/export/pylock_toml.rs index 8a53fd8f7..80cd54be2 100644 --- a/crates/uv-resolver/src/lock/export/pylock_toml.rs +++ b/crates/uv-resolver/src/lock/export/pylock_toml.rs @@ -500,7 +500,7 @@ impl<'lock> PylockToml { .unwrap_or_else(|_| dist.install_path.clone()); package.directory = Some(PylockTomlDirectory { path: PortablePathBuf::from(path), - editable: if dist.editable { Some(true) } else { None }, + editable: dist.editable, subdirectory: None, }); } @@ -737,7 +737,7 @@ impl<'lock> PylockToml { ), editable: match editable { EditableMode::NonEditable => None, - EditableMode::Editable => Some(sdist.editable), + EditableMode::Editable => sdist.editable, }, subdirectory: None, }), @@ -1394,8 +1394,8 @@ impl PylockTomlDirectory { Ok(DirectorySourceDist { name: name.clone(), install_path: path.into_boxed_path(), - editable: self.editable.unwrap_or(false), - r#virtual: false, + editable: self.editable, + r#virtual: Some(false), url, }) } diff --git a/crates/uv-resolver/src/lock/mod.rs b/crates/uv-resolver/src/lock/mod.rs index 7ca100fd8..7cbac67df 100644 --- a/crates/uv-resolver/src/lock/mod.rs +++ b/crates/uv-resolver/src/lock/mod.rs @@ -2396,8 +2396,8 @@ impl Package { name: self.id.name.clone(), url: verbatim_url(&install_path, &self.id)?, install_path: install_path.into_boxed_path(), - editable: false, - r#virtual: false, + editable: Some(false), + r#virtual: Some(false), }; uv_distribution_types::SourceDist::Directory(dir_dist) } @@ -2407,8 +2407,8 @@ impl Package { name: self.id.name.clone(), url: verbatim_url(&install_path, &self.id)?, install_path: install_path.into_boxed_path(), - editable: true, - r#virtual: false, + editable: Some(true), + r#virtual: Some(false), }; uv_distribution_types::SourceDist::Directory(dir_dist) } @@ -2418,8 +2418,8 @@ impl Package { name: self.id.name.clone(), url: verbatim_url(&install_path, &self.id)?, install_path: install_path.into_boxed_path(), - editable: false, - r#virtual: true, + editable: Some(false), + r#virtual: Some(true), }; uv_distribution_types::SourceDist::Directory(dir_dist) } @@ -3250,9 +3250,9 @@ impl Source { let path = relative_to(&directory_dist.install_path, root) .or_else(|_| std::path::absolute(&directory_dist.install_path)) .map_err(LockErrorKind::DistributionRelativePath)?; - if directory_dist.editable { + if directory_dist.editable.unwrap_or(false) { Ok(Source::Editable(path.into_boxed_path())) - } else if directory_dist.r#virtual { + } else if directory_dist.r#virtual.unwrap_or(false) { Ok(Source::Virtual(path.into_boxed_path())) } else { Ok(Source::Directory(path.into_boxed_path())) @@ -4800,8 +4800,8 @@ fn normalize_requirement( marker: requires_python.simplify_markers(requirement.marker), source: RequirementSource::Directory { install_path, - editable, - r#virtual, + editable: Some(editable.unwrap_or(false)), + r#virtual: Some(r#virtual.unwrap_or(false)), url, }, origin: None, diff --git a/crates/uv-resolver/src/resolver/mod.rs b/crates/uv-resolver/src/resolver/mod.rs index 32d684f04..c30c4e947 100644 --- a/crates/uv-resolver/src/resolver/mod.rs +++ b/crates/uv-resolver/src/resolver/mod.rs @@ -620,6 +620,7 @@ impl ResolverState { // Then here, if we get a reason that we consider unrecoverable, we should diff --git a/crates/uv-resolver/src/resolver/urls.rs b/crates/uv-resolver/src/resolver/urls.rs index 73d190b4a..57803ed0b 100644 --- a/crates/uv-resolver/src/resolver/urls.rs +++ b/crates/uv-resolver/src/resolver/urls.rs @@ -63,9 +63,9 @@ impl Urls { verbatim: _, } = package_url { - if !*editable { + if editable.is_none() { debug!("Allowing an editable variant of {}", &package_url.verbatim); - *editable = true; + *editable = Some(true); } } } @@ -201,8 +201,9 @@ fn same_resource(a: &ParsedUrl, b: &ParsedUrl, git: &GitResolver) -> bool { || is_same_file(&a.install_path, &b.install_path).unwrap_or(false) } (ParsedUrl::Directory(a), ParsedUrl::Directory(b)) => { - a.install_path == b.install_path - || is_same_file(&a.install_path, &b.install_path).unwrap_or(false) + (a.install_path == b.install_path + || is_same_file(&a.install_path, &b.install_path).unwrap_or(false)) + && a.editable.is_none_or(|a| b.editable.is_none_or(|b| a == b)) } _ => false, } diff --git a/crates/uv-workspace/src/workspace.rs b/crates/uv-workspace/src/workspace.rs index 1349d739c..8d09554d9 100644 --- a/crates/uv-workspace/src/workspace.rs +++ b/crates/uv-workspace/src/workspace.rs @@ -315,15 +315,15 @@ impl Workspace { source: if member.pyproject_toml.is_package() { RequirementSource::Directory { install_path: member.root.clone().into_boxed_path(), - editable: true, - r#virtual: false, + editable: Some(true), + r#virtual: Some(false), url, } } else { RequirementSource::Directory { install_path: member.root.clone().into_boxed_path(), - editable: false, - r#virtual: true, + editable: Some(false), + r#virtual: Some(true), url, } }, @@ -371,15 +371,15 @@ impl Workspace { source: if member.pyproject_toml.is_package() { RequirementSource::Directory { install_path: member.root.clone().into_boxed_path(), - editable: true, - r#virtual: false, + editable: Some(true), + r#virtual: Some(false), url, } } else { RequirementSource::Directory { install_path: member.root.clone().into_boxed_path(), - editable: false, - r#virtual: true, + editable: Some(false), + r#virtual: Some(true), url, } }, diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index 94586004f..5843df6be 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -747,7 +747,7 @@ fn apply_no_virtual_project(resolution: Resolution) -> Resolution { return true; }; - !dist.r#virtual + !dist.r#virtual.unwrap_or(false) }) } @@ -765,8 +765,8 @@ fn apply_editable_mode(resolution: Resolution, editable: EditableMode) -> Resolu let Dist::Source(SourceDist::Directory(DirectorySourceDist { name, install_path, - editable: true, - r#virtual: false, + editable: Some(true), + r#virtual, url, })) = dist.as_ref() else { @@ -777,8 +777,8 @@ fn apply_editable_mode(resolution: Resolution, editable: EditableMode) -> Resolu dist: Arc::new(Dist::Source(SourceDist::Directory(DirectorySourceDist { name: name.clone(), install_path: install_path.clone(), - editable: false, - r#virtual: false, + editable: Some(false), + r#virtual: *r#virtual, url: url.clone(), }))), version: version.clone(), diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index f91870762..477b4b039 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -10946,7 +10946,7 @@ fn lock_sources_source_tree() -> Result<()> { } /// Lock a project in which a given dependency is requested from two different members, once as -/// editable, and once as non-editable. +/// editable, and once as non-editable. This should trigger a conflicting URL error. #[test] fn lock_editable() -> Result<()> { let context = TestContext::new("3.12"); @@ -11086,86 +11086,16 @@ fn lock_editable() -> Result<()> { library = { path = "../../library", editable = true } "#})?; - uv_snapshot!(context.filters(), context.lock(), @r###" - success: true - exit_code: 0 + uv_snapshot!(context.filters(), context.lock(), @r" + success: false + exit_code: 2 ----- stdout ----- ----- stderr ----- - Resolved 3 packages in [TIME] - "###); - - let lock = context.read("uv.lock"); - - insta::with_settings!({ - filters => context.filters(), - }, { - assert_snapshot!( - lock, @r#" - version = 1 - revision = 2 - requires-python = ">=3.12" - - [options] - exclude-newer = "2024-03-25T00:00:00Z" - - [manifest] - members = [ - "leaf", - "workspace", - ] - - [[package]] - name = "leaf" - version = "0.1.0" - source = { editable = "packages/leaf" } - dependencies = [ - { name = "library" }, - ] - - [package.metadata] - requires-dist = [{ name = "library", editable = "library" }] - - [[package]] - name = "library" - version = "0.1.0" - source = { editable = "library" } - - [[package]] - name = "workspace" - version = "0.1.0" - source = { virtual = "." } - dependencies = [ - { name = "library" }, - ] - - [package.metadata] - requires-dist = [{ name = "library", directory = "library" }] - "# - ); - }); - - // Re-run with `--locked`. - uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r###" - success: true - exit_code: 0 - ----- stdout ----- - - ----- stderr ----- - Resolved 3 packages in [TIME] - "###); - - // Install from the lockfile. - uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###" - success: true - exit_code: 0 - ----- stdout ----- - - ----- stderr ----- - Prepared 1 package in [TIME] - Installed 1 package in [TIME] - + library==0.1.0 (from file://[TEMP_DIR]/library) - "###); + error: Requirements contain conflicting URLs for package `library` in all marker environments: + - file://[TEMP_DIR]/library + - file://[TEMP_DIR]/library (editable) + "); Ok(()) } diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index 9fecd50b0..0165cc7f6 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -10471,3 +10471,335 @@ fn sync_python_platform() -> Result<()> { Ok(()) } + +/// See: +#[test] +#[cfg(not(windows))] +fn conflicting_editable() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + [dependency-groups] + foo = [ + "child", + ] + bar = [ + "child", + ] + [tool.uv] + conflicts = [ + [ + { group = "foo" }, + { group = "bar" }, + ], + ] + [tool.uv.sources] + child = [ + { path = "./child", editable = true, group = "foo" }, + { path = "./child", editable = false, group = "bar" }, + ] + "#, + )?; + + context + .temp_dir + .child("child") + .child("pyproject.toml") + .write_str( + r#" + [project] + name = "child" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + [build-system] + requires = ["hatchling"] + build-backend = "hatchling.build" + "#, + )?; + context + .temp_dir + .child("child") + .child("src") + .child("child") + .child("__init__.py") + .touch()?; + + uv_snapshot!(context.filters(), context.sync(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 3 packages in [TIME] + Audited in [TIME] + "); + + let lock = context.read("uv.lock"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + lock, @r#" + version = 1 + revision = 2 + requires-python = ">=3.12" + conflicts = [[ + { package = "project", group = "bar" }, + { package = "project", group = "foo" }, + ]] + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [[package]] + name = "child" + version = "0.1.0" + source = { directory = "child" } + + [[package]] + name = "child" + version = "0.1.0" + source = { editable = "child" } + + [[package]] + name = "project" + version = "0.1.0" + source = { virtual = "." } + + [package.dev-dependencies] + bar = [ + { name = "child", version = "0.1.0", source = { directory = "child" } }, + ] + foo = [ + { name = "child", version = "0.1.0", source = { editable = "child" } }, + ] + + [package.metadata] + + [package.metadata.requires-dev] + bar = [{ name = "child", directory = "child" }] + foo = [{ name = "child", editable = "child" }] + "# + ); + }); + + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 3 packages in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + child==0.1.0 (from file://[TEMP_DIR]/child) + "); + + uv_snapshot!(context.filters(), context.pip_list().arg("--format").arg("json"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + [{"name":"child","version":"0.1.0","editable_project_location":"[TEMP_DIR]/child"}] + + ----- stderr ----- + "#); + + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("bar"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 3 packages in [TIME] + Prepared 1 package in [TIME] + Uninstalled 1 package in [TIME] + Installed 1 package in [TIME] + ~ child==0.1.0 (from file://[TEMP_DIR]/child) + "); + + uv_snapshot!(context.filters(), context.pip_list().arg("--format").arg("json"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + [{"name":"child","version":"0.1.0"}] + + ----- stderr ----- + "#); + + Ok(()) +} + +/// See: +#[test] +#[cfg(not(windows))] +fn undeclared_editable() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + [dependency-groups] + foo = [ + "child", + ] + bar = [ + "child", + ] + [tool.uv] + conflicts = [ + [ + { group = "foo" }, + { group = "bar" }, + ], + ] + [tool.uv.sources] + child = [ + { path = "./child", editable = true, group = "foo" }, + { path = "./child", group = "bar" }, + ] + "#, + )?; + + context + .temp_dir + .child("child") + .child("pyproject.toml") + .write_str( + r#" + [project] + name = "child" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + [build-system] + requires = ["hatchling"] + build-backend = "hatchling.build" + "#, + )?; + context + .temp_dir + .child("child") + .child("src") + .child("child") + .child("__init__.py") + .touch()?; + + uv_snapshot!(context.filters(), context.sync(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 3 packages in [TIME] + Audited in [TIME] + "); + + let lock = context.read("uv.lock"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + lock, @r#" + version = 1 + revision = 2 + requires-python = ">=3.12" + conflicts = [[ + { package = "project", group = "bar" }, + { package = "project", group = "foo" }, + ]] + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [[package]] + name = "child" + version = "0.1.0" + source = { directory = "child" } + + [[package]] + name = "child" + version = "0.1.0" + source = { editable = "child" } + + [[package]] + name = "project" + version = "0.1.0" + source = { virtual = "." } + + [package.dev-dependencies] + bar = [ + { name = "child", version = "0.1.0", source = { directory = "child" } }, + ] + foo = [ + { name = "child", version = "0.1.0", source = { editable = "child" } }, + ] + + [package.metadata] + + [package.metadata.requires-dev] + bar = [{ name = "child", directory = "child" }] + foo = [{ name = "child", editable = "child" }] + "# + ); + }); + + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 3 packages in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + child==0.1.0 (from file://[TEMP_DIR]/child) + "); + + uv_snapshot!(context.filters(), context.pip_list().arg("--format").arg("json"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + [{"name":"child","version":"0.1.0","editable_project_location":"[TEMP_DIR]/child"}] + + ----- stderr ----- + "#); + + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("bar"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 3 packages in [TIME] + Prepared 1 package in [TIME] + Uninstalled 1 package in [TIME] + Installed 1 package in [TIME] + ~ child==0.1.0 (from file://[TEMP_DIR]/child) + "); + + uv_snapshot!(context.filters(), context.pip_list().arg("--format").arg("json"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + [{"name":"child","version":"0.1.0"}] + + ----- stderr ----- + "#); + + Ok(()) +} From dbaec0537ae5cfd5a55a4fbf17d93dbe9bef04b5 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Fri, 11 Jul 2025 07:47:06 -0500 Subject: [PATCH 244/349] Tear miette out of the `uv venv` command (#14546) This has some changes to the user-facing output, but makes it more consistent with the rest of uv. --- crates/uv/src/commands/venv.rs | 122 ++++++------------------------ crates/uv/tests/it/pip_compile.rs | 4 +- crates/uv/tests/it/venv.rs | 104 +++++++++---------------- 3 files changed, 62 insertions(+), 168 deletions(-) diff --git a/crates/uv/src/commands/venv.rs b/crates/uv/src/commands/venv.rs index 6d6e15758..02bc818f8 100644 --- a/crates/uv/src/commands/venv.rs +++ b/crates/uv/src/commands/venv.rs @@ -4,9 +4,7 @@ use std::str::FromStr; use std::sync::Arc; use std::vec; -use anstream::eprint; use anyhow::Result; -use miette::{Diagnostic, IntoDiagnostic}; use owo_colors::OwoColorize; use thiserror::Error; @@ -42,6 +40,21 @@ use crate::settings::NetworkSettings; use super::project::default_dependency_groups; +#[derive(Error, Debug)] +enum VenvError { + #[error("Failed to create virtual environment")] + Creation(#[source] uv_virtualenv::Error), + + #[error("Failed to install seed packages into virtual environment")] + Seed(#[source] AnyErrorBuild), + + #[error("Failed to extract interpreter tags for installing seed packages")] + Tags(#[source] uv_platform_tags::TagsError), + + #[error("Failed to resolve `--find-links` entry")] + FlatIndex(#[source] uv_client::FlatIndexError), +} + /// Create a virtual environment. #[allow(clippy::unnecessary_wraps, clippy::fn_params_excessive_bools)] pub(crate) async fn venv( @@ -70,89 +83,6 @@ pub(crate) async fn venv( relocatable: bool, preview: PreviewMode, ) -> Result { - match venv_impl( - project_dir, - path, - python_request, - install_mirrors, - link_mode, - index_locations, - index_strategy, - dependency_metadata, - keyring_provider, - network_settings, - prompt, - system_site_packages, - seed, - python_preference, - python_downloads, - allow_existing, - exclude_newer, - concurrency, - no_config, - no_project, - cache, - printer, - relocatable, - preview, - ) - .await - { - Ok(status) => Ok(status), - Err(err) => { - eprint!("{err:?}"); - Ok(ExitStatus::Failure) - } - } -} - -#[derive(Error, Debug, Diagnostic)] -enum VenvError { - #[error("Failed to create virtualenv")] - #[diagnostic(code(uv::venv::creation))] - Creation(#[source] uv_virtualenv::Error), - - #[error("Failed to install seed packages")] - #[diagnostic(code(uv::venv::seed))] - Seed(#[source] AnyErrorBuild), - - #[error("Failed to extract interpreter tags")] - #[diagnostic(code(uv::venv::tags))] - Tags(#[source] uv_platform_tags::TagsError), - - #[error("Failed to resolve `--find-links` entry")] - #[diagnostic(code(uv::venv::flat_index))] - FlatIndex(#[source] uv_client::FlatIndexError), -} - -/// Create a virtual environment. -#[allow(clippy::fn_params_excessive_bools)] -async fn venv_impl( - project_dir: &Path, - path: Option, - python_request: Option, - install_mirrors: PythonInstallMirrors, - link_mode: LinkMode, - index_locations: &IndexLocations, - index_strategy: IndexStrategy, - dependency_metadata: DependencyMetadata, - keyring_provider: KeyringProviderType, - network_settings: &NetworkSettings, - prompt: uv_virtualenv::Prompt, - system_site_packages: bool, - seed: bool, - python_preference: PythonPreference, - python_downloads: PythonDownloads, - allow_existing: bool, - exclude_newer: Option, - concurrency: Concurrency, - no_config: bool, - no_project: bool, - cache: &Cache, - printer: Printer, - relocatable: bool, - preview: PreviewMode, -) -> miette::Result { let workspace_cache = WorkspaceCache::default(); let project = if no_project { None @@ -206,7 +136,7 @@ async fn venv_impl( // If the default dependency-groups demand a higher requires-python // we should bias an empty venv to that to avoid churn. let default_groups = match &project { - Some(project) => default_dependency_groups(project.pyproject_toml()).into_diagnostic()?, + Some(project) => default_dependency_groups(project.pyproject_toml())?, None => DefaultGroups::default(), }; let groups = DependencyGroups::default().with_defaults(default_groups); @@ -221,8 +151,7 @@ async fn venv_impl( project_dir, no_config, ) - .await - .into_diagnostic()?; + .await?; // Locate the Python interpreter to use in the environment let interpreter = { @@ -239,9 +168,8 @@ async fn venv_impl( install_mirrors.python_downloads_json_url.as_deref(), preview, ) - .await - .into_diagnostic()?; - report_interpreter(&python, false, printer).into_diagnostic()?; + .await?; + report_interpreter(&python, false, printer)?; python.into_interpreter() }; @@ -268,8 +196,7 @@ async fn venv_impl( "Creating virtual environment {}at: {}", if seed { "with seed packages " } else { "" }, path.user_display().cyan() - ) - .into_diagnostic()?; + )?; let upgradeable = preview.is_enabled() && python_request @@ -307,8 +234,7 @@ async fn venv_impl( } // Instantiate a client. - let client = RegistryClientBuilder::try_from(client_builder) - .into_diagnostic()? + let client = RegistryClientBuilder::try_from(client_builder)? .cache(cache.clone()) .index_locations(index_locations) .index_strategy(index_strategy) @@ -400,9 +326,7 @@ async fn venv_impl( .map_err(|err| VenvError::Seed(err.into()))?; let changelog = Changelog::from_installed(installed); - DefaultInstallLogger - .on_complete(&changelog, printer) - .into_diagnostic()?; + DefaultInstallLogger.on_complete(&changelog, printer)?; } // Determine the appropriate activation command. @@ -431,7 +355,7 @@ async fn venv_impl( Some(Shell::Cmd) => Some(shlex_windows(venv.scripts().join("activate"), Shell::Cmd)), }; if let Some(act) = activation { - writeln!(printer.stderr(), "Activate with: {}", act.green()).into_diagnostic()?; + writeln!(printer.stderr(), "Activate with: {}", act.green())?; } Ok(ExitStatus::Success) diff --git a/crates/uv/tests/it/pip_compile.rs b/crates/uv/tests/it/pip_compile.rs index f04c16b86..ac3549874 100644 --- a/crates/uv/tests/it/pip_compile.rs +++ b/crates/uv/tests/it/pip_compile.rs @@ -17411,11 +17411,11 @@ fn compile_broken_active_venv() -> Result<()> { .arg(&broken_system_python) .arg("venv2"), @r" success: false - exit_code: 1 + exit_code: 2 ----- stdout ----- ----- stderr ----- - × No interpreter found at path `python3.14159` + error: No interpreter found at path `python3.14159` "); // Simulate a removed Python interpreter diff --git a/crates/uv/tests/it/venv.rs b/crates/uv/tests/it/venv.rs index 52291c05d..43cacb640 100644 --- a/crates/uv/tests/it/venv.rs +++ b/crates/uv/tests/it/venv.rs @@ -656,13 +656,13 @@ fn create_venv_respects_group_requires_python() -> Result<()> { uv_snapshot!(context.filters(), context.venv().arg("--python").arg("3.11"), @r" success: false - exit_code: 1 + exit_code: 2 ----- stdout ----- ----- stderr ----- - × Found conflicting Python requirements: - │ - foo: <3.12 - │ - foo:dev: >=3.12 + error: Found conflicting Python requirements: + - foo: <3.12 + - foo:dev: >=3.12 " ); @@ -808,7 +808,7 @@ fn seed_older_python_version() { #[test] fn create_venv_unknown_python_minor() { - let context = TestContext::new_with_versions(&["3.12"]); + let context = TestContext::new_with_versions(&["3.12"]).with_filtered_python_sources(); let mut command = context.venv(); command @@ -819,34 +819,22 @@ fn create_venv_unknown_python_minor() { // Unset this variable to force what the user would see .env_remove(EnvVars::UV_TEST_PYTHON_PATH); - if cfg!(windows) { - uv_snapshot!(&mut command, @r###" - success: false - exit_code: 1 - ----- stdout ----- + uv_snapshot!(context.filters(), &mut command, @r" + success: false + exit_code: 2 + ----- stdout ----- - ----- stderr ----- - × No interpreter found for Python 3.100 in managed installations, search path, or registry - "### - ); - } else { - uv_snapshot!(&mut command, @r###" - success: false - exit_code: 1 - ----- stdout ----- - - ----- stderr ----- - × No interpreter found for Python 3.100 in managed installations or search path - "### - ); - } + ----- stderr ----- + error: No interpreter found for Python 3.100 in [PYTHON SOURCES] + " + ); context.venv.assert(predicates::path::missing()); } #[test] fn create_venv_unknown_python_patch() { - let context = TestContext::new_with_versions(&["3.12"]); + let context = TestContext::new_with_versions(&["3.12"]).with_filtered_python_sources(); let mut command = context.venv(); command @@ -857,27 +845,15 @@ fn create_venv_unknown_python_patch() { // Unset this variable to force what the user would see .env_remove(EnvVars::UV_TEST_PYTHON_PATH); - if cfg!(windows) { - uv_snapshot!(&mut command, @r###" - success: false - exit_code: 1 - ----- stdout ----- + uv_snapshot!(context.filters(), &mut command, @r" + success: false + exit_code: 2 + ----- stdout ----- - ----- stderr ----- - × No interpreter found for Python 3.12.100 in managed installations, search path, or registry - "### - ); - } else { - uv_snapshot!(&mut command, @r" - success: false - exit_code: 1 - ----- stdout ----- - - ----- stderr ----- - × No interpreter found for Python 3.12.100 in managed installations or search path - " - ); - } + ----- stderr ----- + error: No interpreter found for Python 3.12.[X] in [PYTHON SOURCES] + " + ); context.venv.assert(predicates::path::missing()); } @@ -915,19 +891,17 @@ fn file_exists() -> Result<()> { uv_snapshot!(context.filters(), context.venv() .arg(context.venv.as_os_str()) .arg("--python") - .arg("3.12"), @r###" + .arg("3.12"), @r" success: false - exit_code: 1 + exit_code: 2 ----- stdout ----- ----- stderr ----- Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] Creating virtual environment at: .venv - uv::venv::creation - - × Failed to create virtualenv - ╰─▶ File exists at `.venv` - "### + error: Failed to create virtual environment + Caused by: File exists at `.venv` + " ); Ok(()) @@ -970,19 +944,17 @@ fn non_empty_dir_exists() -> Result<()> { uv_snapshot!(context.filters(), context.venv() .arg(context.venv.as_os_str()) .arg("--python") - .arg("3.12"), @r###" + .arg("3.12"), @r" success: false - exit_code: 1 + exit_code: 2 ----- stdout ----- ----- stderr ----- Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] Creating virtual environment at: .venv - uv::venv::creation - - × Failed to create virtualenv - ╰─▶ The directory `.venv` exists, but it's not a virtual environment - "### + error: Failed to create virtual environment + Caused by: The directory `.venv` exists, but it's not a virtual environment + " ); Ok(()) @@ -1000,19 +972,17 @@ fn non_empty_dir_exists_allow_existing() -> Result<()> { uv_snapshot!(context.filters(), context.venv() .arg(context.venv.as_os_str()) .arg("--python") - .arg("3.12"), @r###" + .arg("3.12"), @r" success: false - exit_code: 1 + exit_code: 2 ----- stdout ----- ----- stderr ----- Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] Creating virtual environment at: .venv - uv::venv::creation - - × Failed to create virtualenv - ╰─▶ The directory `.venv` exists, but it's not a virtual environment - "### + error: Failed to create virtual environment + Caused by: The directory `.venv` exists, but it's not a virtual environment + " ); uv_snapshot!(context.filters(), context.venv() From 9cf78217413fe6bee28fbe44e67689802c7b7486 Mon Sep 17 00:00:00 2001 From: Aria Desires Date: Fri, 11 Jul 2025 13:01:41 -0400 Subject: [PATCH 245/349] Add missing validations for disallowed `uv.toml` fields (#14322) We weren't following our usual "destructure all the options" pattern in this function, and several "this isn't actually read from uv.toml" fields slipped through the cracks over time since folks forgot it existed. Fixes part of #14308, although we could still try to make the warning in FilesystemOptions more accurate? You could argue this is a breaking change, but I think it ultimately isn't really, because we were already silently ignoring these fields. Now we properly error. --- crates/uv-settings/src/lib.rs | 50 ++++++++++++++++++++++++++---- crates/uv-settings/src/settings.rs | 2 +- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/crates/uv-settings/src/lib.rs b/crates/uv-settings/src/lib.rs index cad600cfc..84aef8f28 100644 --- a/crates/uv-settings/src/lib.rs +++ b/crates/uv-settings/src/lib.rs @@ -201,33 +201,71 @@ fn read_file(path: &Path) -> Result { /// Validate that an [`Options`] schema is compatible with `uv.toml`. fn validate_uv_toml(path: &Path, options: &Options) -> Result<(), Error> { + let Options { + globals: _, + top_level: _, + install_mirrors: _, + publish: _, + add: _, + pip: _, + cache_keys: _, + override_dependencies: _, + constraint_dependencies: _, + build_constraint_dependencies: _, + environments: _, + required_environments: _, + conflicts, + workspace, + sources, + dev_dependencies, + default_groups, + dependency_groups, + managed, + package, + build_backend, + } = options; // The `uv.toml` format is not allowed to include any of the following, which are // permitted by the schema since they _can_ be included in `pyproject.toml` files // (and we want to use `deny_unknown_fields`). - if options.workspace.is_some() { + if conflicts.is_some() { + return Err(Error::PyprojectOnlyField(path.to_path_buf(), "conflicts")); + } + if workspace.is_some() { return Err(Error::PyprojectOnlyField(path.to_path_buf(), "workspace")); } - if options.sources.is_some() { + if sources.is_some() { return Err(Error::PyprojectOnlyField(path.to_path_buf(), "sources")); } - if options.dev_dependencies.is_some() { + if dev_dependencies.is_some() { return Err(Error::PyprojectOnlyField( path.to_path_buf(), "dev-dependencies", )); } - if options.default_groups.is_some() { + if default_groups.is_some() { return Err(Error::PyprojectOnlyField( path.to_path_buf(), "default-groups", )); } - if options.managed.is_some() { + if dependency_groups.is_some() { + return Err(Error::PyprojectOnlyField( + path.to_path_buf(), + "dependency-groups", + )); + } + if managed.is_some() { return Err(Error::PyprojectOnlyField(path.to_path_buf(), "managed")); } - if options.package.is_some() { + if package.is_some() { return Err(Error::PyprojectOnlyField(path.to_path_buf(), "package")); } + if build_backend.is_some() { + return Err(Error::PyprojectOnlyField( + path.to_path_buf(), + "build-backend", + )); + } Ok(()) } diff --git a/crates/uv-settings/src/settings.rs b/crates/uv-settings/src/settings.rs index d80ccce2f..e057cb40a 100644 --- a/crates/uv-settings/src/settings.rs +++ b/crates/uv-settings/src/settings.rs @@ -103,7 +103,7 @@ pub struct Options { cache-keys = [{ file = "pyproject.toml" }, { file = "requirements.txt" }, { git = { commit = true } }] "# )] - cache_keys: Option>, + pub cache_keys: Option>, // NOTE(charlie): These fields are shared with `ToolUv` in // `crates/uv-workspace/src/pyproject.rs`. The documentation lives on that struct. From 6df7dab2df6e5a9b3bf36183851dd9d7c0824c9f Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 14 Jul 2025 13:18:39 -0400 Subject: [PATCH 246/349] Use an ephemeral environment for `uv run --with` invocations (#14447) This PR creates separation between the `--with` environment and the environment we actually run in, which in turn solves issues like https://github.com/astral-sh/uv/issues/12889 whereby two invocations share the same `--with` environment, causing them to collide by way of sharing an overlay. Closes https://github.com/astral-sh/uv/issues/7643. --- crates/uv-python/src/environment.rs | 2 +- crates/uv/src/commands/project/environment.rs | 166 ++++++++---------- crates/uv/src/commands/project/mod.rs | 3 - crates/uv/src/commands/project/run.rs | 116 ++++++++---- crates/uv/src/commands/tool/run.rs | 4 - crates/uv/tests/it/lock.rs | 2 +- crates/uv/tests/it/run.rs | 13 +- 7 files changed, 159 insertions(+), 147 deletions(-) diff --git a/crates/uv-python/src/environment.rs b/crates/uv-python/src/environment.rs index 02f9fd683..07f3ddb54 100644 --- a/crates/uv-python/src/environment.rs +++ b/crates/uv-python/src/environment.rs @@ -174,7 +174,7 @@ impl PythonEnvironment { /// N.B. This function also works for system Python environments and users depend on this. pub fn from_root(root: impl AsRef, cache: &Cache) -> Result { debug!( - "Checking for Python environment at `{}`", + "Checking for Python environment at: `{}`", root.as_ref().user_display() ); match root.as_ref().try_exists() { diff --git a/crates/uv/src/commands/project/environment.rs b/crates/uv/src/commands/project/environment.rs index a3cda28c1..cf1add99a 100644 --- a/crates/uv/src/commands/project/environment.rs +++ b/crates/uv/src/commands/project/environment.rs @@ -17,6 +17,69 @@ use crate::commands::project::{ use crate::printer::Printer; use crate::settings::{NetworkSettings, ResolverInstallerSettings}; +/// An ephemeral [`PythonEnvironment`] for running an individual command. +#[derive(Debug)] +pub(crate) struct EphemeralEnvironment(PythonEnvironment); + +impl From for EphemeralEnvironment { + fn from(environment: PythonEnvironment) -> Self { + Self(environment) + } +} + +impl From for PythonEnvironment { + fn from(environment: EphemeralEnvironment) -> Self { + environment.0 + } +} + +impl EphemeralEnvironment { + /// Set the ephemeral overlay for a Python environment. + #[allow(clippy::result_large_err)] + pub(crate) fn set_overlay(&self, contents: impl AsRef<[u8]>) -> Result<(), ProjectError> { + let site_packages = self + .0 + .site_packages() + .next() + .ok_or(ProjectError::NoSitePackages)?; + let overlay_path = site_packages.join("_uv_ephemeral_overlay.pth"); + fs_err::write(overlay_path, contents)?; + Ok(()) + } + + /// Enable system site packages for a Python environment. + #[allow(clippy::result_large_err)] + pub(crate) fn set_system_site_packages(&self) -> Result<(), ProjectError> { + self.0 + .set_pyvenv_cfg("include-system-site-packages", "true")?; + Ok(()) + } + + /// Set the `extends-environment` key in the `pyvenv.cfg` file to the given path. + /// + /// Ephemeral environments created by `uv run --with` extend a parent (virtual or system) + /// environment by adding a `.pth` file to the ephemeral environment's `site-packages` + /// directory. The `pth` file contains Python code to dynamically add the parent + /// environment's `site-packages` directory to Python's import search paths in addition to + /// the ephemeral environment's `site-packages` directory. This works well at runtime, but + /// is too dynamic for static analysis tools like ty to understand. As such, we + /// additionally write the `sys.prefix` of the parent environment to to the + /// `extends-environment` key of the ephemeral environment's `pyvenv.cfg` file, making it + /// easier for these tools to statically and reliably understand the relationship between + /// the two environments. + #[allow(clippy::result_large_err)] + pub(crate) fn set_parent_environment( + &self, + parent_environment_sys_prefix: &Path, + ) -> Result<(), ProjectError> { + self.0.set_pyvenv_cfg( + "extends-environment", + &parent_environment_sys_prefix.escape_for_python(), + )?; + Ok(()) + } +} + /// A [`PythonEnvironment`] stored in the cache. #[derive(Debug)] pub(crate) struct CachedEnvironment(PythonEnvironment); @@ -44,15 +107,13 @@ impl CachedEnvironment { printer: Printer, preview: PreviewMode, ) -> Result { - // Resolve the "base" interpreter, which resolves to an underlying parent interpreter if the - // given interpreter is a virtual environment. - let base_interpreter = Self::base_interpreter(interpreter, cache)?; + let interpreter = Self::base_interpreter(interpreter, cache)?; // Resolve the requirements with the interpreter. let resolution = Resolution::from( resolve_environment( spec, - &base_interpreter, + &interpreter, build_constraints.clone(), &settings.resolver, network_settings, @@ -80,29 +141,20 @@ impl CachedEnvironment { // Use the canonicalized base interpreter path since that's the interpreter we performed the // resolution with and the interpreter the environment will be created with. // - // We also include the canonicalized `sys.prefix` of the non-base interpreter, that is, the - // virtual environment's path. Originally, we shared cached environments independent of the - // environment they'd be layered on top of. However, this causes collisions as the overlay - // `.pth` file can be overridden by another instance of uv. Including this element in the key - // avoids this problem at the cost of creating separate cached environments for identical - // `--with` invocations across projects. We use `sys.prefix` rather than `sys.executable` so - // we can canonicalize it without invalidating the purpose of the element — it'd probably be - // safe to just use the absolute `sys.executable` as well. - // - // TODO(zanieb): Since we're not sharing these environmments across projects, we should move - // [`CachedEvnvironment::set_overlay`] etc. here since the values there should be constant - // now. + // We cache environments independent of the environment they'd be layered on top of. The + // assumption is such that the environment will _not_ be modified by the user or uv; + // otherwise, we risk cache poisoning. For example, if we were to write a `.pth` file to + // the cached environment, it would be shared across all projects that use the same + // interpreter and the same cached dependencies. // // TODO(zanieb): We should include the version of the base interpreter in the hash, so if // the interpreter at the canonicalized path changes versions we construct a new // environment. - let environment_hash = cache_digest(&( - &canonicalize_executable(base_interpreter.sys_executable())?, - &interpreter.sys_prefix().canonicalize()?, - )); + let interpreter_hash = + cache_digest(&canonicalize_executable(interpreter.sys_executable())?); // Search in the content-addressed cache. - let cache_entry = cache.entry(CacheBucket::Environments, environment_hash, resolution_hash); + let cache_entry = cache.entry(CacheBucket::Environments, interpreter_hash, resolution_hash); if cache.refresh().is_none() { if let Ok(root) = cache.resolve_link(cache_entry.path()) { @@ -116,7 +168,7 @@ impl CachedEnvironment { let temp_dir = cache.venv_dir()?; let venv = uv_virtualenv::create_venv( temp_dir.path(), - base_interpreter, + interpreter, uv_virtualenv::Prompt::None, false, false, @@ -150,76 +202,6 @@ impl CachedEnvironment { Ok(Self(PythonEnvironment::from_root(root, cache)?)) } - /// Set the ephemeral overlay for a Python environment. - #[allow(clippy::result_large_err)] - pub(crate) fn set_overlay(&self, contents: impl AsRef<[u8]>) -> Result<(), ProjectError> { - let site_packages = self - .0 - .site_packages() - .next() - .ok_or(ProjectError::NoSitePackages)?; - let overlay_path = site_packages.join("_uv_ephemeral_overlay.pth"); - fs_err::write(overlay_path, contents)?; - Ok(()) - } - - /// Clear the ephemeral overlay for a Python environment, if it exists. - #[allow(clippy::result_large_err)] - pub(crate) fn clear_overlay(&self) -> Result<(), ProjectError> { - let site_packages = self - .0 - .site_packages() - .next() - .ok_or(ProjectError::NoSitePackages)?; - let overlay_path = site_packages.join("_uv_ephemeral_overlay.pth"); - match fs_err::remove_file(overlay_path) { - Ok(()) => (), - Err(err) if err.kind() == std::io::ErrorKind::NotFound => (), - Err(err) => return Err(ProjectError::OverlayRemoval(err)), - } - Ok(()) - } - - /// Enable system site packages for a Python environment. - #[allow(clippy::result_large_err)] - pub(crate) fn set_system_site_packages(&self) -> Result<(), ProjectError> { - self.0 - .set_pyvenv_cfg("include-system-site-packages", "true")?; - Ok(()) - } - - /// Disable system site packages for a Python environment. - #[allow(clippy::result_large_err)] - pub(crate) fn clear_system_site_packages(&self) -> Result<(), ProjectError> { - self.0 - .set_pyvenv_cfg("include-system-site-packages", "false")?; - Ok(()) - } - - /// Set the `extends-environment` key in the `pyvenv.cfg` file to the given path. - /// - /// Ephemeral environments created by `uv run --with` extend a parent (virtual or system) - /// environment by adding a `.pth` file to the ephemeral environment's `site-packages` - /// directory. The `pth` file contains Python code to dynamically add the parent - /// environment's `site-packages` directory to Python's import search paths in addition to - /// the ephemeral environment's `site-packages` directory. This works well at runtime, but - /// is too dynamic for static analysis tools like ty to understand. As such, we - /// additionally write the `sys.prefix` of the parent environment to the - /// `extends-environment` key of the ephemeral environment's `pyvenv.cfg` file, making it - /// easier for these tools to statically and reliably understand the relationship between - /// the two environments. - #[allow(clippy::result_large_err)] - pub(crate) fn set_parent_environment( - &self, - parent_environment_sys_prefix: &Path, - ) -> Result<(), ProjectError> { - self.0.set_pyvenv_cfg( - "extends-environment", - &parent_environment_sys_prefix.escape_for_python(), - )?; - Ok(()) - } - /// Return the [`Interpreter`] to use for the cached environment, based on a given /// [`Interpreter`]. /// diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index 774009f63..eaccaefa6 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -200,9 +200,6 @@ pub(crate) enum ProjectError { #[error("Failed to parse PEP 723 script metadata")] Pep723ScriptTomlParse(#[source] toml::de::Error), - #[error("Failed to remove ephemeral overlay")] - OverlayRemoval(#[source] std::io::Error), - #[error("Failed to find `site-packages` directory for environment")] NoSitePackages, diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index 63850f563..16ebf88fb 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -45,7 +45,7 @@ use crate::commands::pip::loggers::{ DefaultInstallLogger, DefaultResolveLogger, SummaryInstallLogger, SummaryResolveLogger, }; use crate::commands::pip::operations::Modifications; -use crate::commands::project::environment::CachedEnvironment; +use crate::commands::project::environment::{CachedEnvironment, EphemeralEnvironment}; use crate::commands::project::install_target::InstallTarget; use crate::commands::project::lock::LockMode; use crate::commands::project::lock_target::LockTarget; @@ -944,7 +944,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl // If necessary, create an environment for the ephemeral requirements or command. let base_site_packages = SitePackages::from_interpreter(&base_interpreter)?; - let ephemeral_env = match spec { + let requirements_env = match spec { None => None, Some(spec) if can_skip_ephemeral(&spec, &base_interpreter, &base_site_packages, &settings) => @@ -952,7 +952,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl None } Some(spec) => { - debug!("Syncing ephemeral requirements"); + debug!("Syncing `--with` requirements to cached environment"); // Read the build constraints from the lock file. let build_constraints = base_lock @@ -1013,54 +1013,92 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl Err(err) => return Err(err.into()), }; - Some(environment) + Some(PythonEnvironment::from(environment)) } }; - // If we're running in an ephemeral environment, add a path file to enable loading of - // the base environment's site packages. Setting `PYTHONPATH` is insufficient, as it doesn't - // resolve `.pth` files in the base environment. + // If we're layering requirements atop the project environment, run the command in an ephemeral, + // isolated environment. Otherwise, modifications to the "active virtual environment" would + // poison the cache. + let ephemeral_dir = requirements_env + .as_ref() + .map(|_| cache.venv_dir()) + .transpose()?; + + let ephemeral_env = ephemeral_dir + .as_ref() + .map(|dir| { + debug!( + "Creating ephemeral environment at: `{}`", + dir.path().simplified_display() + ); + + uv_virtualenv::create_venv( + dir.path(), + base_interpreter.clone(), + uv_virtualenv::Prompt::None, + false, + false, + false, + false, + false, + preview, + ) + }) + .transpose()? + .map(EphemeralEnvironment::from); + + // If we're running in an ephemeral environment, add a path file to enable loading from the + // `--with` requirements environment and the project environment site packages. // - // `sitecustomize.py` would be an alternative, but it can be shadowed by an existing such - // module in the python installation. + // Setting `PYTHONPATH` is insufficient, as it doesn't resolve `.pth` files in the base + // environment. Adding `sitecustomize.py` would be an alternative, but it can be shadowed by an + // existing such module in the python installation. if let Some(ephemeral_env) = ephemeral_env.as_ref() { - let site_packages = base_interpreter - .site_packages() - .next() - .ok_or_else(|| ProjectError::NoSitePackages)?; - ephemeral_env.set_overlay(format!( - "import site; site.addsitedir(\"{}\")", - site_packages.escape_for_python() - ))?; + if let Some(requirements_env) = requirements_env.as_ref() { + let requirements_site_packages = + requirements_env.site_packages().next().ok_or_else(|| { + anyhow!("Requirements environment has no site packages directory") + })?; + let base_site_packages = base_interpreter + .site_packages() + .next() + .ok_or_else(|| anyhow!("Base environment has no site packages directory"))?; - // Write the `sys.prefix` of the parent environment to the `extends-environment` key of the `pyvenv.cfg` - // file. This helps out static-analysis tools such as ty (see docs on - // `CachedEnvironment::set_parent_environment`). - // - // Note that we do this even if the parent environment is not a virtual environment. - // For ephemeral environments created by `uv run --with`, the parent environment's - // `site-packages` directory is added to `sys.path` even if the parent environment is not - // a virtual environment and even if `--system-site-packages` was not explicitly selected. - ephemeral_env.set_parent_environment(base_interpreter.sys_prefix())?; + ephemeral_env.set_overlay(format!( + "import site; site.addsitedir(\"{}\"); site.addsitedir(\"{}\");", + base_site_packages.escape_for_python(), + requirements_site_packages.escape_for_python(), + ))?; - // If `--system-site-packages` is enabled, add the system site packages to the ephemeral - // environment. - if base_interpreter.is_virtualenv() - && PyVenvConfiguration::parse(base_interpreter.sys_prefix().join("pyvenv.cfg")) - .is_ok_and(|cfg| cfg.include_system_site_packages()) - { - ephemeral_env.set_system_site_packages()?; - } else { - ephemeral_env.clear_system_site_packages()?; + // Write the `sys.prefix` of the parent environment to the `extends-environment` key of the `pyvenv.cfg` + // file. This helps out static-analysis tools such as ty (see docs on + // `CachedEnvironment::set_parent_environment`). + // + // Note that we do this even if the parent environment is not a virtual environment. + // For ephemeral environments created by `uv run --with`, the parent environment's + // `site-packages` directory is added to `sys.path` even if the parent environment is not + // a virtual environment and even if `--system-site-packages` was not explicitly selected. + ephemeral_env.set_parent_environment(base_interpreter.sys_prefix())?; + + // If `--system-site-packages` is enabled, add the system site packages to the ephemeral + // environment. + if base_interpreter.is_virtualenv() + && PyVenvConfiguration::parse(base_interpreter.sys_prefix().join("pyvenv.cfg")) + .is_ok_and(|cfg| cfg.include_system_site_packages()) + { + ephemeral_env.set_system_site_packages()?; + } } } - // Cast from `CachedEnvironment` to `PythonEnvironment`. + // Cast to `PythonEnvironment`. let ephemeral_env = ephemeral_env.map(PythonEnvironment::from); // Determine the Python interpreter to use for the command, if necessary. let interpreter = ephemeral_env .as_ref() + .or(requirements_env.as_ref()) .map_or_else(|| &base_interpreter, |env| env.interpreter()); // Check if any run command is given. @@ -1143,6 +1181,12 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl .as_ref() .map(PythonEnvironment::scripts) .into_iter() + .chain( + requirements_env + .as_ref() + .map(PythonEnvironment::scripts) + .into_iter(), + ) .chain(std::iter::once(base_interpreter.scripts())) .chain( // On Windows, non-virtual Python distributions put `python.exe` in the top-level diff --git a/crates/uv/src/commands/tool/run.rs b/crates/uv/src/commands/tool/run.rs index f6b79774c..a1faa1153 100644 --- a/crates/uv/src/commands/tool/run.rs +++ b/crates/uv/src/commands/tool/run.rs @@ -1081,9 +1081,5 @@ async fn get_or_create_environment( }, }; - // Clear any existing overlay. - environment.clear_overlay()?; - environment.clear_system_site_packages()?; - Ok((from, environment.into())) } diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index 477b4b039..d7ac9b47a 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -15806,7 +15806,7 @@ fn lock_explicit_default_index() -> Result<()> { DEBUG Adding root workspace member: `[TEMP_DIR]/` 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` + DEBUG Checking for Python environment at: `.venv` DEBUG The project environment's Python version satisfies the request: `Python >=3.12` DEBUG Using request timeout of [TIME] DEBUG Found static `pyproject.toml` for: project @ file://[TEMP_DIR]/ diff --git a/crates/uv/tests/it/run.rs b/crates/uv/tests/it/run.rs index 93420cca0..6a1eb6093 100644 --- a/crates/uv/tests/it/run.rs +++ b/crates/uv/tests/it/run.rs @@ -1302,7 +1302,6 @@ fn run_with_pyvenv_cfg_file() -> Result<()> { uv = [UV_VERSION] version_info = 3.12.[X] include-system-site-packages = false - relocatable = true extends-environment = [PARENT_VENV] @@ -4778,7 +4777,6 @@ fn run_groups_include_requires_python() -> Result<()> { baz = ["iniconfig"] dev = ["sniffio", {include-group = "foo"}, {include-group = "baz"}] - [tool.uv.dependency-groups] foo = {requires-python="<3.13"} bar = {requires-python=">=3.13"} @@ -4923,8 +4921,8 @@ fn run_repeated() -> Result<()> { Resolved 1 package in [TIME] "###); - // Re-running as a tool does require reinstalling `typing-extensions`, since the base venv is - // different. + // Re-running as a tool doesn't require reinstalling `typing-extensions`, since the environment + // is cached. uv_snapshot!( context.filters(), context.tool_run().arg("--with").arg("typing-extensions").arg("python").arg("-c").arg("import typing_extensions; import iniconfig"), @r#" @@ -4934,8 +4932,6 @@ fn run_repeated() -> Result<()> { ----- stderr ----- Resolved 1 package in [TIME] - Installed 1 package in [TIME] - + typing-extensions==4.10.0 Traceback (most recent call last): File "", line 1, in import typing_extensions; import iniconfig @@ -4982,8 +4978,7 @@ fn run_without_overlay() -> Result<()> { + typing-extensions==4.10.0 "###); - // Import `iniconfig` in the context of a `tool run` command, which should fail. Note that - // typing-extensions gets installed again, because the venv is not shared. + // Import `iniconfig` in the context of a `tool run` command, which should fail. uv_snapshot!( context.filters(), context.tool_run().arg("--with").arg("typing-extensions").arg("python").arg("-c").arg("import typing_extensions; import iniconfig"), @r#" @@ -4993,8 +4988,6 @@ fn run_without_overlay() -> Result<()> { ----- stderr ----- Resolved 1 package in [TIME] - Installed 1 package in [TIME] - + typing-extensions==4.10.0 Traceback (most recent call last): File "", line 1, in import typing_extensions; import iniconfig From 2850dc05992c47a10a1f968d65c59c1e21cf1df2 Mon Sep 17 00:00:00 2001 From: Aria Desires Date: Mon, 14 Jul 2025 13:47:52 -0400 Subject: [PATCH 247/349] make `--check` outdated a non-error status 1 (#14167) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the case of `uv sync` all we really need to do is handle the `OutdatedEnvironment` error (precisely the error we yield only on dry-runs when everything Works but we determine things are outdated) in `OperationDiagnostic::report` (the post-processor on all `operations::install` calls) because any diagnostic handled by that gets downgraded to from status 2 to status 1 (although I don't know if that's really intentional or a random other bug in our status handling... but I figured it's best to highlight that other potential status code incongruence than not rely on it 😄). Fixes #12603 --------- Co-authored-by: John Mumm --- crates/uv/src/commands/diagnostics.rs | 4 + crates/uv/src/commands/project/lock.rs | 11 +- crates/uv/src/commands/project/mod.rs | 2 +- crates/uv/src/commands/project/sync.rs | 37 +++- crates/uv/tests/it/lock.rs | 233 +++++++++++++++++-------- crates/uv/tests/it/sync.rs | 28 +-- 6 files changed, 217 insertions(+), 98 deletions(-) diff --git a/crates/uv/src/commands/diagnostics.rs b/crates/uv/src/commands/diagnostics.rs index 7a9fcbd35..02412d683 100644 --- a/crates/uv/src/commands/diagnostics.rs +++ b/crates/uv/src/commands/diagnostics.rs @@ -127,6 +127,10 @@ impl OperationDiagnostic { native_tls_hint(err); None } + pip::operations::Error::OutdatedEnvironment => { + anstream::eprint!("{}", err); + None + } err => Some(err), } } diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index f79557d9e..833e59a13 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -234,6 +234,10 @@ pub(crate) async fn lock( Ok(ExitStatus::Success) } + Err(err @ ProjectError::LockMismatch(..)) => { + writeln!(printer.stderr(), "{}", err.to_string().bold())?; + Ok(ExitStatus::Failure) + } Err(ProjectError::Operation(err)) => { diagnostics::OperationDiagnostic::native_tls(network_settings.native_tls) .report(err) @@ -346,8 +350,11 @@ impl<'env> LockOperation<'env> { .await?; // If the lockfile changed, return an error. - if matches!(result, LockResult::Changed(_, _)) { - return Err(ProjectError::LockMismatch(Box::new(result.into_lock()))); + if let LockResult::Changed(prev, cur) = result { + return Err(ProjectError::LockMismatch( + prev.map(Box::new), + Box::new(cur), + )); } Ok(result) diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index eaccaefa6..fde2b638c 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -75,7 +75,7 @@ pub(crate) enum ProjectError { #[error( "The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`." )] - LockMismatch(Box), + LockMismatch(Option>, Box), #[error( "Unable to find lockfile at `uv.lock`. To create a lockfile, run `uv lock` or `uv sync`." diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index 5843df6be..40aa1b352 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -330,10 +330,19 @@ pub(crate) async fn sync( .report(err) .map_or(Ok(ExitStatus::Failure), |err| Err(err.into())); } - Err(ProjectError::LockMismatch(lock)) if dry_run.enabled() => { - // The lockfile is mismatched, but we're in dry-run mode. We should proceed with the - // sync operation, but exit with a non-zero status. - Outcome::LockMismatch(lock) + Err(ProjectError::LockMismatch(prev, cur)) => { + if dry_run.enabled() { + // The lockfile is mismatched, but we're in dry-run mode. We should proceed with the + // sync operation, but exit with a non-zero status. + Outcome::LockMismatch(prev, cur) + } else { + writeln!( + printer.stderr(), + "{}", + ProjectError::LockMismatch(prev, cur).to_string().bold() + )?; + return Ok(ExitStatus::Failure); + } } Err(err) => return Err(err.into()), }; @@ -398,7 +407,14 @@ pub(crate) async fn sync( match outcome { Outcome::Success(..) => Ok(ExitStatus::Success), - Outcome::LockMismatch(lock) => Err(ProjectError::LockMismatch(lock).into()), + Outcome::LockMismatch(prev, cur) => { + writeln!( + printer.stderr(), + "{}", + ProjectError::LockMismatch(prev, cur).to_string().bold() + )?; + Ok(ExitStatus::Failure) + } } } @@ -409,15 +425,18 @@ enum Outcome { /// The `lock` operation was successful. Success(LockResult), /// The `lock` operation successfully resolved, but failed due to a mismatch (e.g., with `--locked`). - LockMismatch(Box), + LockMismatch(Option>, Box), } impl Outcome { /// Return the [`Lock`] associated with this outcome. fn lock(&self) -> &Lock { match self { - Self::Success(lock) => lock.lock(), - Self::LockMismatch(lock) => lock, + Self::Success(lock) => match lock { + LockResult::Changed(_, lock) => lock, + LockResult::Unchanged(lock) => lock, + }, + Self::LockMismatch(_prev, cur) => cur, } } } @@ -1179,7 +1198,7 @@ impl From<(&LockTarget<'_>, &LockMode<'_>, &Outcome)> for LockReport { } } // TODO(zanieb): We don't have a way to report the outcome of the lock yet - Outcome::LockMismatch(_) => LockAction::Check, + Outcome::LockMismatch(..) => LockAction::Check, }, dry_run: matches!(mode, LockMode::DryRun(_)), } diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index d7ac9b47a..faf37a83a 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -6660,15 +6660,15 @@ fn lock_invalid_hash() -> Result<()> { "#)?; // Re-run with `--locked`. - uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r###" + uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- Resolved 4 packages in [TIME] - error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. - "###); + The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + "); // Install from the lockfile. uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###" @@ -11743,6 +11743,95 @@ fn unconditional_overlapping_marker_disjoint_version_constraints() -> Result<()> Ok(()) } +/// Checks the output of `uv lock --check` when there isn't a lock +#[test] +fn check_no_lock() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "myproject" + version = "0.1.0" + requires-python = ">=3.11" + dependencies = ["sortedcollections"] + "#, + )?; + + uv_snapshot!(context.filters(), context.lock() + .arg("--check"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Unable to find lockfile at `uv.lock`. To create a lockfile, run `uv lock` or `uv sync`. + "); + Ok(()) +} + +/// Checks the output of `uv lock --check` when the lock is outdated +#[test] +fn check_outdated_lock() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "myproject" + version = "0.1.0" + requires-python = ">=3.11" + dependencies = ["sortedcollections"] + "#, + )?; + + // Generate the lock + uv_snapshot!(context.filters(), context.lock(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 3 packages in [TIME] + "); + + // Check the --check returns fine + uv_snapshot!(context.filters(), context.lock() + .arg("--check"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 3 packages in [TIME] + "); + + // Edit dependencies so the lock is invalid + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.11" + dependencies = ["iniconfig"] + "#, + )?; + + uv_snapshot!(context.filters(), context.lock() + .arg("--check"), @r" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + "); + Ok(()) +} + /// This checks that markers that normalize to 'false', which are serialized /// to the lockfile as `python_full_version < '0'`, get read back as false. /// Otherwise `uv lock --check` will always fail. @@ -12094,15 +12183,15 @@ fn lock_remove_member() -> Result<()> { )?; // Re-run with `--locked`. This should fail. - uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r###" + uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- Resolved 1 package in [TIME] - error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. - "###); + The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + "); // Re-run without `--locked`. uv_snapshot!(context.filters(), context.lock(), @r###" @@ -12239,15 +12328,15 @@ fn lock_add_member() -> Result<()> { )?; // Re-run with `--locked`. This should fail. - uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r###" + uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- Resolved 5 packages in [TIME] - error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. - "###); + The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + "); // Re-run with `--offline`. This should also fail, during the resolve phase. uv_snapshot!(context.filters(), context.lock().arg("--locked").arg("--offline").arg("--no-cache"), @r###" @@ -12476,15 +12565,15 @@ fn lock_redundant_add_member() -> Result<()> { // Re-run with `--locked`. This will fail, though in theory it could succeed, since the current // _resolution_ satisfies the requirements, even if the inputs are not identical - uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r###" + uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- Resolved 4 packages in [TIME] - error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. - "###); + The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + "); // Re-run without `--locked`. uv_snapshot!(context.filters(), context.lock(), @r###" @@ -12674,15 +12763,15 @@ fn lock_new_constraints() -> Result<()> { )?; // Re-run with `--locked`. This should fail. - uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r###" + uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- Resolved 4 packages in [TIME] - error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. - "###); + The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + "); // Re-run without `--locked`. uv_snapshot!(context.filters(), context.lock(), @r###" @@ -12883,16 +12972,16 @@ fn lock_remove_member_non_project() -> Result<()> { )?; // Re-run with `--locked`. This should fail. - uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r###" + uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- warning: No `requires-python` value found in the workspace. Defaulting to `>=3.12`. Resolved in [TIME] - error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. - "###); + The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + "); // Re-run without `--locked`. uv_snapshot!(context.filters(), context.lock(), @r###" @@ -13015,15 +13104,15 @@ fn lock_rename_project() -> Result<()> { )?; // Re-run with `--locked`. This should fail. - uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r###" + uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- Resolved 2 packages in [TIME] - error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. - "###); + The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + "); // Re-run without `--locked`. uv_snapshot!(context.filters(), context.lock(), @r###" @@ -14015,15 +14104,15 @@ fn lock_constrained_environment() -> Result<()> { )?; // Re-run with `--locked`. This should fail. - uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r###" + uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- Resolved 8 packages in [TIME] - error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. - "###); + The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + "); uv_snapshot!(context.filters(), context.lock(), @r###" success: true @@ -15278,15 +15367,15 @@ fn lock_add_empty_dependency_group() -> Result<()> { )?; // Re-run with `--locked`; this should fail. - uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r###" + uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- Resolved 2 packages in [TIME] - error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. - "###); + The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + "); // Re-lock the project. uv_snapshot!(context.filters(), context.lock(), @r###" @@ -15360,15 +15449,15 @@ fn lock_add_empty_dependency_group() -> Result<()> { )?; // Re-run with `--locked`; this should fail. - uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r###" + uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- Resolved 2 packages in [TIME] - error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. - "###); + The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + "); // Re-lock the project. uv_snapshot!(context.filters(), context.lock(), @r###" @@ -23253,15 +23342,15 @@ fn lock_dynamic_to_static() -> Result<()> { )?; // Rerunning with `--locked` should fail, since the project is no longer dynamic. - uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r###" + uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- Resolved 1 package in [TIME] - error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. - "###); + The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + "); uv_snapshot!(context.filters(), context.lock(), @r###" success: true @@ -23384,15 +23473,15 @@ fn lock_static_to_dynamic() -> Result<()> { .write_str("__version__ = '0.1.0'")?; // Rerunning with `--locked` should fail, since the project is no longer static. - uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r###" + uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- Resolved 1 package in [TIME] - error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. - "###); + The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + "); uv_snapshot!(context.filters(), context.lock(), @r###" success: true @@ -23486,15 +23575,15 @@ fn lock_bump_static_version() -> Result<()> { )?; // Rerunning with `--locked` should fail. - uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r###" + uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- Resolved 1 package in [TIME] - error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. - "###); + The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + "); uv_snapshot!(context.filters(), context.lock(), @r###" success: true @@ -25302,15 +25391,15 @@ fn lock_script() -> Result<()> { })?; // Re-run with `--locked`. - uv_snapshot!(context.filters(), context.lock().arg("--script").arg("script.py").arg("--locked"), @r###" + uv_snapshot!(context.filters(), context.lock().arg("--script").arg("script.py").arg("--locked"), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- Resolved 4 packages in [TIME] - error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. - "###); + The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + "); Ok(()) } @@ -27631,15 +27720,15 @@ fn lock_empty_extra() -> Result<()> { )?; // Re-run with `--locked`. We expect this to fail, since we've added an extra. - uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r###" + uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- Resolved 3 packages in [TIME] - error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. - "###); + The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + "); uv_snapshot!(context.filters(), context.lock(), @r###" success: true @@ -27667,15 +27756,15 @@ fn lock_empty_extra() -> Result<()> { )?; // Re-run with `--locked`. We expect this to fail, since we've added an extra. - uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r###" + uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- Resolved 3 packages in [TIME] - error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. - "###); + The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + "); uv_snapshot!(context.filters(), context.lock(), @r###" success: true @@ -28341,12 +28430,12 @@ fn lock_trailing_slash_index_url_in_pyproject_not_index_argument() -> Result<()> // Re-run with `--locked`. uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- Resolved 4 packages in [TIME] - error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. "); Ok(()) @@ -28432,12 +28521,12 @@ fn lock_trailing_slash_index_url_in_lockfile_not_pyproject() -> Result<()> { // Run `uv lock --locked`. uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- Resolved 4 packages in [TIME] - error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. "); Ok(()) @@ -28523,12 +28612,12 @@ fn lock_trailing_slash_index_url_in_pyproject_and_not_lockfile() -> Result<()> { // Run `uv lock --locked`. uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- Resolved 4 packages in [TIME] - error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. "); Ok(()) @@ -28714,12 +28803,12 @@ fn lock_trailing_slash_find_links() -> Result<()> { // Re-run with `--locked` uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- Resolved 2 packages in [TIME] - error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. "); uv_snapshot!(context.filters(), context.lock(), @r" diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index 0165cc7f6..c225225b8 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -88,12 +88,12 @@ fn locked() -> Result<()> { // Running with `--locked` should error. uv_snapshot!(context.filters(), context.sync().arg("--locked"), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- Resolved 2 packages in [TIME] - error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. "); let updated = context.read("uv.lock"); @@ -424,12 +424,12 @@ fn sync_json() -> Result<()> { .arg("--locked") .arg("--output-format").arg("json"), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- Resolved 2 packages in [TIME] - error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. "); Ok(()) @@ -894,7 +894,7 @@ fn check() -> Result<()> { // Running `uv sync --check` should fail. uv_snapshot!(context.filters(), context.sync().arg("--check"), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- @@ -904,7 +904,7 @@ fn check() -> Result<()> { Would download 1 package Would install 1 package + iniconfig==2.0.0 - error: The environment is outdated; run `uv sync` to update the environment + The environment is outdated; run `uv sync` to update the environment "); // Sync the environment. @@ -8626,7 +8626,7 @@ fn sync_dry_run_and_locked() -> Result<()> { // Running with `--locked` and `--dry-run` should error. uv_snapshot!(context.filters(), context.sync().arg("--locked").arg("--dry-run"), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- @@ -8635,7 +8635,7 @@ fn sync_dry_run_and_locked() -> Result<()> { Would download 1 package Would install 1 package + iniconfig==2.0.0 - error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. "); let updated = context.read("uv.lock"); @@ -8962,13 +8962,13 @@ fn sync_locked_script() -> Result<()> { // Re-run with `--locked`. uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").arg("--locked"), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- Using script environment at: [CACHE_DIR]/environments-v2/script-[HASH] Resolved 4 packages in [TIME] - error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. "); uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r" @@ -9064,14 +9064,14 @@ fn sync_locked_script() -> Result<()> { // Re-run with `--locked`. uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").arg("--locked"), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- Updating script environment at: [CACHE_DIR]/environments-v2/script-[HASH] warning: Ignoring existing lockfile due to fork markers being disjoint with `requires-python`: `python_full_version >= '3.11'` vs `python_full_version >= '3.8' and python_full_version < '3.11'` Resolved 6 packages in [TIME] - error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. "); uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r" @@ -9944,12 +9944,12 @@ fn sync_build_constraints() -> Result<()> { // This should fail, given that the build constraints have changed. uv_snapshot!(context.filters(), context.sync().arg("--locked"), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- Resolved 2 packages in [TIME] - error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. "); // Changing the build constraints should lead to a re-resolve. From 95c0b71f7709e3097b78ef39d9d13be26b4c7f2c Mon Sep 17 00:00:00 2001 From: Aria Desires Date: Wed, 16 Jul 2025 09:24:05 -0400 Subject: [PATCH 248/349] Remove `uv version` fallback (#14161) Fixes #14157 --------- Co-authored-by: John Mumm --- crates/uv/src/commands/project/version.rs | 18 +--------- crates/uv/src/lib.rs | 10 ------ crates/uv/tests/it/version.rs | 43 ++++++++--------------- 3 files changed, 16 insertions(+), 55 deletions(-) diff --git a/crates/uv/src/commands/project/version.rs b/crates/uv/src/commands/project/version.rs index ed1e9e246..efba226b9 100644 --- a/crates/uv/src/commands/project/version.rs +++ b/crates/uv/src/commands/project/version.rs @@ -19,7 +19,6 @@ use uv_pep440::{BumpCommand, PrereleaseKind, Version}; use uv_pep508::PackageName; use uv_python::{PythonDownloads, PythonPreference, PythonRequest}; use uv_settings::PythonInstallMirrors; -use uv_warnings::warn_user; use uv_workspace::pyproject_mut::Error; use uv_workspace::{ DiscoveryOptions, WorkspaceCache, @@ -58,7 +57,6 @@ pub(crate) async fn project_version( mut bump: Vec, short: bool, output_format: VersionFormat, - strict: bool, project_dir: &Path, package: Option, dry_run: bool, @@ -80,21 +78,7 @@ pub(crate) async fn project_version( preview: PreviewMode, ) -> Result { // Read the metadata - let project = match find_target(project_dir, package.as_ref()).await { - Ok(target) => target, - Err(err) => { - // If strict, hard bail on failing to find the pyproject.toml - if strict { - return Err(err)?; - } - // Otherwise, warn and provide fallback to the old `uv version` from before 0.7.0 - warn_user!( - "Failed to read project metadata ({err}). Running `{}` for compatibility. This fallback will be removed in the future; pass `--preview` to force an error.", - "uv self version".green() - ); - return self_version(short, output_format, printer); - } - }; + let project = find_target(project_dir, package.as_ref()).await?; let pyproject_path = project.root().join("pyproject.toml"); let Some(name) = project.project_name().cloned() else { diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 995738638..433f5afd3 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -1062,7 +1062,6 @@ async fn run(mut cli: Cli) -> Result { } Commands::Project(project) => { Box::pin(run_project( - cli.top_level.global_args.project.is_some(), project, &project_dir, run_command, @@ -1663,7 +1662,6 @@ async fn run(mut cli: Cli) -> Result { /// Run a [`ProjectCommand`]. async fn run_project( - project_was_explicit: bool, project_command: Box, project_dir: &Path, command: Option, @@ -2055,19 +2053,11 @@ async fn run_project( .combine(Refresh::from(args.settings.resolver.upgrade.clone())), ); - // If they specified any of these flags, they probably don't mean `uv self version` - let strict = project_was_explicit - || globals.preview.is_enabled() - || args.dry_run - || !args.bump.is_empty() - || args.value.is_some() - || args.package.is_some(); Box::pin(commands::project_version( args.value, args.bump, args.short, args.output_format, - strict, project_dir, args.package, args.dry_run, diff --git a/crates/uv/tests/it/version.rs b/crates/uv/tests/it/version.rs index 3c5e28e0f..78dd64252 100644 --- a/crates/uv/tests/it/version.rs +++ b/crates/uv/tests/it/version.rs @@ -1437,8 +1437,8 @@ fn version_set_dynamic() -> Result<()> { Ok(()) } -// Should fallback to `uv --version` if this pyproject.toml isn't usable for whatever reason -// (In this case, because tool.uv.managed = false) +/// Previously would fallback to `uv --version` if this pyproject.toml isn't usable for whatever reason +/// (In this case, because tool.uv.managed = false) #[test] fn version_get_fallback_unmanaged() -> Result<()> { let context = TestContext::new("3.12"); @@ -1456,13 +1456,12 @@ fn version_get_fallback_unmanaged() -> Result<()> { )?; uv_snapshot!(context.filters(), context.version(), @r" - success: true - exit_code: 0 + success: false + exit_code: 2 ----- stdout ----- - uv [VERSION] ([COMMIT] DATE) ----- stderr ----- - warning: Failed to read project metadata (The project is marked as unmanaged: `[TEMP_DIR]/`). Running `uv self version` for compatibility. This fallback will be removed in the future; pass `--preview` to force an error. + error: The project is marked as unmanaged: `[TEMP_DIR]/` "); let pyproject = fs_err::read_to_string(&pyproject_toml)?; @@ -1507,13 +1506,12 @@ fn version_get_fallback_unmanaged_short() -> Result<()> { .collect::>(); uv_snapshot!(filters, context.version() .arg("--short"), @r" - success: true - exit_code: 0 + success: false + exit_code: 2 ----- stdout ----- - [VERSION] ([COMMIT] DATE) ----- stderr ----- - warning: Failed to read project metadata (The project is marked as unmanaged: `[TEMP_DIR]/`). Running `uv self version` for compatibility. This fallback will be removed in the future; pass `--preview` to force an error. + error: The project is marked as unmanaged: `[TEMP_DIR]/` "); let pyproject = fs_err::read_to_string(&pyproject_toml)?; @@ -1587,25 +1585,14 @@ fn version_get_fallback_unmanaged_json() -> Result<()> { .collect::>(); if git_version_info_expected() { uv_snapshot!(filters, context.version() - .arg("--output-format").arg("json"), @r#" - success: true - exit_code: 0 - ----- stdout ----- - { - "package_name": "uv", - "version": "[VERSION]", - "commit_info": { - "short_commit_hash": "[LONGHASH]", - "commit_hash": "[LONGHASH]", - "commit_date": "[DATE]", - "last_tag": "[TAG]", - "commits_since_last_tag": [COUNT] - } - } + .arg("--output-format").arg("json"), @r" + success: false + exit_code: 2 + ----- stdout ----- - ----- stderr ----- - warning: Failed to read project metadata (The project is marked as unmanaged: `[TEMP_DIR]/`). Running `uv self version` for compatibility. This fallback will be removed in the future; pass `--preview` to force an error. - "#); + ----- stderr ----- + error: The project is marked as unmanaged: `[TEMP_DIR]/` + "); } else { uv_snapshot!(filters, context.version() .arg("--output-format").arg("json"), @r#" From 3c9aea87b41f01b26775facd1e3d34828ef53b9f Mon Sep 17 00:00:00 2001 From: konsti Date: Wed, 16 Jul 2025 19:07:08 +0100 Subject: [PATCH 249/349] `uv init`: Make `uv_build` the default build backend (from `hatchling`) (#14661) Closes https://github.com/astral-sh/uv/issues/14298 Switch the default build backend for `uv init` from `hatchling` to `uv_build`. This change affects the following two commands: * `uv init --lib` * `uv init [--app] --package` It does not affect `uv init` or `uv init --app` without `--package`. `uv init --build-backend <...>` also works as before. **Before** ``` $ uv init --lib project $ cat project/pyproject.toml [project] name = "project" version = "0.1.0" description = "Add your description here" readme = "README.md" authors = [ { name = "konstin", email = "konstin@mailbox.org" } ] requires-python = ">=3.13.2" dependencies = [] [build-system] requires = ["hatchling"] build-backend = "hatchling.build" ``` **After** ``` $ uv init --lib project $ cat project/pyproject.toml [project] name = "project" version = "0.1.0" description = "Add your description here" readme = "README.md" authors = [ { name = "konstin", email = "konstin@mailbox.org" } ] requires-python = ">=3.13.2" dependencies = [] [build-system] requires = ["uv_build>=0.7.20,<0.8"] build-backend = "uv_build" ``` I cleaned up some tests for consistency in the second commit. --- crates/uv-build-backend/src/metadata.rs | 14 +- crates/uv/src/commands/project/init.rs | 21 +-- crates/uv/tests/it/build.rs | 17 +- crates/uv/tests/it/build_backend.rs | 20 +- crates/uv/tests/it/common/mod.rs | 8 + crates/uv/tests/it/init.rs | 235 +++++++----------------- docs/concepts/build-backend.md | 7 +- 7 files changed, 99 insertions(+), 223 deletions(-) diff --git a/crates/uv-build-backend/src/metadata.rs b/crates/uv-build-backend/src/metadata.rs index 00a207c7a..296c76a2b 100644 --- a/crates/uv-build-backend/src/metadata.rs +++ b/crates/uv-build-backend/src/metadata.rs @@ -171,7 +171,7 @@ impl PyProjectToml { /// /// ```toml /// [build-system] - /// requires = ["uv_build>=0.4.15,<5"] + /// requires = ["uv_build>=0.4.15,<0.5"] /// build-backend = "uv_build" /// ``` pub fn check_build_system(&self, uv_version: &str) -> Vec { @@ -826,7 +826,7 @@ mod tests { {payload} [build-system] - requires = ["uv_build>=0.4.15,<5"] + requires = ["uv_build>=0.4.15,<0.5"] build-backend = "uv_build" "# } @@ -909,7 +909,7 @@ mod tests { foo-bar = "foo:bar" [build-system] - requires = ["uv_build>=0.4.15,<5"] + requires = ["uv_build>=0.4.15,<0.5"] build-backend = "uv_build" "# }; @@ -1036,7 +1036,7 @@ mod tests { foo-bar = "foo:bar" [build-system] - requires = ["uv_build>=0.4.15,<5"] + requires = ["uv_build>=0.4.15,<0.5"] build-backend = "uv_build" "# }; @@ -1104,7 +1104,7 @@ mod tests { let contents = extend_project(""); let pyproject_toml = PyProjectToml::parse(&contents).unwrap(); assert_snapshot!( - pyproject_toml.check_build_system("1.0.0+test").join("\n"), + pyproject_toml.check_build_system("0.4.15+test").join("\n"), @"" ); } @@ -1135,7 +1135,7 @@ mod tests { version = "0.1.0" [build-system] - requires = ["uv_build>=0.4.15,<5", "wheel"] + requires = ["uv_build>=0.4.15,<0.5", "wheel"] build-backend = "uv_build" "#}; let pyproject_toml = PyProjectToml::parse(contents).unwrap(); @@ -1171,7 +1171,7 @@ mod tests { version = "0.1.0" [build-system] - requires = ["uv_build>=0.4.15,<5"] + requires = ["uv_build>=0.4.15,<0.5"] build-backend = "setuptools" "#}; let pyproject_toml = PyProjectToml::parse(contents).unwrap(); diff --git a/crates/uv/src/commands/project/init.rs b/crates/uv/src/commands/project/init.rs index 9ff321a72..4fd79b1c2 100644 --- a/crates/uv/src/commands/project/init.rs +++ b/crates/uv/src/commands/project/init.rs @@ -63,9 +63,6 @@ pub(crate) async fn init( printer: Printer, preview: PreviewMode, ) -> Result { - if build_backend == Some(ProjectBuildBackend::Uv) && preview.is_disabled() { - warn_user_once!("The uv build backend is experimental and may change without warning"); - } match init_kind { InitKind::Script => { let Some(path) = explicit_path.as_deref() else { @@ -596,7 +593,6 @@ async fn init_project( author_from, no_readme, package, - preview, )?; if let Some(workspace) = workspace { @@ -724,7 +720,6 @@ impl InitProjectKind { author_from: Option, no_readme: bool, package: bool, - preview: PreviewMode, ) -> Result<()> { match self { InitProjectKind::Application => InitProjectKind::init_application( @@ -739,7 +734,6 @@ impl InitProjectKind { author_from, no_readme, package, - preview, ), InitProjectKind::Library => InitProjectKind::init_library( name, @@ -753,7 +747,6 @@ impl InitProjectKind { author_from, no_readme, package, - preview, ), } } @@ -772,7 +765,6 @@ impl InitProjectKind { author_from: Option, no_readme: bool, package: bool, - preview: PreviewMode, ) -> Result<()> { fs_err::create_dir_all(path)?; @@ -805,11 +797,7 @@ impl InitProjectKind { } // Add a build system - let build_backend = match build_backend { - Some(build_backend) => build_backend, - None if preview.is_enabled() => ProjectBuildBackend::Uv, - None => ProjectBuildBackend::Hatch, - }; + let build_backend = build_backend.unwrap_or(ProjectBuildBackend::Uv); pyproject.push('\n'); pyproject.push_str(&pyproject_build_system(name, build_backend)); pyproject_build_backend_prerequisites(name, path, build_backend)?; @@ -859,7 +847,6 @@ impl InitProjectKind { author_from: Option, no_readme: bool, package: bool, - preview: PreviewMode, ) -> Result<()> { if !package { return Err(anyhow!("Library projects must be packaged")); @@ -880,11 +867,7 @@ impl InitProjectKind { ); // Always include a build system if the project is packaged. - let build_backend = match build_backend { - Some(build_backend) => build_backend, - None if preview.is_enabled() => ProjectBuildBackend::Uv, - None => ProjectBuildBackend::Hatch, - }; + let build_backend = build_backend.unwrap_or(ProjectBuildBackend::Uv); pyproject.push('\n'); pyproject.push_str(&pyproject_build_system(name, build_backend)); pyproject_build_backend_prerequisites(name, path, build_backend)?; diff --git a/crates/uv/tests/it/build.rs b/crates/uv/tests/it/build.rs index 3d08a90d4..656c68d3f 100644 --- a/crates/uv/tests/it/build.rs +++ b/crates/uv/tests/it/build.rs @@ -1439,7 +1439,6 @@ fn build_fast_path() -> Result<()> { let built_by_uv = current_dir()?.join("../../scripts/packages/built-by-uv"); uv_snapshot!(context.build() - .arg("--preview") .arg(&built_by_uv) .arg("--out-dir") .arg(context.temp_dir.join("output1")), @r###" @@ -1465,7 +1464,6 @@ fn build_fast_path() -> Result<()> { .assert(predicate::path::is_file()); uv_snapshot!(context.build() - .arg("--preview") .arg(&built_by_uv) .arg("--out-dir") .arg(context.temp_dir.join("output2")) @@ -1485,7 +1483,6 @@ fn build_fast_path() -> Result<()> { .assert(predicate::path::is_file()); uv_snapshot!(context.build() - .arg("--preview") .arg(&built_by_uv) .arg("--out-dir") .arg(context.temp_dir.join("output3")) @@ -1505,7 +1502,6 @@ fn build_fast_path() -> Result<()> { .assert(predicate::path::is_file()); uv_snapshot!(context.build() - .arg("--preview") .arg(&built_by_uv) .arg("--out-dir") .arg(context.temp_dir.join("output4")) @@ -1545,7 +1541,6 @@ fn build_list_files() -> Result<()> { // By default, we build the wheel from the source dist, which we need to do even for the list // task. uv_snapshot!(context.build() - .arg("--preview") .arg(&built_by_uv) .arg("--out-dir") .arg(context.temp_dir.join("output1")) @@ -1601,7 +1596,6 @@ fn build_list_files() -> Result<()> { .assert(predicate::path::missing()); uv_snapshot!(context.build() - .arg("--preview") .arg(&built_by_uv) .arg("--out-dir") .arg(context.temp_dir.join("output2")) @@ -1670,7 +1664,6 @@ fn build_list_files_errors() -> Result<()> { // In CI, we run with link mode settings. filters.push(("--link-mode ", "")); uv_snapshot!(filters, context.build() - .arg("--preview") .arg(&built_by_uv) .arg("--out-dir") .arg(context.temp_dir.join("output1")) @@ -1694,7 +1687,6 @@ fn build_list_files_errors() -> Result<()> { // Windows normalization filters.push(("/crates/uv/../../", "/")); uv_snapshot!(filters, context.build() - .arg("--preview") .arg(&anyio_local) .arg("--out-dir") .arg(context.temp_dir.join("output2")) @@ -1987,12 +1979,7 @@ fn force_pep517() -> Result<()> { // We need to use a real `uv_build` package. let context = TestContext::new("3.12").with_exclude_newer("2025-05-27T00:00:00Z"); - context - .init() - .arg("--build-backend") - .arg("uv") - .assert() - .success(); + context.init().assert().success(); let pyproject_toml = context.temp_dir.child("pyproject.toml"); pyproject_toml.write_str(indoc! {r#" @@ -2026,7 +2013,7 @@ fn force_pep517() -> Result<()> { ----- stderr ----- Building source distribution... - Error: Missing module directory for `does_not_exist` in `src`. Found: `temp` + Error: Missing source directory at: `src` × Failed to build `[TEMP_DIR]/` ├─▶ The build backend returned an error ╰─▶ Call to `uv_build.build_sdist` failed (exit status: 1) diff --git a/crates/uv/tests/it/build_backend.rs b/crates/uv/tests/it/build_backend.rs index b3bd337ae..ae3a7a740 100644 --- a/crates/uv/tests/it/build_backend.rs +++ b/crates/uv/tests/it/build_backend.rs @@ -222,8 +222,7 @@ fn preserve_executable_bit() -> Result<()> { let project_dir = context.temp_dir.path().join("preserve_executable_bit"); context .init() - .arg("--build-backend") - .arg("uv") + .arg("--lib") .arg(&project_dir) .assert() .success(); @@ -296,7 +295,7 @@ fn rename_module() -> Result<()> { module-name = "bar" [build-system] - requires = ["uv_build>=0.5,<0.8"] + requires = ["uv_build>=0.7,<10000"] build-backend = "uv_build" "#})?; @@ -377,7 +376,7 @@ fn rename_module_editable_build() -> Result<()> { module-name = "bar" [build-system] - requires = ["uv_build>=0.5,<0.8"] + requires = ["uv_build>=0.7,<10000"] build-backend = "uv_build" "#})?; @@ -436,7 +435,7 @@ fn build_module_name_normalization() -> Result<()> { version = "1.0.0" [build-system] - requires = ["uv_build>=0.5,<0.8"] + requires = ["uv_build>=0.7,<10000"] build-backend = "uv_build" [tool.uv.build-backend] @@ -548,7 +547,7 @@ fn build_sdist_with_long_path() -> Result<()> { version = "1.0.0" [build-system] - requires = ["uv_build>=0.7,<0.8"] + requires = ["uv_build>=0.7,<10000"] build-backend = "uv_build" "#})?; context @@ -591,7 +590,7 @@ fn sdist_error_without_module() -> Result<()> { version = "1.0.0" [build-system] - requires = ["uv_build>=0.7,<0.8"] + requires = ["uv_build>=0.7,<10000"] build-backend = "uv_build" "#})?; @@ -661,7 +660,7 @@ fn complex_namespace_packages() -> Result<()> { module-name = "{project_name_dist_info}.{part_name}" [build-system] - requires = ["uv_build>=0.5.15,<10000"] + requires = ["uv_build>=0.7,<10000"] build-backend = "uv_build" "# }; @@ -770,8 +769,7 @@ fn symlinked_file() -> Result<()> { let project = context.temp_dir.child("project"); context .init() - .arg("--build-backend") - .arg("uv") + .arg("--lib") .arg(project.path()) .assert() .success(); @@ -783,7 +781,7 @@ fn symlinked_file() -> Result<()> { license-files = ["LICENSE"] [build-system] - requires = ["uv_build>=0.5.15,<10000"] + requires = ["uv_build>=0.7,<10000"] build-backend = "uv_build" "# })?; diff --git a/crates/uv/tests/it/common/mod.rs b/crates/uv/tests/it/common/mod.rs index d4a73f953..bc6e65f4e 100644 --- a/crates/uv/tests/it/common/mod.rs +++ b/crates/uv/tests/it/common/mod.rs @@ -664,6 +664,14 @@ impl TestContext { )); // For wiremock tests filters.push((r"127\.0\.0\.1:\d*".to_string(), "[LOCALHOST]".to_string())); + // Avoid breaking the tests when bumping the uv version + filters.push(( + format!( + r#"requires = \["uv_build>={},<[0-9.]+"\]"#, + uv_version::version() + ), + r#"requires = ["uv_build>=[CURRENT_VERSION],<[NEXT_BREAKING]"]"#.to_string(), + )); Self { root: ChildPath::new(root.path()), diff --git a/crates/uv/tests/it/init.rs b/crates/uv/tests/it/init.rs index c5993d670..3f374eada 100644 --- a/crates/uv/tests/it/init.rs +++ b/crates/uv/tests/it/init.rs @@ -314,7 +314,7 @@ fn init_application_package() -> Result<()> { filters => context.filters(), }, { assert_snapshot!( - pyproject, @r###" + pyproject, @r#" [project] name = "foo" version = "0.1.0" @@ -327,9 +327,9 @@ fn init_application_package() -> Result<()> { foo = "foo:main" [build-system] - requires = ["hatchling"] - build-backend = "hatchling.build" - "### + requires = ["uv_build>=[CURRENT_VERSION],<[NEXT_BREAKING]"] + build-backend = "uv_build" + "# ); }); @@ -390,7 +390,7 @@ fn init_library() -> Result<()> { filters => context.filters(), }, { assert_snapshot!( - pyproject, @r###" + pyproject, @r#" [project] name = "foo" version = "0.1.0" @@ -400,9 +400,9 @@ fn init_library() -> Result<()> { dependencies = [] [build-system] - requires = ["hatchling"] - build-backend = "hatchling.build" - "### + requires = ["uv_build>=[CURRENT_VERSION],<[NEXT_BREAKING]"] + build-backend = "uv_build" + "# ); }); @@ -446,91 +446,6 @@ fn init_library() -> Result<()> { Ok(()) } -/// Test the uv build backend with using `uv init --lib --preview`. To be merged with the regular -/// init lib test once the uv build backend becomes the stable default. -#[test] -fn init_library_preview() -> Result<()> { - let context = TestContext::new("3.12"); - - let child = context.temp_dir.child("foo"); - child.create_dir_all()?; - - let pyproject_toml = child.join("pyproject.toml"); - let init_py = child.join("src").join("foo").join("__init__.py"); - let py_typed = child.join("src").join("foo").join("py.typed"); - - uv_snapshot!(context.filters(), context.init().current_dir(&child).arg("--lib").arg("--preview"), @r###" - success: true - exit_code: 0 - ----- stdout ----- - - ----- stderr ----- - Initialized project `foo` - "###); - - let pyproject = fs_err::read_to_string(&pyproject_toml)?; - let mut filters = context.filters(); - filters.push((r#"\["uv_build>=.*,<.*"\]"#, r#"["uv_build[SPECIFIERS]"]"#)); - insta::with_settings!({ - filters => filters, - }, { - assert_snapshot!( - pyproject, @r#" - [project] - name = "foo" - version = "0.1.0" - description = "Add your description here" - readme = "README.md" - requires-python = ">=3.12" - dependencies = [] - - [build-system] - requires = ["uv_build[SPECIFIERS]"] - build-backend = "uv_build" - "# - ); - }); - - let init = fs_err::read_to_string(init_py)?; - insta::with_settings!({ - filters => context.filters(), - }, { - assert_snapshot!( - init, @r###" - def hello() -> str: - return "Hello from foo!" - "### - ); - }); - - let py_typed = fs_err::read_to_string(py_typed)?; - insta::with_settings!({ - filters => context.filters(), - }, { - assert_snapshot!( - py_typed, @"" - ); - }); - - uv_snapshot!(context.filters(), context.run().arg("--preview").current_dir(&child).arg("python").arg("-c").arg("import foo; print(foo.hello())"), @r###" - success: true - exit_code: 0 - ----- stdout ----- - Hello from foo! - - ----- stderr ----- - warning: `VIRTUAL_ENV=[VENV]/` does not match the project environment path `.venv` and will be ignored; use `--active` to target the active environment instead - Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] - Creating virtual environment at: .venv - Resolved 1 package in [TIME] - Prepared 1 package in [TIME] - Installed 1 package in [TIME] - + foo==0.1.0 (from file://[TEMP_DIR]/foo) - "###); - - Ok(()) -} - /// Test the uv build backend with using `uv init --package --preview`. To be merged with the regular /// init lib test once the uv build backend becomes the stable default. #[test] @@ -550,10 +465,8 @@ fn init_package_preview() -> Result<()> { "###); let pyproject = fs_err::read_to_string(child.join("pyproject.toml"))?; - let mut filters = context.filters(); - filters.push((r#"\["uv_build>=.*,<.*"\]"#, r#"["uv_build[SPECIFIERS]"]"#)); insta::with_settings!({ - filters => filters, + filters => context.filters(), }, { assert_snapshot!( pyproject, @r#" @@ -569,7 +482,7 @@ fn init_package_preview() -> Result<()> { foo = "foo:main" [build-system] - requires = ["uv_build[SPECIFIERS]"] + requires = ["uv_build>=[CURRENT_VERSION],<[NEXT_BREAKING]"] build-backend = "uv_build" "# ); @@ -615,7 +528,7 @@ fn init_bare_lib() { filters => context.filters(), }, { assert_snapshot!( - pyproject, @r###" + pyproject, @r#" [project] name = "foo" version = "0.1.0" @@ -623,9 +536,9 @@ fn init_bare_lib() { dependencies = [] [build-system] - requires = ["hatchling"] - build-backend = "hatchling.build" - "### + requires = ["uv_build>=[CURRENT_VERSION],<[NEXT_BREAKING]"] + build-backend = "uv_build" + "# ); }); } @@ -667,7 +580,7 @@ fn init_bare_package() { filters => context.filters(), }, { assert_snapshot!( - pyproject, @r###" + pyproject, @r#" [project] name = "foo" version = "0.1.0" @@ -675,9 +588,9 @@ fn init_bare_package() { dependencies = [] [build-system] - requires = ["hatchling"] - build-backend = "hatchling.build" - "### + requires = ["uv_build>=[CURRENT_VERSION],<[NEXT_BREAKING]"] + build-backend = "uv_build" + "# ); }); } @@ -1154,7 +1067,7 @@ fn init_library_current_dir() -> Result<()> { filters => context.filters(), }, { assert_snapshot!( - pyproject, @r###" + pyproject, @r#" [project] name = "foo" version = "0.1.0" @@ -1164,9 +1077,9 @@ fn init_library_current_dir() -> Result<()> { dependencies = [] [build-system] - requires = ["hatchling"] - build-backend = "hatchling.build" - "### + requires = ["uv_build>=[CURRENT_VERSION],<[NEXT_BREAKING]"] + build-backend = "uv_build" + "# ); }); @@ -1283,7 +1196,7 @@ fn init_dot_args() -> Result<()> { filters => context.filters(), }, { assert_snapshot!( - pyproject, @r###" + pyproject, @r#" [project] name = "foo" version = "0.1.0" @@ -1293,9 +1206,9 @@ fn init_dot_args() -> Result<()> { dependencies = [] [build-system] - requires = ["hatchling"] - build-backend = "hatchling.build" - "### + requires = ["uv_build>=[CURRENT_VERSION],<[NEXT_BREAKING]"] + build-backend = "uv_build" + "# ); }); @@ -1361,7 +1274,7 @@ fn init_workspace() -> Result<()> { filters => context.filters(), }, { assert_snapshot!( - pyproject, @r###" + pyproject, @r#" [project] name = "foo" version = "0.1.0" @@ -1371,9 +1284,9 @@ fn init_workspace() -> Result<()> { dependencies = [] [build-system] - requires = ["hatchling"] - build-backend = "hatchling.build" - "### + requires = ["uv_build>=[CURRENT_VERSION],<[NEXT_BREAKING]"] + build-backend = "uv_build" + "# ); }); @@ -1546,7 +1459,7 @@ fn init_workspace_relative_sub_package() -> Result<()> { filters => context.filters(), }, { assert_snapshot!( - pyproject, @r###" + pyproject, @r#" [project] name = "foo" version = "0.1.0" @@ -1556,9 +1469,9 @@ fn init_workspace_relative_sub_package() -> Result<()> { dependencies = [] [build-system] - requires = ["hatchling"] - build-backend = "hatchling.build" - "### + requires = ["uv_build>=[CURRENT_VERSION],<[NEXT_BREAKING]"] + build-backend = "uv_build" + "# ); }); @@ -1643,7 +1556,7 @@ fn init_workspace_outside() -> Result<()> { filters => context.filters(), }, { assert_snapshot!( - pyproject, @r###" + pyproject, @r#" [project] name = "foo" version = "0.1.0" @@ -1653,9 +1566,9 @@ fn init_workspace_outside() -> Result<()> { dependencies = [] [build-system] - requires = ["hatchling"] - build-backend = "hatchling.build" - "### + requires = ["uv_build>=[CURRENT_VERSION],<[NEXT_BREAKING]"] + build-backend = "uv_build" + "# ); }); @@ -1725,7 +1638,7 @@ fn init_normalized_names() -> Result<()> { filters => context.filters(), }, { assert_snapshot!( - pyproject, @r###" + pyproject, @r#" [project] name = "foo-bar" version = "0.1.0" @@ -1735,9 +1648,9 @@ fn init_normalized_names() -> Result<()> { dependencies = [] [build-system] - requires = ["hatchling"] - build-backend = "hatchling.build" - "### + requires = ["uv_build>=[CURRENT_VERSION],<[NEXT_BREAKING]"] + build-backend = "uv_build" + "# ); }); @@ -3008,8 +2921,8 @@ fn init_with_author() { dependencies = [] [build-system] - requires = ["hatchling"] - build-backend = "hatchling.build" + requires = ["uv_build>=[CURRENT_VERSION],<[NEXT_BREAKING]"] + build-backend = "uv_build" "# ); }); @@ -3038,8 +2951,8 @@ fn init_with_author() { dependencies = [] [build-system] - requires = ["hatchling"] - build-backend = "hatchling.build" + requires = ["uv_build>=[CURRENT_VERSION],<[NEXT_BREAKING]"] + build-backend = "uv_build" "# ); }); @@ -3822,9 +3735,9 @@ fn init_lib_build_backend_scikit() -> Result<()> { Ok(()) } -/// Run `uv init --app --package --build-backend uv` to create a packaged application project +/// Run `uv init --app --package --build-backend hatchling` to create a packaged application project #[test] -fn init_application_package_uv() -> Result<()> { +fn init_application_package_hatchling() -> Result<()> { let context = TestContext::new("3.12"); let child = context.temp_dir.child("foo"); @@ -3833,41 +3746,34 @@ fn init_application_package_uv() -> Result<()> { let pyproject_toml = child.join("pyproject.toml"); let init_py = child.join("src").join("foo").join("__init__.py"); - uv_snapshot!(context.filters(), context.init().current_dir(&child).arg("--app").arg("--package").arg("--build-backend").arg("uv"), @r###" + uv_snapshot!(context.filters(), context.init().current_dir(&child).arg("--app").arg("--package").arg("--build-backend").arg("hatchling"), @r###" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - warning: The uv build backend is experimental and may change without warning Initialized project `foo` "###); let pyproject = fs_err::read_to_string(&pyproject_toml)?; - let mut filters = context.filters(); - filters.push((r#"\["uv_build>=.*,<.*"\]"#, r#"["uv_build[SPECIFIERS]"]"#)); - insta::with_settings!({ - filters => filters, - }, { - assert_snapshot!( - pyproject, @r###" - [project] - name = "foo" - version = "0.1.0" - description = "Add your description here" - readme = "README.md" - requires-python = ">=3.12" - dependencies = [] + assert_snapshot!( + pyproject, @r#" + [project] + name = "foo" + version = "0.1.0" + description = "Add your description here" + readme = "README.md" + requires-python = ">=3.12" + dependencies = [] - [project.scripts] - foo = "foo:main" + [project.scripts] + foo = "foo:main" - [build-system] - requires = ["uv_build[SPECIFIERS]"] - build-backend = "uv_build" - "### - ); - }); + [build-system] + requires = ["hatchling"] + build-backend = "hatchling.build" + "# + ); let init = fs_err::read_to_string(init_py)?; insta::with_settings!({ @@ -3881,8 +3787,7 @@ fn init_application_package_uv() -> Result<()> { ); }); - // Use preview to go through the fast path. - uv_snapshot!(context.filters(), context.run().arg("--preview").arg("foo").current_dir(&child).env_remove(EnvVars::VIRTUAL_ENV), @r###" + uv_snapshot!(context.filters(), context.run().arg("foo").current_dir(&child).env_remove(EnvVars::VIRTUAL_ENV), @r###" success: true exit_code: 0 ----- stdout ----- @@ -3935,8 +3840,8 @@ fn init_with_description() -> Result<()> { dependencies = [] [build-system] - requires = ["hatchling"] - build-backend = "hatchling.build" + requires = ["uv_build>=[CURRENT_VERSION],<[NEXT_BREAKING]"] + build-backend = "uv_build" "# ); }); @@ -3977,8 +3882,8 @@ fn init_without_description() -> Result<()> { dependencies = [] [build-system] - requires = ["hatchling"] - build-backend = "hatchling.build" + requires = ["uv_build>=[CURRENT_VERSION],<[NEXT_BREAKING]"] + build-backend = "uv_build" "# ); }); diff --git a/docs/concepts/build-backend.md b/docs/concepts/build-backend.md index 5f52463bf..d2edf1bad 100644 --- a/docs/concepts/build-backend.md +++ b/docs/concepts/build-backend.md @@ -1,10 +1,5 @@ # The uv build backend -!!! note - - Currently, the default build backend for `uv init` is - [hatchling](https://pypi.org/project/hatchling/). This will change to `uv` in a future version. - A build backend transforms a source tree (i.e., a directory) into a source distribution or a wheel. uv supports all build backends (as specified by [PEP 517](https://peps.python.org/pep-0517/)), but @@ -49,7 +44,7 @@ build-backend = "uv_build" To create a new project that uses the uv build backend, use `uv init`: ```console -$ uv init --build-backend uv +$ uv init ``` When the project is built, e.g., with [`uv build`](../guides/package.md), the uv build backend will From 25e69458b1f6bf8aa937bb0d83b660e8d5c088e7 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 16 Jul 2025 14:26:42 -0500 Subject: [PATCH 250/349] Stabilize addition of Python versions to the Windows registry (#14625) Following #14614 this is non-fatal and has an opt-out so it should be safe to stabilize. --- crates/uv/src/commands/python/install.rs | 2 +- crates/uv/src/commands/python/uninstall.rs | 2 +- docs/concepts/python-versions.md | 15 +++++++++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/crates/uv/src/commands/python/install.rs b/crates/uv/src/commands/python/install.rs index feb0cf7c7..b9d4660df 100644 --- a/crates/uv/src/commands/python/install.rs +++ b/crates/uv/src/commands/python/install.rs @@ -501,7 +501,7 @@ pub(crate) async fn install( ); } - if preview.is_enabled() && !matches!(registry, Some(false)) { + if !matches!(registry, Some(false)) { #[cfg(windows)] { match uv_python::windows_registry::create_registry_entry(installation) { diff --git a/crates/uv/src/commands/python/uninstall.rs b/crates/uv/src/commands/python/uninstall.rs index 642942d07..dd306fc4d 100644 --- a/crates/uv/src/commands/python/uninstall.rs +++ b/crates/uv/src/commands/python/uninstall.rs @@ -211,7 +211,7 @@ async fn do_uninstall( } #[cfg(windows)] - if preview.is_enabled() { + { uv_python::windows_registry::remove_registry_entry( &matching_installations, all, diff --git a/docs/concepts/python-versions.md b/docs/concepts/python-versions.md index a7472bea8..ee18fa9da 100644 --- a/docs/concepts/python-versions.md +++ b/docs/concepts/python-versions.md @@ -435,3 +435,18 @@ are not yet available for musl Linux on ARM). ### PyPy distributions PyPy distributions are provided by the PyPy project. + +## Registration in the Windows registry + +On Windows, installation of managed Python versions will register them with the Windows registry as +defined by [PEP 514](https://peps.python.org/pep-0514/). + +After installation, the Python versions can be selected with the `py` launcher, e.g.: + +```console +$ uv python install 3.13.1 +$ py -V:Astral/CPython3.13.1 +``` + +On uninstall, uv will remove the registry entry for the target version as well as any broken +registry entries. From 2df06ebfbc2d4df90d47ef9a9e631ba9926712e0 Mon Sep 17 00:00:00 2001 From: John Mumm Date: Wed, 16 Jul 2025 21:25:48 +0200 Subject: [PATCH 251/349] Require `uv venv --clear` before removing an existing directory (#14309) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit By default, `uv venv ` currently removes the ` directory if it exists. This can be surprising behavior: not everyone expects an existing environment to be overwritten. This PR updates the default to fail if a non-empty `` directory already exists and neither `--allow-existing` nor the new `-c/--clear` option is provided (if a TTY is detected, it prompts first). If it's not a TTY, then uv will only warn and not fail for now — we'll make this an error in the future. I've also added a corresponding `UV_VENV_CLEAR` env var. I've chosen to use `--clear` instead of `--force` for this option because it is used by the `venv` module and `virtualenv` and will be familiar to users. I also think its meaning is clearer in this context than `--force` (which could plausibly mean force overwrite just the virtual environment files, which is what our current `--allow-existing` option does). Closes #1472. --------- Co-authored-by: Zanie Blue --- .github/workflows/ci.yml | 10 +- Cargo.lock | 4 + crates/uv-build-frontend/src/lib.rs | 2 +- crates/uv-cli/src/compat.rs | 9 - crates/uv-cli/src/lib.rs | 13 +- crates/uv-console/src/lib.rs | 32 +++- crates/uv-static/src/env_vars.rs | 4 + crates/uv-tool/src/lib.rs | 2 +- crates/uv-virtualenv/Cargo.toml | 4 + crates/uv-virtualenv/src/lib.rs | 6 +- crates/uv-virtualenv/src/virtualenv.rs | 169 ++++++++++++++---- crates/uv/src/commands/project/environment.rs | 16 +- crates/uv/src/commands/project/mod.rs | 8 +- crates/uv/src/commands/project/run.rs | 8 +- crates/uv/src/commands/venv.rs | 5 +- crates/uv/src/lib.rs | 4 +- crates/uv/src/settings.rs | 3 + crates/uv/tests/it/cache_prune.rs | 2 +- crates/uv/tests/it/common/mod.rs | 1 + crates/uv/tests/it/pip_install.rs | 7 +- crates/uv/tests/it/pip_sync.rs | 2 +- crates/uv/tests/it/sync.rs | 1 + crates/uv/tests/it/venv.rs | 94 +++++----- docs/reference/cli.md | 6 +- docs/reference/environment.md | 5 + 25 files changed, 282 insertions(+), 135 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e9beddcc5..4fb67346e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1042,7 +1042,7 @@ jobs: - name: "Create a virtual environment (uv)" run: | - ./uv venv -p 3.13t --managed-python + ./uv venv -c -p 3.13t --managed-python - name: "Check version (uv)" run: | @@ -1087,7 +1087,7 @@ jobs: - name: "Create a virtual environment (uv)" run: | - ./uv venv -p 3.13 --managed-python + ./uv venv -c -p 3.13 --managed-python - name: "Check version (uv)" run: | @@ -1132,7 +1132,7 @@ jobs: - name: "Create a virtual environment (uv)" run: | - ./uv venv -p 3.13 --managed-python + ./uv venv -c -p 3.13 --managed-python - name: "Check version (uv)" run: | @@ -1758,14 +1758,14 @@ jobs: ./uv run --no-project python -c "from built_by_uv import greet; print(greet())" # Test both `build_wheel` and `build_sdist` through uv - ./uv venv -v + ./uv venv -c -v ./uv build -v --force-pep517 scripts/packages/built-by-uv --find-links crates/uv-build/dist --offline ./uv pip install -v scripts/packages/built-by-uv/dist/*.tar.gz --find-links crates/uv-build/dist --offline --no-deps ./uv run --no-project python -c "from built_by_uv import greet; print(greet())" # Test both `build_wheel` and `build_sdist` through the official `build` rm -rf scripts/packages/built-by-uv/dist/ - ./uv venv -v + ./uv venv -c -v ./uv pip install build # Add the uv binary to PATH for `build` to find PATH="$(pwd):$PATH" UV_OFFLINE=1 UV_FIND_LINKS=crates/uv-build/dist ./uv run --no-project python -m build -v --installer uv scripts/packages/built-by-uv diff --git a/Cargo.lock b/Cargo.lock index 2963b6374..0900699cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5998,18 +5998,22 @@ version = "0.7.22" name = "uv-virtualenv" version = "0.0.4" dependencies = [ + "console 0.15.11", "fs-err 3.1.1", "itertools 0.14.0", + "owo-colors", "pathdiff", "self-replace", "thiserror 2.0.12", "tracing", "uv-configuration", + "uv-console", "uv-fs", "uv-pypi-types", "uv-python", "uv-shell", "uv-version", + "uv-warnings", ] [[package]] diff --git a/crates/uv-build-frontend/src/lib.rs b/crates/uv-build-frontend/src/lib.rs index 5cbaece2e..67bee9619 100644 --- a/crates/uv-build-frontend/src/lib.rs +++ b/crates/uv-build-frontend/src/lib.rs @@ -331,7 +331,7 @@ impl SourceBuild { interpreter.clone(), uv_virtualenv::Prompt::None, false, - false, + uv_virtualenv::OnExisting::Remove, false, false, false, diff --git a/crates/uv-cli/src/compat.rs b/crates/uv-cli/src/compat.rs index d29afa760..344d1a4e7 100644 --- a/crates/uv-cli/src/compat.rs +++ b/crates/uv-cli/src/compat.rs @@ -266,9 +266,6 @@ enum Resolver { /// These represent a subset of the `virtualenv` interface that uv supports by default. #[derive(Args)] pub struct VenvCompatArgs { - #[clap(long, hide = true)] - clear: bool, - #[clap(long, hide = true)] no_seed: bool, @@ -289,12 +286,6 @@ impl CompatArgs for VenvCompatArgs { /// 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.clear { - warn_user!( - "virtualenv's `--clear` has no effect (uv always clears the virtual environment)" - ); - } - if self.no_seed { warn_user!( "virtualenv's `--no-seed` has no effect (uv omits seed packages by default)" diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 4c01fd780..5df818654 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -2615,16 +2615,23 @@ pub struct VenvArgs { #[arg(long, value_parser = clap::builder::BoolishValueParser::new(), env = EnvVars::UV_VENV_SEED)] pub seed: bool, + /// Remove any existing files or directories at the target path. + /// + /// By default, `uv venv` will exit with an error if the given path is non-empty. The + /// `--clear` option will instead clear a non-empty path before creating a new virtual + /// environment. + #[clap(long, short, overrides_with = "allow_existing", value_parser = clap::builder::BoolishValueParser::new(), env = EnvVars::UV_VENV_CLEAR)] + pub clear: bool, + /// Preserve any existing files or directories at the target path. /// - /// By default, `uv venv` will remove an existing virtual environment at the given path, and - /// exit with an error if the path is non-empty but _not_ a virtual environment. The + /// By default, `uv venv` will exit with an error if the given path is non-empty. The /// `--allow-existing` option will instead write to the given path, regardless of its contents, /// and without clearing it beforehand. /// /// WARNING: This option can lead to unexpected behavior if the existing virtual environment and /// the newly-created virtual environment are linked to different Python interpreters. - #[clap(long)] + #[clap(long, overrides_with = "clear")] pub allow_existing: bool, /// The path to the virtual environment to create. diff --git a/crates/uv-console/src/lib.rs b/crates/uv-console/src/lib.rs index 807b77aa4..24c5eea16 100644 --- a/crates/uv-console/src/lib.rs +++ b/crates/uv-console/src/lib.rs @@ -6,6 +6,25 @@ use std::{cmp::Ordering, iter}; /// This is a slimmed-down version of `dialoguer::Confirm`, with the post-confirmation report /// enabled. pub fn confirm(message: &str, term: &Term, default: bool) -> std::io::Result { + confirm_inner(message, None, term, default) +} + +/// Prompt the user for confirmation in the given [`Term`], with a hint. +pub fn confirm_with_hint( + message: &str, + hint: &str, + term: &Term, + default: bool, +) -> std::io::Result { + confirm_inner(message, Some(hint), term, default) +} + +fn confirm_inner( + message: &str, + hint: Option<&str>, + term: &Term, + default: bool, +) -> std::io::Result { let prompt = format!( "{} {} {} {} {}", style("?".to_string()).for_stderr().yellow(), @@ -18,6 +37,13 @@ pub fn confirm(message: &str, term: &Term, default: bool) -> std::io::Result std::io::Result { - if metadata.is_file() { - return Err(Error::Io(io::Error::new( - io::ErrorKind::AlreadyExists, - format!("File exists at `{}`", location.user_display()), - ))); - } else if metadata.is_dir() { - if allow_existing { - debug!("Allowing existing directory"); - } else if uv_fs::is_virtualenv_base(location) { - debug!("Removing existing directory"); - - // On Windows, if the current executable is in the directory, guard against - // self-deletion. - #[cfg(windows)] - if let Ok(itself) = std::env::current_exe() { - let target = std::path::absolute(location)?; - if itself.starts_with(&target) { - debug!("Detected self-delete of executable: {}", itself.display()); - self_replace::self_delete_outside_path(location)?; - } - } - - fs::remove_dir_all(location)?; - fs::create_dir_all(location)?; - } else if location - .read_dir() - .is_ok_and(|mut dir| dir.next().is_none()) + Ok(metadata) if metadata.is_file() => { + return Err(Error::Io(io::Error::new( + io::ErrorKind::AlreadyExists, + format!("File exists at `{}`", location.user_display()), + ))); + } + Ok(metadata) if metadata.is_dir() => { + let name = if uv_fs::is_virtualenv_base(location) { + "virtual environment" + } else { + "directory" + }; + match on_existing { + OnExisting::Allow => { + debug!("Allowing existing {name} due to `--allow-existing`"); + } + OnExisting::Remove => { + debug!("Removing existing {name} due to `--clear`"); + remove_venv_directory(location)?; + } + OnExisting::Fail + if location + .read_dir() + .is_ok_and(|mut dir| dir.next().is_none()) => { debug!("Ignoring empty directory"); - } else { - return Err(Error::Io(io::Error::new( - io::ErrorKind::AlreadyExists, - format!( - "The directory `{}` exists, but it's not a virtual environment", - location.user_display() - ), - ))); + } + OnExisting::Fail => { + match confirm_clear(location, name)? { + Some(true) => { + debug!("Removing existing {name} due to confirmation"); + remove_venv_directory(location)?; + } + Some(false) => { + let hint = format!( + "Use the `{}` flag or set `{}` to replace the existing {name}", + "--clear".green(), + "UV_VENV_CLEAR=1".green() + ); + return Err(Error::Io(io::Error::new( + io::ErrorKind::AlreadyExists, + format!( + "A {name} already exists at: {}\n\n{}{} {hint}", + location.user_display(), + "hint".bold().cyan(), + ":".bold(), + ), + ))); + } + // When we don't have a TTY, warn that the behavior will change in the future + None => { + warn_user_once!( + "A {name} already exists at `{}`. In the future, uv will require `{}` to replace it", + location.user_display(), + "--clear".green(), + ); + } + } } } } + Ok(_) => { + // It's not a file or a directory + return Err(Error::Io(io::Error::new( + io::ErrorKind::AlreadyExists, + format!("Object already exists at `{}`", location.user_display()), + ))); + } Err(err) if err.kind() == io::ErrorKind::NotFound => { fs::create_dir_all(location)?; } @@ -464,6 +494,71 @@ pub(crate) fn create( }) } +/// Prompt a confirmation that the virtual environment should be cleared. +/// +/// If not a TTY, returns `None`. +fn confirm_clear(location: &Path, name: &'static str) -> Result, io::Error> { + let term = Term::stderr(); + if term.is_term() { + let prompt = format!( + "A {name} already exists at `{}`. Do you want to replace it?", + location.user_display(), + ); + let hint = format!( + "Use the `{}` flag or set `{}` to skip this prompt", + "--clear".green(), + "UV_VENV_CLEAR=1".green() + ); + Ok(Some(uv_console::confirm_with_hint( + &prompt, &hint, &term, true, + )?)) + } else { + Ok(None) + } +} + +fn remove_venv_directory(location: &Path) -> Result<(), Error> { + // On Windows, if the current executable is in the directory, guard against + // self-deletion. + #[cfg(windows)] + if let Ok(itself) = std::env::current_exe() { + let target = std::path::absolute(location)?; + if itself.starts_with(&target) { + debug!("Detected self-delete of executable: {}", itself.display()); + self_replace::self_delete_outside_path(location)?; + } + } + + fs::remove_dir_all(location)?; + fs::create_dir_all(location)?; + + Ok(()) +} + +#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] +pub enum OnExisting { + /// Fail if the directory already exists and is non-empty. + #[default] + Fail, + /// Allow an existing directory, overwriting virtual environment files while retaining other + /// files in the directory. + Allow, + /// Remove an existing directory. + Remove, +} + +impl OnExisting { + pub fn from_args(allow_existing: bool, clear: bool) -> Self { + if allow_existing { + OnExisting::Allow + } else if clear { + OnExisting::Remove + } else { + OnExisting::default() + } + } +} + #[derive(Debug, Copy, Clone)] enum WindowsExecutable { /// The `python.exe` executable (or `venvlauncher.exe` launcher shim). diff --git a/crates/uv/src/commands/project/environment.rs b/crates/uv/src/commands/project/environment.rs index cf1add99a..4f9d936c5 100644 --- a/crates/uv/src/commands/project/environment.rs +++ b/crates/uv/src/commands/project/environment.rs @@ -2,13 +2,6 @@ use std::path::Path; use tracing::debug; -use uv_cache::{Cache, CacheBucket}; -use uv_cache_key::{cache_digest, hash_digest}; -use uv_configuration::{Concurrency, Constraints, PreviewMode}; -use uv_distribution_types::{Name, Resolution}; -use uv_fs::PythonExt; -use uv_python::{Interpreter, PythonEnvironment, canonicalize_executable}; - use crate::commands::pip::loggers::{InstallLogger, ResolveLogger}; use crate::commands::pip::operations::Modifications; use crate::commands::project::{ @@ -17,6 +10,13 @@ use crate::commands::project::{ use crate::printer::Printer; use crate::settings::{NetworkSettings, ResolverInstallerSettings}; +use uv_cache::{Cache, CacheBucket}; +use uv_cache_key::{cache_digest, hash_digest}; +use uv_configuration::{Concurrency, Constraints, PreviewMode}; +use uv_distribution_types::{Name, Resolution}; +use uv_fs::PythonExt; +use uv_python::{Interpreter, PythonEnvironment, canonicalize_executable}; + /// An ephemeral [`PythonEnvironment`] for running an individual command. #[derive(Debug)] pub(crate) struct EphemeralEnvironment(PythonEnvironment); @@ -171,7 +171,7 @@ impl CachedEnvironment { interpreter, uv_virtualenv::Prompt::None, false, - false, + uv_virtualenv::OnExisting::Remove, true, false, false, diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index fde2b638c..23655c1ca 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -1336,7 +1336,7 @@ impl ProjectEnvironment { interpreter, prompt, false, - false, + uv_virtualenv::OnExisting::Remove, false, false, upgradeable, @@ -1375,7 +1375,7 @@ impl ProjectEnvironment { interpreter, prompt, false, - false, + uv_virtualenv::OnExisting::Remove, false, false, upgradeable, @@ -1527,7 +1527,7 @@ impl ScriptEnvironment { interpreter, prompt, false, - false, + uv_virtualenv::OnExisting::Remove, false, false, upgradeable, @@ -1563,7 +1563,7 @@ impl ScriptEnvironment { interpreter, prompt, false, - false, + uv_virtualenv::OnExisting::Remove, false, false, upgradeable, diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index 16ebf88fb..ba8935013 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -465,7 +465,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl interpreter, uv_virtualenv::Prompt::None, false, - false, + uv_virtualenv::OnExisting::Remove, false, false, false, @@ -670,7 +670,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl interpreter, uv_virtualenv::Prompt::None, false, - false, + uv_virtualenv::OnExisting::Remove, false, false, false, @@ -907,7 +907,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl interpreter, uv_virtualenv::Prompt::None, false, - false, + uv_virtualenv::OnExisting::Remove, false, false, false, @@ -1038,7 +1038,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl base_interpreter.clone(), uv_virtualenv::Prompt::None, false, - false, + uv_virtualenv::OnExisting::Remove, false, false, false, diff --git a/crates/uv/src/commands/venv.rs b/crates/uv/src/commands/venv.rs index 02bc818f8..92eb1ead7 100644 --- a/crates/uv/src/commands/venv.rs +++ b/crates/uv/src/commands/venv.rs @@ -27,6 +27,7 @@ use uv_resolver::{ExcludeNewer, FlatIndex}; use uv_settings::PythonInstallMirrors; use uv_shell::{Shell, shlex_posix, shlex_windows}; use uv_types::{AnyErrorBuild, BuildContext, BuildIsolation, BuildStack, HashStrategy}; +use uv_virtualenv::OnExisting; use uv_warnings::warn_user; use uv_workspace::{DiscoveryOptions, VirtualProject, WorkspaceCache, WorkspaceError}; @@ -73,7 +74,7 @@ pub(crate) async fn venv( prompt: uv_virtualenv::Prompt, system_site_packages: bool, seed: bool, - allow_existing: bool, + on_existing: OnExisting, exclude_newer: Option, concurrency: Concurrency, no_config: bool, @@ -209,7 +210,7 @@ pub(crate) async fn venv( interpreter, prompt, system_site_packages, - allow_existing, + on_existing, relocatable, seed, upgradeable, diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 433f5afd3..9c9b41065 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -1032,6 +1032,8 @@ async fn run(mut cli: Cli) -> Result { let python_request: Option = args.settings.python.as_deref().map(PythonRequest::parse); + let on_existing = uv_virtualenv::OnExisting::from_args(args.allow_existing, args.clear); + commands::venv( &project_dir, args.path, @@ -1048,7 +1050,7 @@ async fn run(mut cli: Cli) -> Result { uv_virtualenv::Prompt::from_args(prompt), args.system_site_packages, args.seed, - args.allow_existing, + on_existing, args.settings.exclude_newer, globals.concurrency, cli.top_level.no_config, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index bf3bca4a4..1ebeecba8 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -2623,6 +2623,7 @@ impl BuildSettings { pub(crate) struct VenvSettings { pub(crate) seed: bool, pub(crate) allow_existing: bool, + pub(crate) clear: bool, pub(crate) path: Option, pub(crate) prompt: Option, pub(crate) system_site_packages: bool, @@ -2641,6 +2642,7 @@ impl VenvSettings { no_system, seed, allow_existing, + clear, path, prompt, system_site_packages, @@ -2658,6 +2660,7 @@ impl VenvSettings { Self { seed, allow_existing, + clear, path, prompt, system_site_packages, diff --git a/crates/uv/tests/it/cache_prune.rs b/crates/uv/tests/it/cache_prune.rs index a6ec48bd4..99493fe21 100644 --- a/crates/uv/tests/it/cache_prune.rs +++ b/crates/uv/tests/it/cache_prune.rs @@ -227,7 +227,7 @@ fn prune_unzipped() -> Result<()> { Removed [N] files ([SIZE]) "###); - context.venv().assert().success(); + context.venv().arg("--clear").assert().success(); // Reinstalling the source distribution should not require re-downloading the source // distribution. diff --git a/crates/uv/tests/it/common/mod.rs b/crates/uv/tests/it/common/mod.rs index bc6e65f4e..9d3c1428f 100644 --- a/crates/uv/tests/it/common/mod.rs +++ b/crates/uv/tests/it/common/mod.rs @@ -1415,6 +1415,7 @@ pub fn create_venv_from_executable>(path: P, cache_dir: &ChildPat assert_cmd::Command::new(get_bin()) .arg("venv") .arg(path.as_ref().as_os_str()) + .arg("--clear") .arg("--cache-dir") .arg(cache_dir.path()) .arg("--python") diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index 123d9066b..9cd394bbd 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -2859,7 +2859,7 @@ fn install_no_binary_cache() { ); // Re-create the virtual environment. - context.venv().assert().success(); + context.venv().arg("--clear").assert().success(); // Re-install. The distribution should be installed from the cache. uv_snapshot!( @@ -2877,7 +2877,7 @@ fn install_no_binary_cache() { ); // Re-create the virtual environment. - context.venv().assert().success(); + context.venv().arg("--clear").assert().success(); // Install with `--no-binary`. The distribution should be built from source, despite a binary // distribution being available in the cache. @@ -3088,7 +3088,7 @@ fn cache_priority() { ); // Re-create the virtual environment. - context.venv().assert().success(); + context.venv().arg("--clear").assert().success(); // Install `idna` without a version specifier. uv_snapshot!( @@ -8252,6 +8252,7 @@ fn install_relocatable() -> Result<()> { context .venv() .arg(context.venv.as_os_str()) + .arg("--clear") .arg("--python") .arg("3.12") .arg("--relocatable") diff --git a/crates/uv/tests/it/pip_sync.rs b/crates/uv/tests/it/pip_sync.rs index 537c5dff2..4b249be8c 100644 --- a/crates/uv/tests/it/pip_sync.rs +++ b/crates/uv/tests/it/pip_sync.rs @@ -5625,7 +5625,7 @@ fn sync_seed() -> Result<()> { ); // Re-create the environment with seed packages. - uv_snapshot!(context.filters(), context.venv() + uv_snapshot!(context.filters(), context.venv().arg("--clear") .arg("--seed"), @r" success: true exit_code: 0 diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index c225225b8..35a06ea57 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -9987,6 +9987,7 @@ fn sync_when_virtual_environment_incompatible_with_interpreter() -> Result<()> { context .venv() .arg(context.venv.as_os_str()) + .arg("--clear") .arg("--python") .arg("3.12") .assert() diff --git a/crates/uv/tests/it/venv.rs b/crates/uv/tests/it/venv.rs index 43cacb640..2430e607d 100644 --- a/crates/uv/tests/it/venv.rs +++ b/crates/uv/tests/it/venv.rs @@ -30,10 +30,28 @@ fn create_venv() { context.venv.assert(predicates::path::is_dir()); - // Create a virtual environment at the same location, which should replace it. uv_snapshot!(context.filters(), context.venv() .arg(context.venv.as_os_str()) .arg("--python") + .arg("3.12"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + Creating virtual environment at: .venv + warning: A virtual environment already exists at `.venv`. In the future, uv will require `--clear` to replace it + Activate with: source .venv/[BIN]/activate + " + ); + + // Create a virtual environment at the same location using `--clear`, + // which should replace it. + uv_snapshot!(context.filters(), context.venv() + .arg(context.venv.as_os_str()) + .arg("--clear") + .arg("--python") .arg("3.12"), @r###" success: true exit_code: 0 @@ -162,7 +180,7 @@ fn create_venv_project_environment() -> Result<()> { .assert(predicates::path::is_dir()); // Or, of they opt-out with `--no-workspace` or `--no-project` - uv_snapshot!(context.filters(), context.venv().arg("--no-workspace"), @r###" + uv_snapshot!(context.filters(), context.venv().arg("--clear").arg("--no-workspace"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -174,7 +192,7 @@ fn create_venv_project_environment() -> Result<()> { "### ); - uv_snapshot!(context.filters(), context.venv().arg("--no-project"), @r###" + uv_snapshot!(context.filters(), context.venv().arg("--clear").arg("--no-project"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -252,7 +270,7 @@ fn create_venv_reads_request_from_python_version_file() { .write_str("3.12") .unwrap(); - uv_snapshot!(context.filters(), context.venv(), @r###" + uv_snapshot!(context.filters(), context.venv().arg("--clear"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -291,7 +309,7 @@ fn create_venv_reads_request_from_python_versions_file() { .write_str("3.12\n3.11") .unwrap(); - uv_snapshot!(context.filters(), context.venv(), @r###" + uv_snapshot!(context.filters(), context.venv().arg("--clear"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -334,7 +352,7 @@ fn create_venv_respects_pyproject_requires_python() -> Result<()> { "# })?; - uv_snapshot!(context.filters(), context.venv(), @r###" + uv_snapshot!(context.filters(), context.venv().arg("--clear"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -357,7 +375,7 @@ fn create_venv_respects_pyproject_requires_python() -> Result<()> { "# })?; - uv_snapshot!(context.filters(), context.venv(), @r###" + uv_snapshot!(context.filters(), context.venv().arg("--clear"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -380,7 +398,7 @@ fn create_venv_respects_pyproject_requires_python() -> Result<()> { "# })?; - uv_snapshot!(context.filters(), context.venv(), @r###" + uv_snapshot!(context.filters(), context.venv().arg("--clear"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -414,7 +432,7 @@ fn create_venv_respects_pyproject_requires_python() -> Result<()> { "# })?; - uv_snapshot!(context.filters(), context.venv(), @r###" + uv_snapshot!(context.filters(), context.venv().arg("--clear"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -437,7 +455,7 @@ fn create_venv_respects_pyproject_requires_python() -> Result<()> { "# })?; - uv_snapshot!(context.filters(), context.venv(), @r###" + uv_snapshot!(context.filters(), context.venv().arg("--clear"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -460,7 +478,7 @@ fn create_venv_respects_pyproject_requires_python() -> Result<()> { "# })?; - uv_snapshot!(context.filters(), context.venv(), @r###" + uv_snapshot!(context.filters(), context.venv().arg("--clear"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -475,7 +493,7 @@ fn create_venv_respects_pyproject_requires_python() -> Result<()> { context.venv.assert(predicates::path::is_dir()); // We warn if we receive an incompatible version - uv_snapshot!(context.filters(), context.venv().arg("--python").arg("3.11"), @r" + uv_snapshot!(context.filters(), context.venv().arg("--clear").arg("--python").arg("3.11"), @r" success: true exit_code: 0 ----- stdout ----- @@ -527,7 +545,7 @@ fn create_venv_respects_group_requires_python() -> Result<()> { "# })?; - uv_snapshot!(context.filters(), context.venv(), @r" + uv_snapshot!(context.filters(), context.venv().arg("--clear"), @r" success: true exit_code: 0 ----- stdout ----- @@ -560,7 +578,7 @@ fn create_venv_respects_group_requires_python() -> Result<()> { "# })?; - uv_snapshot!(context.filters(), context.venv(), @r" + uv_snapshot!(context.filters(), context.venv().arg("--clear"), @r" success: true exit_code: 0 ----- stdout ----- @@ -593,7 +611,7 @@ fn create_venv_respects_group_requires_python() -> Result<()> { "# })?; - uv_snapshot!(context.filters(), context.venv(), @r" + uv_snapshot!(context.filters(), context.venv().arg("--clear"), @r" success: true exit_code: 0 ----- stdout ----- @@ -621,7 +639,7 @@ fn create_venv_respects_group_requires_python() -> Result<()> { "# })?; - uv_snapshot!(context.filters(), context.venv().arg("--python").arg("3.11"), @r" + uv_snapshot!(context.filters(), context.venv().arg("--clear").arg("--python").arg("3.11"), @r" success: true exit_code: 0 ----- stdout ----- @@ -654,7 +672,7 @@ fn create_venv_respects_group_requires_python() -> Result<()> { "# })?; - uv_snapshot!(context.filters(), context.venv().arg("--python").arg("3.11"), @r" + uv_snapshot!(context.filters(), context.venv().arg("--clear").arg("--python").arg("3.11"), @r" success: false exit_code: 2 ----- stdout ----- @@ -945,15 +963,15 @@ fn non_empty_dir_exists() -> Result<()> { .arg(context.venv.as_os_str()) .arg("--python") .arg("3.12"), @r" - success: false - exit_code: 2 + success: true + exit_code: 0 ----- stdout ----- ----- stderr ----- Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] Creating virtual environment at: .venv - error: Failed to create virtual environment - Caused by: The directory `.venv` exists, but it's not a virtual environment + warning: A directory already exists at `.venv`. In the future, uv will require `--clear` to replace it + Activate with: source .venv/[BIN]/activate " ); @@ -973,15 +991,15 @@ fn non_empty_dir_exists_allow_existing() -> Result<()> { .arg(context.venv.as_os_str()) .arg("--python") .arg("3.12"), @r" - success: false - exit_code: 2 + success: true + exit_code: 0 ----- stdout ----- ----- stderr ----- Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] Creating virtual environment at: .venv - error: Failed to create virtual environment - Caused by: The directory `.venv` exists, but it's not a virtual environment + warning: A directory already exists at `.venv`. In the future, uv will require `--clear` to replace it + Activate with: source .venv/[BIN]/activate " ); @@ -1102,31 +1120,6 @@ fn windows_shims() -> Result<()> { Ok(()) } -#[test] -fn virtualenv_compatibility() { - let context = TestContext::new_with_versions(&["3.12"]); - - // Create a virtual environment at `.venv`, passing the redundant `--clear` flag. - uv_snapshot!(context.filters(), context.venv() - .arg(context.venv.as_os_str()) - .arg("--clear") - .arg("--python") - .arg("3.12"), @r###" - success: true - exit_code: 0 - ----- stdout ----- - - ----- stderr ----- - warning: virtualenv's `--clear` has no effect (uv always clears the virtual environment) - Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] - Creating virtual environment at: .venv - Activate with: source .venv/[BIN]/activate - "### - ); - - context.venv.assert(predicates::path::is_dir()); -} - #[test] fn verify_pyvenv_cfg() { let context = TestContext::new("3.12"); @@ -1154,6 +1147,7 @@ fn verify_pyvenv_cfg_relocatable() { context .venv() .arg(context.venv.as_os_str()) + .arg("--clear") .arg("--python") .arg("3.12") .arg("--relocatable") diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 881c96697..9be647449 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -4683,7 +4683,7 @@ uv venv [OPTIONS] [PATH]

    Options

    --allow-existing

    Preserve any existing files or directories at the target path.

    -

    By default, uv venv will remove an existing virtual environment at the given path, and exit with an error if the path is non-empty but not a virtual environment. The --allow-existing option will instead write to the given path, regardless of its contents, and without clearing it beforehand.

    +

    By default, uv venv will exit with an error if the given path is non-empty. The --allow-existing option will instead write to the given path, regardless of its contents, and without clearing it beforehand.

    WARNING: This option can lead to unexpected behavior if the existing virtual environment and the newly-created virtual environment are linked to different Python interpreters.

    --allow-insecure-host, --trusted-host allow-insecure-host

    Allow insecure connections to a host.

    Can be provided multiple times.

    @@ -4692,7 +4692,9 @@ uv venv [OPTIONS] [PATH]

    May also be set with the UV_INSECURE_HOST environment variable.

    --cache-dir cache-dir

    Path to the cache directory.

    Defaults to $XDG_CACHE_HOME/uv or $HOME/.cache/uv on macOS and Linux, and %LOCALAPPDATA%\uv\cache on Windows.

    To view the location of the cache directory, run uv cache dir.

    -

    May also be set with the UV_CACHE_DIR environment variable.

    --color color-choice

    Control the use of color in output.

    +

    May also be set with the UV_CACHE_DIR environment variable.

    --clear, -c

    Remove any existing files or directories at the target path.

    +

    By default, uv venv will exit with an error if the given path is non-empty. The --clear option will instead clear a non-empty path before creating a new virtual environment.

    +

    May also be set with the UV_VENV_CLEAR environment variable.

    --color color-choice

    Control the use of color in output.

    By default, uv will automatically detect support for colors when writing to a terminal.

    Possible values:

      diff --git a/docs/reference/environment.md b/docs/reference/environment.md index a64869edb..e848d4a41 100644 --- a/docs/reference/environment.md +++ b/docs/reference/environment.md @@ -458,6 +458,11 @@ Equivalent to the `--torch-backend` command-line argument (e.g., `cpu`, `cu126`, Used ephemeral environments like CI to install uv to a specific path while preventing the installer from modifying shell profiles or environment variables. +### `UV_VENV_CLEAR` + +Equivalent to the `--clear` command-line argument. If set, uv will remove any +existing files or directories at the target path. + ### `UV_VENV_SEED` Install seed packages (one or more of: `pip`, `setuptools`, and `wheel`) into the virtual environment From b98ac8c224f651a61ee3c44f6829d70cde80b3a9 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 16 Jul 2025 15:31:47 -0500 Subject: [PATCH 252/349] Validate that discovered interpreters meet the Python preference (#7934) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes https://github.com/astral-sh/uv/issues/5144 e.g. ``` ❯ cargo run -q -- sync --python-preference only-system Using CPython 3.12.6 interpreter at: /opt/homebrew/opt/python@3.12/bin/python3.12 Removed virtual environment at: .venv Creating virtual environment at: .venv Resolved 9 packages in 14ms Installed 8 packages in 9ms + anyio==4.6.0 + certifi==2024.8.30 + h11==0.14.0 + httpcore==1.0.5 + httpx==0.27.2 + idna==3.10 + ruff==0.6.7 + sniffio==1.3.1 ❯ cargo run -q -- sync --python-preference only-managed Using CPython 3.12.1 Removed virtual environment at: .venv Creating virtual environment at: .venv Resolved 9 packages in 14ms Installed 8 packages in 11ms + anyio==4.6.0 + certifi==2024.8.30 + h11==0.14.0 + httpcore==1.0.5 + httpx==0.27.2 + idna==3.10 + ruff==0.6.7 + sniffio==1.3.1 ``` --- crates/uv-python/src/discovery.rs | 113 ++++++++++++++++++++- crates/uv-python/src/environment.rs | 3 +- crates/uv-python/src/interpreter.rs | 23 ++++- crates/uv-python/src/lib.rs | 2 +- crates/uv-static/src/env_vars.rs | 8 ++ crates/uv/src/commands/project/mod.rs | 26 ++++- crates/uv/tests/it/common/mod.rs | 21 ++++ crates/uv/tests/it/pip_install.rs | 55 ++++++++++ crates/uv/tests/it/python_find.rs | 51 ++++++++++ crates/uv/tests/it/run.rs | 46 +++++++++ crates/uv/tests/it/sync.rs | 141 ++++++++++++++++++++++++++ crates/uv/tests/it/venv.rs | 66 ++++++++++++ 12 files changed, 544 insertions(+), 11 deletions(-) diff --git a/crates/uv-python/src/discovery.rs b/crates/uv-python/src/discovery.rs index c067082dd..f10b480e2 100644 --- a/crates/uv-python/src/discovery.rs +++ b/crates/uv-python/src/discovery.rs @@ -446,7 +446,16 @@ fn python_executables_from_installed<'a>( .flatten(); match preference { - PythonPreference::OnlyManaged => Box::new(from_managed_installations), + PythonPreference::OnlyManaged => { + // TODO(zanieb): Ideally, we'd create "fake" managed installation directories for tests, + // but for now... we'll just include the test interpreters which are always on the + // search path. + if std::env::var(uv_static::EnvVars::UV_INTERNAL__TEST_PYTHON_MANAGED).is_ok() { + Box::new(from_managed_installations.chain(from_search_path)) + } else { + Box::new(from_managed_installations) + } + } PythonPreference::Managed => Box::new( from_managed_installations .chain(from_search_path) @@ -730,6 +739,9 @@ fn python_interpreters<'a>( false } }) + .filter_ok(move |(source, interpreter)| { + satisfies_python_preference(*source, interpreter, preference) + }) } /// Lazily convert Python executables into interpreters. @@ -857,6 +869,93 @@ fn source_satisfies_environment_preference( } } +/// Returns true if a Python interpreter matches the [`PythonPreference`]. +pub fn satisfies_python_preference( + source: PythonSource, + interpreter: &Interpreter, + preference: PythonPreference, +) -> bool { + // If the source is "explicit", we will not apply the Python preference, e.g., if the user has + // activated a virtual environment, we should always allow it. We may want to invalidate the + // environment in some cases, like in projects, but we can't distinguish between explicit + // requests for a different Python preference or a persistent preference in a configuration file + // which would result in overly aggressive invalidation. + let is_explicit = match source { + PythonSource::ProvidedPath + | PythonSource::ParentInterpreter + | PythonSource::ActiveEnvironment + | PythonSource::CondaPrefix => true, + PythonSource::Managed + | PythonSource::DiscoveredEnvironment + | PythonSource::SearchPath + | PythonSource::SearchPathFirst + | PythonSource::Registry + | PythonSource::MicrosoftStore + | PythonSource::BaseCondaPrefix => false, + }; + + match preference { + PythonPreference::OnlyManaged => { + // Perform a fast check using the source before querying the interpreter + if matches!(source, PythonSource::Managed) || interpreter.is_managed() { + true + } else { + if is_explicit { + debug!( + "Allowing unmanaged Python interpreter at `{}` (in conflict with the `python-preference`) since it is from source: {source}", + interpreter.sys_executable().display() + ); + true + } else { + debug!( + "Ignoring Python interpreter at `{}`: only managed interpreters allowed", + interpreter.sys_executable().display() + ); + false + } + } + } + // If not "only" a kind, any interpreter is okay + PythonPreference::Managed | PythonPreference::System => true, + PythonPreference::OnlySystem => { + let is_system = match source { + // A managed interpreter is never a system interpreter + PythonSource::Managed => false, + // We can't be sure if this is a system interpreter without checking + PythonSource::ProvidedPath + | PythonSource::ParentInterpreter + | PythonSource::ActiveEnvironment + | PythonSource::CondaPrefix + | PythonSource::DiscoveredEnvironment + | PythonSource::SearchPath + | PythonSource::SearchPathFirst + | PythonSource::Registry + | PythonSource::BaseCondaPrefix => !interpreter.is_managed(), + // Managed interpreters should never be found in the store + PythonSource::MicrosoftStore => true, + }; + + if is_system { + true + } else { + if is_explicit { + debug!( + "Allowing managed Python interpreter at `{}` (in conflict with the `python-preference`) since it is from source: {source}", + interpreter.sys_executable().display() + ); + true + } else { + debug!( + "Ignoring Python interpreter at `{}`: only system interpreters allowed", + interpreter.sys_executable().display() + ); + false + } + } + } + } +} + /// Check if an encountered error is critical and should stop discovery. /// /// Returns false when an error could be due to a faulty Python installation and we should continue searching for a working one. @@ -2812,6 +2911,18 @@ impl PythonPreference { } } } + + /// Return the canonical name. + // TODO(zanieb): This should be a `Display` impl and we should have a different view for + // the sources + pub fn canonical_name(&self) -> &'static str { + match self { + Self::OnlyManaged => "only managed", + Self::Managed => "prefer managed", + Self::System => "prefer system", + Self::OnlySystem => "only system", + } + } } impl fmt::Display for PythonPreference { diff --git a/crates/uv-python/src/environment.rs b/crates/uv-python/src/environment.rs index 07f3ddb54..10cec16ad 100644 --- a/crates/uv-python/src/environment.rs +++ b/crates/uv-python/src/environment.rs @@ -158,8 +158,7 @@ impl PythonEnvironment { let installation = match find_python_installation( request, preference, - // Ignore managed installations when looking for environments - PythonPreference::OnlySystem, + PythonPreference::default(), cache, preview, )? { diff --git a/crates/uv-python/src/interpreter.rs b/crates/uv-python/src/interpreter.rs index fc5adb833..dd9dd1cb4 100644 --- a/crates/uv-python/src/interpreter.rs +++ b/crates/uv-python/src/interpreter.rs @@ -271,15 +271,28 @@ impl Interpreter { /// /// Returns `false` if we cannot determine the path of the uv managed Python interpreters. pub fn is_managed(&self) -> bool { + if let Ok(test_managed) = + std::env::var(uv_static::EnvVars::UV_INTERNAL__TEST_PYTHON_MANAGED) + { + // During testing, we collect interpreters into an artificial search path and need to + // be able to mock whether an interpreter is managed or not. + return test_managed.split_ascii_whitespace().any(|item| { + let version = ::from_str(item).expect( + "`UV_INTERNAL__TEST_PYTHON_MANAGED` items should be valid Python versions", + ); + if version.patch().is_some() { + version.version() == self.python_version() + } else { + (version.major(), version.minor()) == self.python_tuple() + } + }); + } + let Ok(installations) = ManagedPythonInstallations::from_settings(None) else { return false; }; - installations - .find_all() - .into_iter() - .flatten() - .any(|install| install.path() == self.sys_base_prefix) + self.sys_base_prefix.starts_with(installations.root()) } /// Returns `Some` if the environment is externally managed, optionally including an error diff --git a/crates/uv-python/src/lib.rs b/crates/uv-python/src/lib.rs index ea6f0db61..2461f9006 100644 --- a/crates/uv-python/src/lib.rs +++ b/crates/uv-python/src/lib.rs @@ -8,7 +8,7 @@ use uv_static::EnvVars; pub use crate::discovery::{ EnvironmentPreference, Error as DiscoveryError, PythonDownloads, PythonNotFound, PythonPreference, PythonRequest, PythonSource, PythonVariant, VersionRequest, - find_python_installations, + find_python_installations, satisfies_python_preference, }; pub use crate::downloads::PlatformRequest; pub use crate::environment::{InvalidEnvironmentKind, PythonEnvironment}; diff --git a/crates/uv-static/src/env_vars.rs b/crates/uv-static/src/env_vars.rs index a99808468..f7fa6cb31 100644 --- a/crates/uv-static/src/env_vars.rs +++ b/crates/uv-static/src/env_vars.rs @@ -376,6 +376,14 @@ impl EnvVars { #[attr_hidden] pub const UV_INTERNAL__SHOW_DERIVATION_TREE: &'static str = "UV_INTERNAL__SHOW_DERIVATION_TREE"; + /// Used to set a temporary directory for some tests. + #[attr_hidden] + pub const UV_INTERNAL__TEST_DIR: &'static str = "UV_INTERNAL__TEST_DIR"; + + /// Used to force treating an interpreter as "managed" during tests. + #[attr_hidden] + pub const UV_INTERNAL__TEST_PYTHON_MANAGED: &'static str = "UV_INTERNAL__TEST_PYTHON_MANAGED"; + /// Path to system-level configuration directory on Unix systems. pub const XDG_CONFIG_DIRS: &'static str = "XDG_CONFIG_DIRS"; diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index 23655c1ca..cce02a70b 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -30,8 +30,8 @@ use uv_pep508::MarkerTreeContents; use uv_pypi_types::{ConflictPackage, ConflictSet, Conflicts}; use uv_python::{ EnvironmentPreference, Interpreter, InvalidEnvironmentKind, PythonDownloads, PythonEnvironment, - PythonInstallation, PythonPreference, PythonRequest, PythonVariant, PythonVersionFile, - VersionFileDiscoveryOptions, VersionRequest, + PythonInstallation, PythonPreference, PythonRequest, PythonSource, PythonVariant, + PythonVersionFile, VersionFileDiscoveryOptions, VersionRequest, satisfies_python_preference, }; use uv_requirements::upgrade::{LockedRequirements, read_lock_requirements}; use uv_requirements::{NamedRequirementsResolver, RequirementsSpecification}; @@ -664,6 +664,7 @@ impl ScriptInterpreter { &venv, EnvironmentKind::Script, python_request.as_ref(), + python_preference, requires_python .as_ref() .map(|(requires_python, _)| requires_python), @@ -794,6 +795,9 @@ pub(crate) enum EnvironmentIncompatibilityError { "The interpreter in the {0} environment has a different version ({1}) than it was created with ({2})" )] PyenvVersionConflict(EnvironmentKind, Version, Version), + + #[error("The {0} environment's Python interpreter does not meet the Python preference: `{1}`")] + PythonPreference(EnvironmentKind, PythonPreference), } /// Whether an environment is usable for a project or script, i.e., if it matches the requirements. @@ -801,6 +805,7 @@ fn environment_is_usable( environment: &PythonEnvironment, kind: EnvironmentKind, python_request: Option<&PythonRequest>, + python_preference: PythonPreference, requires_python: Option<&RequiresPython>, cache: &Cache, ) -> Result<(), EnvironmentIncompatibilityError> { @@ -836,6 +841,22 @@ fn environment_is_usable( } } + if satisfies_python_preference( + PythonSource::DiscoveredEnvironment, + environment.interpreter(), + python_preference, + ) { + trace!( + "The virtual environment's Python interpreter meets the Python preference: `{}`", + python_preference + ); + } else { + return Err(EnvironmentIncompatibilityError::PythonPreference( + kind, + python_preference, + )); + } + Ok(()) } @@ -889,6 +910,7 @@ impl ProjectInterpreter { &venv, EnvironmentKind::Project, python_request.as_ref(), + python_preference, requires_python.as_ref(), cache, ) { diff --git a/crates/uv/tests/it/common/mod.rs b/crates/uv/tests/it/common/mod.rs index 9d3c1428f..08eeec3aa 100644 --- a/crates/uv/tests/it/common/mod.rs +++ b/crates/uv/tests/it/common/mod.rs @@ -187,6 +187,18 @@ impl TestContext { "virtual environments, managed installations, search path, or registry".to_string(), "[PYTHON SOURCES]".to_string(), )); + self.filters.push(( + "virtual environments, search path, or registry".to_string(), + "[PYTHON SOURCES]".to_string(), + )); + self.filters.push(( + "virtual environments, registry, or search path".to_string(), + "[PYTHON SOURCES]".to_string(), + )); + self.filters.push(( + "virtual environments or search path".to_string(), + "[PYTHON SOURCES]".to_string(), + )); self.filters.push(( "managed installations or search path".to_string(), "[PYTHON SOURCES]".to_string(), @@ -415,6 +427,15 @@ impl TestContext { self } + pub fn with_versions_as_managed(mut self, versions: &[&str]) -> Self { + self.extra_env.push(( + EnvVars::UV_INTERNAL__TEST_PYTHON_MANAGED.into(), + versions.iter().join(" ").into(), + )); + + self + } + /// Clear filters on `TestContext`. pub fn clear_filters(mut self) -> Self { self.filters.clear(); diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index 9cd394bbd..2a7b0f404 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -11684,3 +11684,58 @@ fn strip_shebang_arguments() -> Result<()> { Ok(()) } + +#[test] +fn install_python_preference() { + let context = + TestContext::new_with_versions(&["3.12", "3.11"]).with_versions_as_managed(&["3.12"]); + + // Create a managed interpreter environment + uv_snapshot!(context.filters(), context.venv(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + Creating virtual environment at: .venv + Activate with: source .venv/[BIN]/activate + "); + + // Install a package, requesting managed Python + uv_snapshot!(context.filters(), context.pip_install().arg("anyio").arg("--managed-python"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 3 packages in [TIME] + Prepared 3 packages in [TIME] + Installed 3 packages in [TIME] + + anyio==4.3.0 + + idna==3.6 + + sniffio==1.3.1 + "); + + // Install a package, requesting unmanaged Python + // This is allowed, because the virtual environment already exists + uv_snapshot!(context.filters(), context.pip_install().arg("anyio").arg("--no-managed-python"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Audited 1 package in [TIME] + "); + + // This also works with `VIRTUAL_ENV` unset + uv_snapshot!(context.filters(), context.pip_install() + .arg("anyio").arg("--no-managed-python").env_remove("VIRTUAL_ENV"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Audited 1 package in [TIME] + "); +} diff --git a/crates/uv/tests/it/python_find.rs b/crates/uv/tests/it/python_find.rs index 49e60c068..41eceeb92 100644 --- a/crates/uv/tests/it/python_find.rs +++ b/crates/uv/tests/it/python_find.rs @@ -728,6 +728,57 @@ fn python_find_venv_invalid() { "###); } +#[test] +fn python_find_managed() { + let context: TestContext = TestContext::new_with_versions(&["3.11", "3.12"]) + .with_filtered_python_sources() + .with_versions_as_managed(&["3.12"]); + + // We find the managed interpreter + uv_snapshot!(context.filters(), context.python_find().arg("--managed-python"), @r" + success: true + exit_code: 0 + ----- stdout ----- + [PYTHON-3.12] + + ----- stderr ----- + "); + + // Request an interpreter that cannot be satisfied + uv_snapshot!(context.filters(), context.python_find().arg("--managed-python").arg("3.11"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: No interpreter found for Python 3.11 in virtual environments or managed installations + "); + + let context: TestContext = TestContext::new_with_versions(&["3.11", "3.12"]) + .with_filtered_python_sources() + .with_versions_as_managed(&["3.11"]); + + // We find the unmanaged interpreter + uv_snapshot!(context.filters(), context.python_find().arg("--no-managed-python"), @r" + success: true + exit_code: 0 + ----- stdout ----- + [PYTHON-3.12] + + ----- stderr ----- + "); + + // Request an interpreter that cannot be satisfied + uv_snapshot!(context.filters(), context.python_find().arg("--no-managed-python").arg("3.11"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: No interpreter found for Python 3.11 in [PYTHON SOURCES] + "); +} + /// See: /// /// This test will not succeed on macOS if using a Homebrew provided interpreter. The interpreter diff --git a/crates/uv/tests/it/run.rs b/crates/uv/tests/it/run.rs index 6a1eb6093..ad8672788 100644 --- a/crates/uv/tests/it/run.rs +++ b/crates/uv/tests/it/run.rs @@ -5500,3 +5500,49 @@ fn run_no_sync_incompatible_python() -> Result<()> { Ok(()) } + +#[test] +fn run_python_preference_no_project() { + let context = + TestContext::new_with_versions(&["3.12", "3.11"]).with_versions_as_managed(&["3.12"]); + + context.venv().assert().success(); + + uv_snapshot!(context.filters(), context.run().arg("python").arg("--version"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.12.[X] + + ----- stderr ----- + "); + + uv_snapshot!(context.filters(), context.run().arg("--managed-python").arg("python").arg("--version"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.12.[X] + + ----- stderr ----- + "); + + // `VIRTUAL_ENV` is set here, so we'll ignore the flag + uv_snapshot!(context.filters(), context.run().arg("--no-managed-python").arg("python").arg("--version"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.12.[X] + + ----- stderr ----- + "); + + // If we remove the `VIRTUAL_ENV` variable, we should get the unmanaged Python + uv_snapshot!(context.filters(), context.run().arg("--no-managed-python").arg("python").arg("--version").env_remove("VIRTUAL_ENV"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.11.[X] + + ----- stderr ----- + "); +} diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index 35a06ea57..3544f1961 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -10804,3 +10804,144 @@ fn undeclared_editable() -> Result<()> { Ok(()) } + +#[test] +fn sync_python_preference() -> Result<()> { + let context = TestContext::new_with_versions(&["3.12", "3.11"]); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.11" + dependencies = [] + "#, + )?; + + // Run an initial sync, with 3.12 as an "unmanaged" interpreter + context.sync().assert().success(); + + // Mark 3.12 as a managed interpreter for the rest of the tests + let context = context.with_versions_as_managed(&["3.12"]); + uv_snapshot!(context.filters(), context.sync(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Audited in [TIME] + "); + + // We should invalidate the environment and switch to 3.11 + uv_snapshot!(context.filters(), context.sync().arg("--no-managed-python"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.11.[X] interpreter at: [PYTHON-3.11] + Removed virtual environment at: .venv + Creating virtual environment at: .venv + Resolved 1 package in [TIME] + Audited in [TIME] + "); + + // We will use the environment if it exists + uv_snapshot!(context.filters(), context.sync(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Audited in [TIME] + "); + + // Unless the user requests a Python preference that is incompatible + uv_snapshot!(context.filters(), context.sync().arg("--managed-python"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + Removed virtual environment at: .venv + Creating virtual environment at: .venv + Resolved 1 package in [TIME] + Audited in [TIME] + "); + + // If a interpreter cannot be found, we'll fail + uv_snapshot!(context.filters(), context.sync().arg("--managed-python").arg("-p").arg("3.11"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: No interpreter found for Python 3.11 in managed installations + + hint: A managed Python download is available for Python 3.11, but Python downloads are set to 'never' + "); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.11" + dependencies = [] + + [tool.uv] + python-preference = "only-system" + "#, + )?; + + // We'll respect a `python-preference` in the `pyproject.toml` file + uv_snapshot!(context.filters(), context.sync(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.11.[X] interpreter at: [PYTHON-3.11] + Removed virtual environment at: .venv + Creating virtual environment at: .venv + Resolved 1 package in [TIME] + Audited in [TIME] + "); + + // But it can be overridden via the CLI + uv_snapshot!(context.filters(), context.sync().arg("--managed-python"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + Removed virtual environment at: .venv + Creating virtual environment at: .venv + Resolved 1 package in [TIME] + Audited in [TIME] + "); + + // `uv run` will invalidate the environment too + uv_snapshot!(context.filters(), context.run().arg("python").arg("--version"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.11.[X] + + ----- stderr ----- + Using CPython 3.11.[X] interpreter at: [PYTHON-3.11] + Removed virtual environment at: .venv + Creating virtual environment at: .venv + Resolved 1 package in [TIME] + Audited in [TIME] + "); + + Ok(()) +} diff --git a/crates/uv/tests/it/venv.rs b/crates/uv/tests/it/venv.rs index 2430e607d..120d7def2 100644 --- a/crates/uv/tests/it/venv.rs +++ b/crates/uv/tests/it/venv.rs @@ -1322,3 +1322,69 @@ fn create_venv_apostrophe() { let stdout = String::from_utf8_lossy(&output.stdout); assert_eq!(stdout.trim(), venv_dir.to_string_lossy()); } + +#[test] +fn venv_python_preference() { + let context = + TestContext::new_with_versions(&["3.12", "3.11"]).with_versions_as_managed(&["3.12"]); + + // Create a managed interpreter environment + uv_snapshot!(context.filters(), context.venv(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + Creating virtual environment at: .venv + Activate with: source .venv/[BIN]/activate + "); + + uv_snapshot!(context.filters(), context.venv().arg("--no-managed-python"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.11.[X] interpreter at: [PYTHON-3.11] + Creating virtual environment at: .venv + warning: A virtual environment already exists at `.venv`. In the future, uv will require `--clear` to replace it + Activate with: source .venv/[BIN]/activate + "); + + uv_snapshot!(context.filters(), context.venv().arg("--no-managed-python"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.11.[X] interpreter at: [PYTHON-3.11] + Creating virtual environment at: .venv + warning: A virtual environment already exists at `.venv`. In the future, uv will require `--clear` to replace it + Activate with: source .venv/[BIN]/activate + "); + + uv_snapshot!(context.filters(), context.venv(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + Creating virtual environment at: .venv + warning: A virtual environment already exists at `.venv`. In the future, uv will require `--clear` to replace it + Activate with: source .venv/[BIN]/activate + "); + + uv_snapshot!(context.filters(), context.venv().arg("--managed-python"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + Creating virtual environment at: .venv + warning: A virtual environment already exists at `.venv`. In the future, uv will require `--clear` to replace it + Activate with: source .venv/[BIN]/activate + "); +} From ff30f14d50cfdda544397f910808eeec8b20f11b Mon Sep 17 00:00:00 2001 From: John Mumm Date: Wed, 16 Jul 2025 23:17:01 +0200 Subject: [PATCH 253/349] Build `path` sources without build systems by default (#14413) We currently treat path sources as virtual if they do not specify a build system, which is surprising behavior. This PR updates the behavior to treat path sources as packages unless the path source is explicitly marked as `package = false` or its own `tool.uv.package` is set to `false`. Closes #12015 --------- Co-authored-by: Zanie Blue --- .../uv-distribution/src/metadata/lowering.rs | 4 +- crates/uv-workspace/src/pyproject.rs | 15 ++-- crates/uv/tests/it/edit.rs | 8 +- crates/uv/tests/it/lock.rs | 33 +++---- crates/uv/tests/it/sync.rs | 85 +++++++++++++++++++ docs/concepts/projects/config.md | 5 +- docs/concepts/projects/dependencies.md | 70 ++++++++++----- 7 files changed, 172 insertions(+), 48 deletions(-) diff --git a/crates/uv-distribution/src/metadata/lowering.rs b/crates/uv-distribution/src/metadata/lowering.rs index 54782c083..c05ac4779 100644 --- a/crates/uv-distribution/src/metadata/lowering.rs +++ b/crates/uv-distribution/src/metadata/lowering.rs @@ -729,12 +729,14 @@ fn path_source( }) } else { // Determine whether the project is a package or virtual. + // If the `package` option is unset, check if `tool.uv.package` is set + // on the path source (otherwise, default to `true`). let is_package = package.unwrap_or_else(|| { let pyproject_path = install_path.join("pyproject.toml"); fs_err::read_to_string(&pyproject_path) .ok() .and_then(|contents| PyProjectToml::from_string(contents).ok()) - .map(|pyproject_toml| pyproject_toml.is_package()) + .and_then(|pyproject_toml| pyproject_toml.tool_uv_package()) .unwrap_or(true) }); diff --git a/crates/uv-workspace/src/pyproject.rs b/crates/uv-workspace/src/pyproject.rs index 124a62881..aa64c601e 100644 --- a/crates/uv-workspace/src/pyproject.rs +++ b/crates/uv-workspace/src/pyproject.rs @@ -83,12 +83,7 @@ impl PyProjectToml { /// non-package ("virtual") project. pub fn is_package(&self) -> bool { // If `tool.uv.package` is set, defer to that explicit setting. - if let Some(is_package) = self - .tool - .as_ref() - .and_then(|tool| tool.uv.as_ref()) - .and_then(|uv| uv.package) - { + if let Some(is_package) = self.tool_uv_package() { return is_package; } @@ -96,6 +91,14 @@ impl PyProjectToml { self.build_system.is_some() } + /// Returns the value of `tool.uv.package` if set. + pub fn tool_uv_package(&self) -> Option { + self.tool + .as_ref() + .and_then(|tool| tool.uv.as_ref()) + .and_then(|uv| uv.package) + } + /// Returns `true` if the project uses a dynamic version. pub fn is_dynamic(&self) -> bool { self.project diff --git a/crates/uv/tests/it/edit.rs b/crates/uv/tests/it/edit.rs index ccc0cabf2..70b8d6e50 100644 --- a/crates/uv/tests/it/edit.rs +++ b/crates/uv/tests/it/edit.rs @@ -13381,7 +13381,9 @@ fn add_path_with_no_workspace() -> Result<()> { ----- stderr ----- Resolved 2 packages in [TIME] - Audited in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + dep==0.1.0 (from file://[TEMP_DIR]/dep) "); let pyproject_toml = context.read("pyproject.toml"); @@ -13452,7 +13454,9 @@ fn add_path_outside_workspace_no_default() -> Result<()> { Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] Creating virtual environment at: .venv Resolved 2 packages in [TIME] - Audited in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + dep==0.1.0 (from file://[TEMP_DIR]/external_dep) "); let pyproject_toml = fs_err::read_to_string(workspace_toml)?; diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index faf37a83a..75d81b4c0 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -7205,12 +7205,12 @@ fn lock_exclusion() -> Result<()> { ] [package.metadata] - requires-dist = [{ name = "project", virtual = "../" }] + requires-dist = [{ name = "project", directory = "../" }] [[package]] name = "project" version = "0.1.0" - source = { virtual = "../" } + source = { directory = "../" } "# ); }); @@ -7793,7 +7793,7 @@ fn lock_dev_transitive() -> Result<()> { [package.metadata] requires-dist = [ { name = "baz", editable = "baz" }, - { name = "foo", virtual = "../foo" }, + { name = "foo", directory = "../foo" }, { name = "iniconfig", specifier = ">1" }, ] @@ -7815,7 +7815,7 @@ fn lock_dev_transitive() -> Result<()> { [[package]] name = "foo" version = "0.1.0" - source = { virtual = "../foo" } + source = { directory = "../foo" } [package.metadata] @@ -13651,7 +13651,7 @@ fn lock_narrowed_python_version_upper() -> Result<()> { [[package]] name = "dependency" version = "0.1.0" - source = { virtual = "dependency" } + source = { directory = "dependency" } dependencies = [ { name = "iniconfig", marker = "python_full_version >= '3.10'" }, ] @@ -13677,7 +13677,7 @@ fn lock_narrowed_python_version_upper() -> Result<()> { ] [package.metadata] - requires-dist = [{ name = "dependency", marker = "python_full_version >= '3.10'", virtual = "dependency" }] + requires-dist = [{ name = "dependency", marker = "python_full_version >= '3.10'", directory = "dependency" }] "# ); }); @@ -17173,10 +17173,10 @@ fn lock_implicit_virtual_project() -> Result<()> { Ok(()) } -/// Lock a project that has a path dependency that is implicitly virtual (by way of omitting -/// `build-system`). +/// Lock a project that has a path dependency that is implicitly non-virtual (despite +/// omitting `build-system`). #[test] -fn lock_implicit_virtual_path() -> Result<()> { +fn lock_implicit_package_path() -> Result<()> { let context = TestContext::new("3.12"); let pyproject_toml = context.temp_dir.child("pyproject.toml"); @@ -17243,7 +17243,7 @@ fn lock_implicit_virtual_path() -> Result<()> { [[package]] name = "child" version = "0.1.0" - source = { virtual = "child" } + source = { directory = "child" } dependencies = [ { name = "iniconfig" }, ] @@ -17281,7 +17281,7 @@ fn lock_implicit_virtual_path() -> Result<()> { [package.metadata] requires-dist = [ { name = "anyio", specifier = ">3" }, - { name = "child", virtual = "child" }, + { name = "child", directory = "child" }, ] [[package]] @@ -17317,20 +17317,21 @@ fn lock_implicit_virtual_path() -> Result<()> { Resolved 6 packages in [TIME] "###); - // Install from the lockfile. The virtual project should _not_ be installed. - uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###" + // Install from the lockfile. The path dependency should be installed. + uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - Prepared 4 packages in [TIME] - Installed 4 packages in [TIME] + Prepared 5 packages in [TIME] + Installed 5 packages in [TIME] + anyio==4.3.0 + + child==0.1.0 (from file://[TEMP_DIR]/child) + idna==3.6 + iniconfig==2.0.0 + sniffio==1.3.1 - "###); + "); Ok(()) } diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index 3544f1961..bb3546e22 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -5939,6 +5939,91 @@ fn sync_override_package() -> Result<()> { ~ project==0.0.0 (from file://[TEMP_DIR]/) "); + // Update the source `tool.uv` to `package = true` + let pyproject_toml = context.temp_dir.child("core").child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "core" + version = "0.1.0" + requires-python = ">=3.12" + + [build-system] + requires = ["hatchling"] + build-backend = "hatchling.build" + + [tool.uv] + package = true + "#, + )?; + + // Mark the source as `package = false`. + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.0.0" + requires-python = ">=3.12" + dependencies = ["core"] + + [build-system] + requires = ["hatchling"] + build-backend = "hatchling.build" + + [tool.uv.sources] + core = { path = "./core", package = false } + "#, + )?; + + // Syncing the project should _not_ install `core`. + uv_snapshot!(context.filters(), context.sync(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + Prepared 1 package in [TIME] + Uninstalled 1 package in [TIME] + Installed 1 package in [TIME] + ~ project==0.0.0 (from file://[TEMP_DIR]/) + "); + + // Remove the `package = false` mark. + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.0.0" + requires-python = ">=3.12" + dependencies = ["core"] + + [build-system] + requires = ["hatchling"] + build-backend = "hatchling.build" + + [tool.uv.sources] + core = { path = "./core" } + "#, + )?; + + // Syncing the project _should_ install `core`. + uv_snapshot!(context.filters(), context.sync(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + Prepared 2 packages in [TIME] + Uninstalled 1 package in [TIME] + Installed 2 packages in [TIME] + + core==0.1.0 (from file://[TEMP_DIR]/core) + ~ project==0.0.0 (from file://[TEMP_DIR]/) + "); + Ok(()) } diff --git a/docs/concepts/projects/config.md b/docs/concepts/projects/config.md index 8efb667a1..34b62c01a 100644 --- a/docs/concepts/projects/config.md +++ b/docs/concepts/projects/config.md @@ -116,8 +116,9 @@ with the default build system. the presence of a `[build-system]` table is not required in other packages. For legacy reasons, if a build system is not defined, then `setuptools.build_meta:__legacy__` is used to build the package. Packages you depend on may not explicitly declare their build system but are still - installable. Similarly, if you add a dependency on a local package or install it with `uv pip`, - uv will always attempt to build and install it. + installable. Similarly, if you [add a dependency on a local project](./dependencies.md#path) + or install it with `uv pip`, uv will attempt to build and install it regardless of the presence + of a `[build-system]` table. ### Build system options diff --git a/docs/concepts/projects/dependencies.md b/docs/concepts/projects/dependencies.md index 022db4d7e..bf11e7174 100644 --- a/docs/concepts/projects/dependencies.md +++ b/docs/concepts/projects/dependencies.md @@ -410,33 +410,28 @@ $ uv add ~/projects/bar/ !!! important - An [editable installation](#editable-dependencies) is not used for path dependencies by - default. An editable installation may be requested for project directories: + When using a directory as a path dependency, uv will attempt to build and install the target as + a package by default. See the [virtual dependency](#virtual-dependencies) documentation for + details. - ```console - $ uv add --editable ../projects/bar/ - ``` +An [editable installation](#editable-dependencies) is not used for path dependencies by default. An +editable installation may be requested for project directories: - Which will result in a `pyproject.toml` with: +```console +$ uv add --editable ../projects/bar/ +``` - ```toml title="pyproject.toml" - [project] - dependencies = ["bar"] +Which will result in a `pyproject.toml` with: - [tool.uv.sources] - bar = { path = "../projects/bar", editable = true } - ``` +```toml title="pyproject.toml" +[project] +dependencies = ["bar"] - Similarly, if a project is marked as a [non-package](./config.md#build-systems), but you'd - like to install it in the environment as a package, set `package = true` on the source: +[tool.uv.sources] +bar = { path = "../projects/bar", editable = true } +``` - ```toml title="pyproject.toml" - [project] - dependencies = ["bar"] - - [tool.uv.sources] - bar = { path = "../projects/bar", package = true } - ``` +!!! tip For multiple packages in the same repository, [_workspaces_](./workspaces.md) may be a better fit. @@ -808,6 +803,39 @@ Or, to opt-out of using an editable dependency in a workspace: $ uv add --no-editable ./path/foo ``` +## Virtual dependencies + +uv allows dependencies to be "virtual", in which the dependency itself is not installed as a +[package](./config.md#project-packaging), but its dependencies are. + +By default, only workspace members without build systems declared are virtual. + +A dependency with a [`path` source](#path) is not virtual unless it explicitly sets +[`tool.uv.package = false`](../../reference/settings.md#package). Unlike working _in_ the dependent +project with uv, the package will be built even if a [build system](./config.md#build-systems) is +not declared. + +To treat a dependency as virtual, set `package = false` on the source: + +```toml title="pyproject.toml" +[project] +dependencies = ["bar"] + +[tool.uv.sources] +bar = { path = "../projects/bar", package = false } +``` + +Similarly, if a dependency sets `tool.uv.package = false`, it can be overridden by declaring +`package = true` on the source: + +```toml title="pyproject.toml" +[project] +dependencies = ["bar"] + +[tool.uv.sources] +bar = { path = "../projects/bar", package = true } +``` + ## Dependency specifiers uv uses standard From 0077f2357f4e016c871b2b651ca59a139a95f19a Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 17 Jul 2025 11:09:13 -0500 Subject: [PATCH 254/349] Stabilize addition of Python executables to the bin (#14626) Closes https://github.com/astral-sh/uv/issues/14296 As mentioned in #14681, this does not stabilize the `--default` behavior. --- crates/uv-cli/src/lib.rs | 7 +- crates/uv/src/commands/python/install.rs | 62 ++- crates/uv/tests/it/common/mod.rs | 25 +- crates/uv/tests/it/help.rs | 7 +- crates/uv/tests/it/python_install.rs | 555 +++++++++++++++++++++-- docs/concepts/python-versions.md | 25 +- docs/guides/install-python.md | 23 +- docs/reference/cli.md | 2 +- 8 files changed, 593 insertions(+), 113 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 5df818654..9d7cfa6e0 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -4810,10 +4810,9 @@ pub enum PythonCommand { /// Python versions are installed into the uv Python directory, which can be retrieved with `uv /// python dir`. /// - /// A `python` executable is not made globally available, managed Python versions are only used - /// in uv commands or in active virtual environments. There is experimental support for adding - /// Python executables to a directory on the path — use the `--preview` flag to enable this - /// behavior and `uv python dir --bin` to retrieve the target directory. + /// By default, Python executables are added to a directory on the path with a minor version + /// suffix, e.g., `python3.13`. To install `python3` and `python`, use the `--default` flag. Use + /// `uv python dir --bin` to see the target directory. /// /// Multiple Python versions may be requested. /// diff --git a/crates/uv/src/commands/python/install.rs b/crates/uv/src/commands/python/install.rs index b9d4660df..37d6a6777 100644 --- a/crates/uv/src/commands/python/install.rs +++ b/crates/uv/src/commands/python/install.rs @@ -166,12 +166,14 @@ pub(crate) async fn install( ) -> Result { let start = std::time::Instant::now(); + // TODO(zanieb): We should consider marking the Python installation as the default when + // `--default` is used. It's not clear how this overlaps with a global Python pin, but I'd be + // surprised if `uv python find` returned the "newest" Python version rather than the one I just + // installed with the `--default` flag. if default && !preview.is_enabled() { - writeln!( - printer.stderr(), - "The `--default` flag is only available in preview mode; add the `--preview` flag to use `--default`" - )?; - return Ok(ExitStatus::Failure); + warn_user!( + "The `--default` option is experimental and may change without warning. Pass `--preview` to disable this warning" + ); } if upgrade && preview.is_disabled() { @@ -222,6 +224,8 @@ pub(crate) async fn install( .map(PythonVersionFile::into_versions) .unwrap_or_else(|| { // If no version file is found and no requests were made + // TODO(zanieb): We should consider differentiating between a global Python version + // file here, allowing a request from there to enable `is_default_install`. is_default_install = true; vec![if reinstall { // On bare `--reinstall`, reinstall all Python versions @@ -451,10 +455,10 @@ pub(crate) async fn install( } } - let bin_dir = if matches!(bin, Some(true)) || preview.is_enabled() { - Some(python_executable_dir()?) - } else { + let bin_dir = if matches!(bin, Some(false)) { None + } else { + Some(python_executable_dir()?) }; let installations: Vec<_> = downloaded.iter().chain(satisfied.iter().copied()).collect(); @@ -469,20 +473,10 @@ pub(crate) async fn install( e.warn_user(installation); } - if preview.is_disabled() { - debug!("Skipping installation of Python executables, use `--preview` to enable."); - continue; - } - - let bin_dir = bin_dir - .as_ref() - .expect("We should have a bin directory with preview enabled") - .as_path(); - let upgradeable = (default || is_default_install) || requested_minor_versions.contains(&installation.key().version().python_version()); - if !matches!(bin, Some(false)) { + if let Some(bin_dir) = bin_dir.as_ref() { create_bin_links( installation, bin_dir, @@ -661,11 +655,7 @@ pub(crate) async fn install( } } - if preview.is_enabled() && !matches!(bin, Some(false)) { - let bin_dir = bin_dir - .as_ref() - .expect("We should have a bin directory with preview enabled") - .as_path(); + if let Some(bin_dir) = bin_dir.as_ref() { warn_if_not_on_path(bin_dir); } } @@ -749,16 +739,20 @@ fn create_bin_links( errors: &mut Vec<(InstallErrorKind, PythonInstallationKey, Error)>, preview: PreviewMode, ) { - let targets = - if (default || is_default_install) && first_request.matches_installation(installation) { - vec![ - installation.key().executable_name_minor(), - installation.key().executable_name_major(), - installation.key().executable_name(), - ] - } else { - vec![installation.key().executable_name_minor()] - }; + // TODO(zanieb): We want more feedback on the `is_default_install` behavior before stabilizing + // it. In particular, it may be confusing because it does not apply when versions are loaded + // from a `.python-version` file. + let targets = if (default || (is_default_install && preview.is_enabled())) + && first_request.matches_installation(installation) + { + vec![ + installation.key().executable_name_minor(), + installation.key().executable_name_major(), + installation.key().executable_name(), + ] + } else { + vec![installation.key().executable_name_minor()] + }; for target in targets { let target = bin.join(target); diff --git a/crates/uv/tests/it/common/mod.rs b/crates/uv/tests/it/common/mod.rs index 08eeec3aa..ab4c38247 100644 --- a/crates/uv/tests/it/common/mod.rs +++ b/crates/uv/tests/it/common/mod.rs @@ -220,17 +220,30 @@ impl TestContext { /// and `.exe` suffixes. #[must_use] pub fn with_filtered_python_names(mut self) -> Self { + use env::consts::EXE_SUFFIX; + let exe_suffix = regex::escape(EXE_SUFFIX); + + self.filters.push(( + format!(r"python\d.\d\d{exe_suffix}"), + "[PYTHON]".to_string(), + )); + self.filters + .push((format!(r"python\d{exe_suffix}"), "[PYTHON]".to_string())); + if cfg!(windows) { + // On Windows, we want to filter out all `python.exe` instances self.filters - .push((r"python\.exe".to_string(), "[PYTHON]".to_string())); + .push((format!(r"python{exe_suffix}"), "[PYTHON]".to_string())); + // Including ones where we'd already stripped the `.exe` in another filter + self.filters + .push((r"[\\/]python".to_string(), "/[PYTHON]".to_string())); } else { + // On Unix, it's a little trickier — we don't want to clobber use of `python` in the + // middle of something else, e.g., `cpython`. For this reason, we require a leading `/`. self.filters - .push((r"python\d.\d\d".to_string(), "[PYTHON]".to_string())); - self.filters - .push((r"python\d".to_string(), "[PYTHON]".to_string())); - self.filters - .push((r"/python".to_string(), "/[PYTHON]".to_string())); + .push((format!(r"/python{exe_suffix}"), "/[PYTHON]".to_string())); } + self } diff --git a/crates/uv/tests/it/help.rs b/crates/uv/tests/it/help.rs index d9353f7c3..d4f46b0cb 100644 --- a/crates/uv/tests/it/help.rs +++ b/crates/uv/tests/it/help.rs @@ -469,10 +469,9 @@ fn help_subsubcommand() { Python versions are installed into the uv Python directory, which can be retrieved with `uv python dir`. - A `python` executable is not made globally available, managed Python versions are only used in uv - commands or in active virtual environments. There is experimental support for adding Python - executables to a directory on the path — use the `--preview` flag to enable this behavior and `uv - python dir --bin` to retrieve the target directory. + By default, Python executables are added to a directory on the path with a minor version suffix, + e.g., `python3.13`. To install `python3` and `python`, use the `--default` flag. Use `uv python dir + --bin` to see the target directory. Multiple Python versions may be requested. diff --git a/crates/uv/tests/it/python_install.rs b/crates/uv/tests/it/python_install.rs index 50b0b3cf5..51e394aad 100644 --- a/crates/uv/tests/it/python_install.rs +++ b/crates/uv/tests/it/python_install.rs @@ -30,15 +30,49 @@ fn python_install() { ----- stderr ----- Installed Python 3.13.5 in [TIME] - + cpython-3.13.5-[PLATFORM] + + cpython-3.13.5-[PLATFORM] (python3.13) "); let bin_python = context .bin_dir .child(format!("python3.13{}", std::env::consts::EXE_SUFFIX)); - // The executable should not be installed in the bin directory (requires preview) - bin_python.assert(predicate::path::missing()); + // The executable should be installed in the bin directory + bin_python.assert(predicate::path::exists()); + + // On Unix, it should be a link + #[cfg(unix)] + bin_python.assert(predicate::path::is_symlink()); + + // The link should be a path to the binary + if cfg!(unix) { + insta::with_settings!({ + filters => context.filters(), + }, { + insta::assert_snapshot!( + read_link(&bin_python), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/bin/python3.13" + ); + }); + } else if cfg!(windows) { + insta::with_settings!({ + filters => context.filters(), + }, { + insta::assert_snapshot!( + read_link(&bin_python), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/python" + ); + }); + } + + // The executable should "work" + uv_snapshot!(context.filters(), Command::new(bin_python.as_os_str()) + .arg("-c").arg("import subprocess; print('hello world')"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + hello world + + ----- stderr ----- + "###); // Should be a no-op when already installed uv_snapshot!(context.filters(), context.python_install(), @r###" @@ -67,9 +101,12 @@ fn python_install() { ----- stderr ----- Installed Python 3.13.5 in [TIME] - ~ cpython-3.13.5-[PLATFORM] + ~ cpython-3.13.5-[PLATFORM] (python3.13) "); + // The executable should still be present in the bin directory + bin_python.assert(predicate::path::exists()); + // Uninstallation requires an argument uv_snapshot!(context.filters(), context.python_uninstall(), @r###" success: false @@ -93,8 +130,11 @@ fn python_install() { ----- stderr ----- Searching for Python versions matching: Python 3.13 Uninstalled Python 3.13.5 in [TIME] - - cpython-3.13.5-[PLATFORM] + - cpython-3.13.5-[PLATFORM] (python3.13) "); + + // The executable should be removed + bin_python.assert(predicate::path::missing()); } #[test] @@ -112,8 +152,8 @@ fn python_reinstall() { ----- stderr ----- Installed 2 versions in [TIME] - + cpython-3.12.11-[PLATFORM] - + cpython-3.13.5-[PLATFORM] + + cpython-3.12.11-[PLATFORM] (python3.12) + + cpython-3.13.5-[PLATFORM] (python3.13) "); // Reinstall a single version @@ -124,7 +164,7 @@ fn python_reinstall() { ----- stderr ----- Installed Python 3.13.5 in [TIME] - ~ cpython-3.13.5-[PLATFORM] + ~ cpython-3.13.5-[PLATFORM] (python3.13) "); // Reinstall multiple versions @@ -135,8 +175,8 @@ fn python_reinstall() { ----- stderr ----- Installed 2 versions in [TIME] - ~ cpython-3.12.11-[PLATFORM] - ~ cpython-3.13.5-[PLATFORM] + ~ cpython-3.12.11-[PLATFORM] (python3.12) + ~ cpython-3.13.5-[PLATFORM] (python3.13) "); // Reinstalling a version that is not installed should also work @@ -147,7 +187,7 @@ fn python_reinstall() { ----- stderr ----- Installed Python 3.11.13 in [TIME] - + cpython-3.11.13-[PLATFORM] + + cpython-3.11.13-[PLATFORM] (python3.11) "); } @@ -167,7 +207,7 @@ fn python_reinstall_patch() { ----- stderr ----- Installed 2 versions in [TIME] + cpython-3.12.6-[PLATFORM] - + cpython-3.12.7-[PLATFORM] + + cpython-3.12.7-[PLATFORM] (python3.12) "); // Reinstall all "3.12" versions @@ -180,7 +220,7 @@ fn python_reinstall_patch() { ----- stderr ----- Installed Python 3.12.11 in [TIME] - + cpython-3.12.11-[PLATFORM] + + cpython-3.12.11-[PLATFORM] (python3.12) "); } @@ -328,6 +368,208 @@ fn regression_cpython() { "###); } +#[test] +fn python_install_force() { + let context: TestContext = TestContext::new_with_versions(&[]) + .with_filtered_python_keys() + .with_filtered_exe_suffix() + .with_managed_python_dirs(); + + // Install the latest version + uv_snapshot!(context.filters(), context.python_install(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.13.5 in [TIME] + + cpython-3.13.5-[PLATFORM] (python3.13) + "); + + let bin_python = context + .bin_dir + .child(format!("python3.13{}", std::env::consts::EXE_SUFFIX)); + + // You can force replacement of the executables + uv_snapshot!(context.filters(), context.python_install().arg("--force"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.13.5 in [TIME] + + cpython-3.13.5-[PLATFORM] (python3.13) + "); + + // The executable should still be present in the bin directory + bin_python.assert(predicate::path::exists()); + + // If an unmanaged executable is present, `--force` is required + fs_err::remove_file(bin_python.path()).unwrap(); + bin_python.touch().unwrap(); + + uv_snapshot!(context.filters(), context.python_install().arg("3.13"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: Failed to install executable for cpython-3.13.5-[PLATFORM] + Caused by: Executable already exists at `[BIN]/python3.13` but is not managed by uv; use `--force` to replace it + "); + + uv_snapshot!(context.filters(), context.python_install().arg("--force").arg("3.13"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.13.5 in [TIME] + + cpython-3.13.5-[PLATFORM] (python3.13) + "); + + bin_python.assert(predicate::path::exists()); +} + +#[test] +fn python_install_minor() { + let context: TestContext = TestContext::new_with_versions(&[]) + .with_filtered_python_keys() + .with_filtered_exe_suffix() + .with_managed_python_dirs(); + + // Install a minor version + uv_snapshot!(context.filters(), context.python_install().arg("3.11"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.11.13 in [TIME] + + cpython-3.11.13-[PLATFORM] (python3.11) + "); + + let bin_python = context + .bin_dir + .child(format!("python3.11{}", std::env::consts::EXE_SUFFIX)); + + // The executable should be installed in the bin directory + bin_python.assert(predicate::path::exists()); + + // It should be a link to the minor version + if cfg!(unix) { + insta::with_settings!({ + filters => context.filters(), + }, { + insta::assert_snapshot!( + read_link(&bin_python), @"[TEMP_DIR]/managed/cpython-3.11.13-[PLATFORM]/bin/python3.11" + ); + }); + } else if cfg!(windows) { + insta::with_settings!({ + filters => context.filters(), + }, { + insta::assert_snapshot!( + read_link(&bin_python), @"[TEMP_DIR]/managed/cpython-3.11.13-[PLATFORM]/python" + ); + }); + } + + uv_snapshot!(context.filters(), context.python_uninstall().arg("3.11"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Searching for Python versions matching: Python 3.11 + Uninstalled Python 3.11.13 in [TIME] + - cpython-3.11.13-[PLATFORM] (python3.11) + "); + + // The executable should be removed + bin_python.assert(predicate::path::missing()); +} + +#[test] +fn python_install_multiple_patch() { + let context: TestContext = TestContext::new_with_versions(&[]) + .with_filtered_python_keys() + .with_filtered_exe_suffix() + .with_managed_python_dirs(); + + // Install multiple patch versions + uv_snapshot!(context.filters(), context.python_install().arg("3.12.8").arg("3.12.6"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed 2 versions in [TIME] + + cpython-3.12.6-[PLATFORM] + + cpython-3.12.8-[PLATFORM] (python3.12) + "); + + let bin_python = context + .bin_dir + .child(format!("python3.12{}", std::env::consts::EXE_SUFFIX)); + + // The executable should be installed in the bin directory + bin_python.assert(predicate::path::exists()); + + // The link should resolve to the newer patch version + if cfg!(unix) { + insta::with_settings!({ + filters => context.filters(), + }, { + insta::assert_snapshot!( + canonicalize_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.8-[PLATFORM]/bin/python3.12" + ); + }); + } else if cfg!(windows) { + insta::with_settings!({ + filters => context.filters(), + }, { + insta::assert_snapshot!( + canonicalize_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.8-[PLATFORM]/python" + ); + }); + } + + uv_snapshot!(context.filters(), context.python_uninstall().arg("3.12.8"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Searching for Python versions matching: Python 3.12.8 + Uninstalled Python 3.12.8 in [TIME] + - cpython-3.12.8-[PLATFORM] (python3.12) + "); + + // TODO(zanieb): This behavior is not implemented yet + // // The executable should be installed in the bin directory + // bin_python.assert(predicate::path::exists()); + + // // When the version is removed, the link should point to the other patch version + // if cfg!(unix) { + // insta::with_settings!({ + // filters => context.filters(), + // }, { + // insta::assert_snapshot!( + // canonicalize_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.6-[PLATFORM]/bin/python3.12" + // ); + // }); + // } else if cfg!(windows) { + // insta::with_settings!({ + // filters => context.filters(), + // }, { + // insta::assert_snapshot!( + // canonicalize_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.6-[PLATFORM]/python" + // ); + // }); + // } +} + #[test] fn python_install_preview() { let context: TestContext = TestContext::new_with_versions(&[]) @@ -853,7 +1095,7 @@ fn python_install_freethreaded() { ----- stderr ----- Installed Python 3.13.5 in [TIME] - + cpython-3.13.5-[PLATFORM] + + cpython-3.13.5-[PLATFORM] (python3.13) "); // Should not work with older Python versions @@ -875,7 +1117,7 @@ fn python_install_freethreaded() { Searching for Python installations Uninstalled 2 versions in [TIME] - cpython-3.13.5+freethreaded-[PLATFORM] (python3.13t) - - cpython-3.13.5-[PLATFORM] + - cpython-3.13.5-[PLATFORM] (python3.13) "); } @@ -936,15 +1178,243 @@ fn python_install_default() { .bin_dir .child(format!("python{}", std::env::consts::EXE_SUFFIX)); - // `--preview` is required for `--default` - uv_snapshot!(context.filters(), context.python_install().arg("--default"), @r###" - success: false - exit_code: 1 + // Install a specific version + uv_snapshot!(context.filters(), context.python_install().arg("3.13"), @r" + success: true + exit_code: 0 ----- stdout ----- ----- stderr ----- - The `--default` flag is only available in preview mode; add the `--preview` flag to use `--default` - "###); + Installed Python 3.13.5 in [TIME] + + cpython-3.13.5-[PLATFORM] (python3.13) + "); + + // Only the minor versioned executable should be installed + bin_python_minor_13.assert(predicate::path::exists()); + bin_python_major.assert(predicate::path::missing()); + bin_python_default.assert(predicate::path::missing()); + + // Install again, with `--default` + uv_snapshot!(context.filters(), context.python_install().arg("--default").arg("3.13"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: The `--default` option is experimental and may change without warning. Pass `--preview` to disable this warning + Installed Python 3.13.5 in [TIME] + + cpython-3.13.5-[PLATFORM] (python, python3) + "); + + // Now all the executables should be installed + bin_python_minor_13.assert(predicate::path::exists()); + bin_python_major.assert(predicate::path::exists()); + bin_python_default.assert(predicate::path::exists()); + + // Uninstall + uv_snapshot!(context.filters(), context.python_uninstall().arg("--all"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Searching for Python installations + Uninstalled Python 3.13.5 in [TIME] + - cpython-3.13.5-[PLATFORM] (python, python3, python3.13) + "); + + // The executables should be removed + bin_python_minor_13.assert(predicate::path::missing()); + bin_python_major.assert(predicate::path::missing()); + bin_python_default.assert(predicate::path::missing()); + + // Install the latest version, i.e., a "default install" + uv_snapshot!(context.filters(), context.python_install().arg("--default"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: The `--default` option is experimental and may change without warning. Pass `--preview` to disable this warning + Installed Python 3.13.5 in [TIME] + + cpython-3.13.5-[PLATFORM] (python, python3, python3.13) + "); + + // Since it's a default install, we should include all of the executables + bin_python_minor_13.assert(predicate::path::exists()); + bin_python_major.assert(predicate::path::exists()); + bin_python_default.assert(predicate::path::exists()); + + // And 3.13 should be the default + if cfg!(unix) { + insta::with_settings!({ + filters => context.filters(), + }, { + insta::assert_snapshot!( + read_link(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/bin/python3.13" + ); + }); + + insta::with_settings!({ + filters => context.filters(), + }, { + insta::assert_snapshot!( + read_link(&bin_python_minor_13), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/bin/python3.13" + ); + }); + + insta::with_settings!({ + filters => context.filters(), + }, { + insta::assert_snapshot!( + read_link(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/bin/python3.13" + ); + }); + } else if cfg!(windows) { + insta::with_settings!({ + filters => context.filters(), + }, { + insta::assert_snapshot!( + read_link(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/python" + ); + }); + + insta::with_settings!({ + filters => context.filters(), + }, { + insta::assert_snapshot!( + read_link(&bin_python_minor_13), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/python" + ); + }); + + insta::with_settings!({ + filters => context.filters(), + }, { + insta::assert_snapshot!( + read_link(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/python" + ); + }); + } + + // Uninstall again + uv_snapshot!(context.filters(), context.python_uninstall().arg("3.13"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Searching for Python versions matching: Python 3.13 + Uninstalled Python 3.13.5 in [TIME] + - cpython-3.13.5-[PLATFORM] (python, python3, python3.13) + "); + + // We should remove all the executables + bin_python_minor_13.assert(predicate::path::missing()); + bin_python_major.assert(predicate::path::missing()); + bin_python_default.assert(predicate::path::missing()); + + // Install multiple versions, with the `--default` flag + uv_snapshot!(context.filters(), context.python_install().arg("3.12").arg("3.13").arg("--default"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + warning: The `--default` option is experimental and may change without warning. Pass `--preview` to disable this warning + error: The `--default` flag cannot be used with multiple targets + "); + + // Install 3.12 as a new default + uv_snapshot!(context.filters(), context.python_install().arg("3.12").arg("--default"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: The `--default` option is experimental and may change without warning. Pass `--preview` to disable this warning + Installed Python 3.12.11 in [TIME] + + cpython-3.12.11-[PLATFORM] (python, python3, python3.12) + "); + + let bin_python_minor_12 = context + .bin_dir + .child(format!("python3.12{}", std::env::consts::EXE_SUFFIX)); + + // All the executables should exist + bin_python_minor_12.assert(predicate::path::exists()); + bin_python_major.assert(predicate::path::exists()); + bin_python_default.assert(predicate::path::exists()); + + // And 3.12 should be the default + if cfg!(unix) { + insta::with_settings!({ + filters => context.filters(), + }, { + insta::assert_snapshot!( + read_link(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/bin/python3.12" + ); + }); + + insta::with_settings!({ + filters => context.filters(), + }, { + insta::assert_snapshot!( + read_link(&bin_python_minor_12), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/bin/python3.12" + ); + }); + + insta::with_settings!({ + filters => context.filters(), + }, { + insta::assert_snapshot!( + read_link(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/bin/python3.12" + ); + }); + } else { + insta::with_settings!({ + filters => context.filters(), + }, { + insta::assert_snapshot!( + read_link(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/python" + ); + }); + + insta::with_settings!({ + filters => context.filters(), + }, { + insta::assert_snapshot!( + read_link(&bin_python_minor_12), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/python" + ); + }); + + insta::with_settings!({ + filters => context.filters(), + }, { + insta::assert_snapshot!( + read_link(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/python" + ); + }); + } +} + +#[test] +fn python_install_default_preview() { + let context: TestContext = TestContext::new_with_versions(&[]) + .with_filtered_python_keys() + .with_filtered_exe_suffix() + .with_managed_python_dirs(); + + let bin_python_minor_13 = context + .bin_dir + .child(format!("python3.13{}", std::env::consts::EXE_SUFFIX)); + + let bin_python_major = context + .bin_dir + .child(format!("python3{}", std::env::consts::EXE_SUFFIX)); + + let bin_python_default = context + .bin_dir + .child(format!("python{}", std::env::consts::EXE_SUFFIX)); // Install a specific version uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.13"), @r" @@ -1342,7 +1812,7 @@ fn python_install_unknown() { #[cfg(unix)] #[test] -fn python_install_preview_broken_link() { +fn python_install_broken_link() { use assert_fs::prelude::PathCreateDir; use fs_err::os::unix::fs::symlink; @@ -1358,7 +1828,7 @@ fn python_install_preview_broken_link() { symlink(context.temp_dir.join("does-not-exist"), &bin_python).unwrap(); // Install - uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.13"), @r" + uv_snapshot!(context.filters(), context.python_install().arg("3.13"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1393,7 +1863,7 @@ fn python_install_default_from_env() { ----- stderr ----- Installed Python 3.12.11 in [TIME] - + cpython-3.12.11-[PLATFORM] + + cpython-3.12.11-[PLATFORM] (python3.12) "); // But prefer explicit requests @@ -1404,7 +1874,7 @@ fn python_install_default_from_env() { ----- stderr ----- Installed Python 3.11.13 in [TIME] - + cpython-3.11.13-[PLATFORM] + + cpython-3.11.13-[PLATFORM] (python3.11) "); // We should ignore `UV_PYTHON` here and complain there is not a target @@ -1431,8 +1901,8 @@ fn python_install_default_from_env() { ----- stderr ----- Searching for Python installations Uninstalled 2 versions in [TIME] - - cpython-3.11.13-[PLATFORM] - - cpython-3.12.11-[PLATFORM] + - cpython-3.11.13-[PLATFORM] (python3.11) + - cpython-3.12.11-[PLATFORM] (python3.12) "); // Uninstall with no targets should error @@ -1516,8 +1986,6 @@ fn python_install_314() { let context: TestContext = TestContext::new_with_versions(&[]) .with_filtered_python_keys() .with_managed_python_dirs() - .with_filtered_python_install_bin() - .with_filtered_python_names() .with_filtered_exe_suffix(); // Install 3.14 @@ -1529,7 +1997,7 @@ fn python_install_314() { ----- stderr ----- Installed Python 3.14.0b4 in [TIME] - + cpython-3.14.0b4-[PLATFORM] + + cpython-3.14.0b4-[PLATFORM] (python3.14) "); // Install a specific pre-release @@ -1543,6 +2011,17 @@ fn python_install_314() { + cpython-3.14.0a4-[PLATFORM] "); + // Add name filtering for the `find` tests, we avoid it in `install` tests because it clobbers + // the version suffixes which matter in the install logs + let filters = context + .filters() + .iter() + .map(|(a, b)| ((*a).to_string(), (*b).to_string())) + .collect::>(); + let context = context + .with_filtered_python_install_bin() + .with_filtered_python_names(); + // We should be able to find this version without opt-in, because there is no stable release // installed uv_snapshot!(context.filters(), context.python_find().arg("3.14"), @r" @@ -1574,14 +2053,14 @@ fn python_install_314() { "); // If we install a stable version, that should be preferred though - uv_snapshot!(context.filters(), context.python_install().arg("3.13"), @r" + uv_snapshot!(filters, context.python_install().arg("3.13"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Installed Python 3.13.5 in [TIME] - + cpython-3.13.5-[PLATFORM] + + cpython-3.13.5-[PLATFORM] (python3.13) "); uv_snapshot!(context.filters(), context.python_find().arg("3"), @r" @@ -1621,15 +2100,15 @@ fn python_install_cached() { ----- stderr ----- Installed Python 3.13.5 in [TIME] - + cpython-3.13.5-[PLATFORM] + + cpython-3.13.5-[PLATFORM] (python3.13) "); let bin_python = context .bin_dir .child(format!("python3.13{}", std::env::consts::EXE_SUFFIX)); - // The executable should not be installed in the bin directory (requires preview) - bin_python.assert(predicate::path::missing()); + // The executable should be installed in the bin directory + bin_python.assert(predicate::path::exists()); // Should be a no-op when already installed uv_snapshot!(context.filters(), context @@ -1651,7 +2130,7 @@ fn python_install_cached() { ----- stderr ----- Searching for Python versions matching: Python 3.13 Uninstalled Python 3.13.5 in [TIME] - - cpython-3.13.5-[PLATFORM] + - cpython-3.13.5-[PLATFORM] (python3.13) "); // The cached archive can be installed offline @@ -1665,7 +2144,7 @@ fn python_install_cached() { ----- stderr ----- Installed Python 3.13.5 in [TIME] - + cpython-3.13.5-[PLATFORM] + + cpython-3.13.5-[PLATFORM] (python3.13) "); // 3.12 isn't cached, so it can't be installed @@ -1714,7 +2193,7 @@ fn python_install_emulated_macos() { ----- stderr ----- Installed Python 3.13.5 in [TIME] - + cpython-3.13.5-macos-x86_64-none + + cpython-3.13.5-macos-x86_64-none (python3.13) "); // It should be discoverable with `uv python find` diff --git a/docs/concepts/python-versions.md b/docs/concepts/python-versions.md index ee18fa9da..0c16218d4 100644 --- a/docs/concepts/python-versions.md +++ b/docs/concepts/python-versions.md @@ -121,28 +121,17 @@ present, uv will install all the Python versions listed in the file. ### Installing Python executables -!!! important - - Support for installing Python executables is in _preview_. This means the behavior is experimental - and subject to change. - -To install Python executables into your `PATH`, provide the `--preview` option: - -```console -$ uv python install 3.12 --preview -``` - -This will install a Python executable for the requested version into `~/.local/bin`, e.g., as -`python3.12`. +uv installs Python executables into your `PATH` by default, e.g., `uv python install 3.12` will +install a Python executable into `~/.local/bin`, e.g., as `python3.12`. !!! tip If `~/.local/bin` is not in your `PATH`, you can add it with `uv tool update-shell`. -To install `python` and `python3` executables, include the `--default` option: +To install `python` and `python3` executables, include the experimental `--default` option: ```console -$ uv python install 3.12 --default --preview +$ uv python install 3.12 --default ``` When installing Python executables, uv will only overwrite an existing executable if it is managed @@ -153,9 +142,9 @@ uv will update executables that it manages. However, it will prefer the latest p Python minor version by default. For example: ```console -$ uv python install 3.12.7 --preview # Adds `python3.12` to `~/.local/bin` -$ uv python install 3.12.6 --preview # Does not update `python3.12` -$ uv python install 3.12.8 --preview # Updates `python3.12` to point to 3.12.8 +$ uv python install 3.12.7 # Adds `python3.12` to `~/.local/bin` +$ uv python install 3.12.6 # Does not update `python3.12` +$ uv python install 3.12.8 # Updates `python3.12` to point to 3.12.8 ``` ## Upgrading Python versions diff --git a/docs/guides/install-python.md b/docs/guides/install-python.md index da841eac6..374ab29fd 100644 --- a/docs/guides/install-python.md +++ b/docs/guides/install-python.md @@ -24,17 +24,24 @@ $ uv python install Python does not publish official distributable binaries. As such, uv uses distributions from the Astral [`python-build-standalone`](https://github.com/astral-sh/python-build-standalone) project. See the [Python distributions](../concepts/python-versions.md#managed-python-distributions) documentation for more details. -Once Python is installed, it will be used by `uv` commands automatically. +Once Python is installed, it will be used by `uv` commands automatically. uv also adds the installed +version to your `PATH`: -!!! important +```console +$ python3.13 +``` - When Python is installed by uv, it will not be available globally (i.e. via the `python` command). - Support for this feature is in _preview_. See [Installing Python executables](../concepts/python-versions.md#installing-python-executables) - for details. +uv only installs a _versioned_ executable by default. To install `python` and `python3` executables, +include the experimental `--default` option: - You can still use - [`uv run`](../guides/scripts.md#using-different-python-versions) or - [create and activate a virtual environment](../pip/environments.md) to use `python` directly. +```console +$ uv python install --default +``` + +!!! tip + + See the documentation on [installing Python executables](../concepts/python-versions.md#installing-python-executables) + for more details. ## Installing a specific version diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 9be647449..4fc832cdb 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -2739,7 +2739,7 @@ Supports CPython and PyPy. CPython distributions are downloaded from the Astral Python versions are installed into the uv Python directory, which can be retrieved with `uv python dir`. -A `python` executable is not made globally available, managed Python versions are only used in uv commands or in active virtual environments. There is experimental support for adding Python executables to a directory on the path — use the `--preview` flag to enable this behavior and `uv python dir --bin` to retrieve the target directory. +By default, Python executables are added to a directory on the path with a minor version suffix, e.g., `python3.13`. To install `python3` and `python`, use the `--default` flag. Use `uv python dir --bin` to see the target directory. Multiple Python versions may be requested. From cd40a3452295a8d4b6af69206c43282096507c89 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 17 Jul 2025 13:38:02 -0500 Subject: [PATCH 255/349] Build and install workspace members that are dependencies by default (#14663) Regardless of the presence of a build system, as in https://github.com/astral-sh/uv/pull/14413 --------- Co-authored-by: John Mumm --- .../uv-distribution/src/metadata/lowering.rs | 8 +- crates/uv-platform-tags/src/tags.rs | 2 +- crates/uv-resolver/src/lock/mod.rs | 6 +- crates/uv-workspace/src/pyproject.rs | 8 +- crates/uv-workspace/src/workspace.rs | 125 ++++- crates/uv/src/commands/build_frontend.rs | 4 +- crates/uv/src/commands/project/lock.rs | 4 + crates/uv/src/commands/project/lock_target.rs | 14 +- crates/uv/src/commands/project/sync.rs | 2 +- crates/uv/tests/it/edit.rs | 20 +- crates/uv/tests/it/lock.rs | 459 +++++++++++++++++- crates/uv/tests/it/lock_conflict.rs | 40 +- crates/uv/tests/it/pip_compile.rs | 12 +- crates/uv/tests/it/sync.rs | 100 +++- docs/concepts/projects/dependencies.md | 54 ++- 15 files changed, 791 insertions(+), 67 deletions(-) diff --git a/crates/uv-distribution/src/metadata/lowering.rs b/crates/uv-distribution/src/metadata/lowering.rs index c05ac4779..a8e899bb4 100644 --- a/crates/uv-distribution/src/metadata/lowering.rs +++ b/crates/uv-distribution/src/metadata/lowering.rs @@ -306,7 +306,10 @@ impl LoweredRequirement { }, url, } - } else if member.pyproject_toml().is_package() { + } else if member + .pyproject_toml() + .is_package(!workspace.is_required_member(&requirement.name)) + { RequirementSource::Directory { install_path: install_path.into_boxed_path(), url, @@ -736,7 +739,8 @@ fn path_source( fs_err::read_to_string(&pyproject_path) .ok() .and_then(|contents| PyProjectToml::from_string(contents).ok()) - .and_then(|pyproject_toml| pyproject_toml.tool_uv_package()) + // We don't require a build system for path dependencies + .map(|pyproject_toml| pyproject_toml.is_package(false)) .unwrap_or(true) }); diff --git a/crates/uv-platform-tags/src/tags.rs b/crates/uv-platform-tags/src/tags.rs index 7381f5dd5..f2c6d6cbb 100644 --- a/crates/uv-platform-tags/src/tags.rs +++ b/crates/uv-platform-tags/src/tags.rs @@ -771,7 +771,7 @@ mod tests { /// A reference list can be generated with: /// ```text /// $ python -c "from packaging import tags; [print(tag) for tag in tags.platform_tags()]"` - /// ```` + /// ``` #[test] fn test_platform_tags_manylinux() { let tags = compatible_tags(&Platform::new( diff --git a/crates/uv-resolver/src/lock/mod.rs b/crates/uv-resolver/src/lock/mod.rs index 7cbac67df..49cb851b3 100644 --- a/crates/uv-resolver/src/lock/mod.rs +++ b/crates/uv-resolver/src/lock/mod.rs @@ -1255,6 +1255,7 @@ impl Lock { root: &Path, packages: &BTreeMap, members: &[PackageName], + required_members: &BTreeSet, requirements: &[Requirement], constraints: &[Requirement], overrides: &[Requirement], @@ -1282,7 +1283,10 @@ impl Lock { // Validate that the member sources have not changed (e.g., that they've switched from // virtual to non-virtual or vice versa). for (name, member) in packages { - let expected = !member.pyproject_toml().is_package(); + // We don't require a build system, if the workspace member is a dependency + let expected = !member + .pyproject_toml() + .is_package(!required_members.contains(name)); let actual = self .find_by_name(name) .ok() diff --git a/crates/uv-workspace/src/pyproject.rs b/crates/uv-workspace/src/pyproject.rs index aa64c601e..4a994b801 100644 --- a/crates/uv-workspace/src/pyproject.rs +++ b/crates/uv-workspace/src/pyproject.rs @@ -66,7 +66,7 @@ pub struct PyProjectToml { /// Used to determine whether a `build-system` section is present. #[serde(default, skip_serializing)] - build_system: Option, + pub build_system: Option, } impl PyProjectToml { @@ -81,18 +81,18 @@ impl PyProjectToml { /// Returns `true` if the project should be considered a Python package, as opposed to a /// non-package ("virtual") project. - pub fn is_package(&self) -> bool { + pub fn is_package(&self, require_build_system: bool) -> bool { // If `tool.uv.package` is set, defer to that explicit setting. if let Some(is_package) = self.tool_uv_package() { return is_package; } // Otherwise, a project is assumed to be a package if `build-system` is present. - self.build_system.is_some() + self.build_system.is_some() || !require_build_system } /// Returns the value of `tool.uv.package` if set. - pub fn tool_uv_package(&self) -> Option { + fn tool_uv_package(&self) -> Option { self.tool .as_ref() .and_then(|tool| tool.uv.as_ref()) diff --git a/crates/uv-workspace/src/workspace.rs b/crates/uv-workspace/src/workspace.rs index 8d09554d9..09f2b692a 100644 --- a/crates/uv-workspace/src/workspace.rs +++ b/crates/uv-workspace/src/workspace.rs @@ -20,7 +20,7 @@ use uv_warnings::warn_user_once; use crate::dependency_groups::{DependencyGroupError, FlatDependencyGroup, FlatDependencyGroups}; use crate::pyproject::{ - Project, PyProjectToml, PyprojectTomlError, Sources, ToolUvSources, ToolUvWorkspace, + Project, PyProjectToml, PyprojectTomlError, Source, Sources, ToolUvSources, ToolUvWorkspace, }; type WorkspaceMembers = Arc>; @@ -109,6 +109,8 @@ pub struct Workspace { install_path: PathBuf, /// The members of the workspace. packages: WorkspaceMembers, + /// The workspace members that are required by other members. + required_members: BTreeSet, /// The sources table from the workspace `pyproject.toml`. /// /// This table is overridden by the project sources. @@ -260,6 +262,7 @@ impl Workspace { pyproject_toml: PyProjectToml, ) -> Option { let mut packages = self.packages; + let member = Arc::make_mut(&mut packages).get_mut(package_name)?; if member.root == self.install_path { @@ -279,17 +282,33 @@ impl Workspace { // Set the `pyproject.toml` for the member. member.pyproject_toml = pyproject_toml; + // Recompute required_members with the updated data + let required_members = Self::collect_required_members( + &packages, + &workspace_sources, + &workspace_pyproject_toml, + ); + Some(Self { pyproject_toml: workspace_pyproject_toml, sources: workspace_sources, packages, + required_members, ..self }) } else { // Set the `pyproject.toml` for the member. member.pyproject_toml = pyproject_toml; - Some(Self { packages, ..self }) + // Recompute required_members with the updated member data + let required_members = + Self::collect_required_members(&packages, &self.sources, &self.pyproject_toml); + + Some(Self { + packages, + required_members, + ..self + }) } } @@ -303,7 +322,7 @@ impl Workspace { /// Returns the set of all workspace members. pub fn members_requirements(&self) -> impl Iterator + '_ { - self.packages.values().filter_map(|member| { + self.packages.iter().filter_map(|(name, member)| { let url = VerbatimUrl::from_absolute_path(&member.root) .expect("path is valid URL") .with_given(member.root.to_string_lossy()); @@ -312,7 +331,10 @@ impl Workspace { extras: Box::new([]), groups: Box::new([]), marker: MarkerTree::TRUE, - source: if member.pyproject_toml.is_package() { + source: if member + .pyproject_toml() + .is_package(!self.is_required_member(name)) + { RequirementSource::Directory { install_path: member.root.clone().into_boxed_path(), editable: Some(true), @@ -332,9 +354,65 @@ impl Workspace { }) } + /// The workspace members that are required my another member of the workspace. + pub fn required_members(&self) -> &BTreeSet { + &self.required_members + } + + /// Compute the workspace members that are required by another member of the workspace. + /// + /// N.B. this checks if a workspace member is required by inspecting `tool.uv.source` entries, + /// but does not actually check if the source is _used_, which could result in false positives + /// but is easier to compute. + fn collect_required_members( + packages: &BTreeMap, + sources: &BTreeMap, + pyproject_toml: &PyProjectToml, + ) -> BTreeSet { + sources + .iter() + .filter(|(name, _)| { + pyproject_toml + .project + .as_ref() + .is_none_or(|project| project.name != **name) + }) + .chain( + packages + .iter() + .filter_map(|(name, member)| { + member + .pyproject_toml + .tool + .as_ref() + .and_then(|tool| tool.uv.as_ref()) + .and_then(|uv| uv.sources.as_ref()) + .map(ToolUvSources::inner) + .map(move |sources| { + sources + .iter() + .filter(move |(source_name, _)| name != *source_name) + }) + }) + .flatten(), + ) + .filter_map(|(package, sources)| { + sources + .iter() + .any(|source| matches!(source, Source::Workspace { .. })) + .then_some(package.clone()) + }) + .collect() + } + + /// Whether a given workspace member is required by another member. + pub fn is_required_member(&self, name: &PackageName) -> bool { + self.required_members().contains(name) + } + /// Returns the set of all workspace member dependency groups. pub fn group_requirements(&self) -> impl Iterator + '_ { - self.packages.values().filter_map(|member| { + self.packages.iter().filter_map(|(name, member)| { let url = VerbatimUrl::from_absolute_path(&member.root) .expect("path is valid URL") .with_given(member.root.to_string_lossy()); @@ -368,7 +446,10 @@ impl Workspace { extras: Box::new([]), groups: groups.into_boxed_slice(), marker: MarkerTree::TRUE, - source: if member.pyproject_toml.is_package() { + source: if member + .pyproject_toml() + .is_package(!self.is_required_member(name)) + { RequirementSource::Directory { install_path: member.root.clone().into_boxed_path(), editable: Some(true), @@ -746,9 +827,16 @@ impl Workspace { .and_then(|uv| uv.index) .unwrap_or_default(); + let required_members = Self::collect_required_members( + &workspace_members, + &workspace_sources, + &workspace_pyproject_toml, + ); + Ok(Workspace { install_path: workspace_root, packages: workspace_members, + required_members, sources: workspace_sources, indexes: workspace_indexes, pyproject_toml: workspace_pyproject_toml, @@ -1232,15 +1320,23 @@ impl ProjectWorkspace { project.name.clone(), current_project, )])); + let workspace_sources = BTreeMap::default(); + let required_members = Workspace::collect_required_members( + ¤t_project_as_members, + &workspace_sources, + project_pyproject_toml, + ); + return Ok(Self { project_root: project_path.clone(), project_name: project.name.clone(), workspace: Workspace { install_path: project_path.clone(), packages: current_project_as_members, + required_members, // There may be package sources, but we don't need to duplicate them into the // workspace sources. - sources: BTreeMap::default(), + sources: workspace_sources, indexes: Vec::default(), pyproject_toml: project_pyproject_toml.clone(), }, @@ -1692,6 +1788,7 @@ mod tests { "pyproject_toml": "[PYPROJECT_TOML]" } }, + "required_members": [], "sources": {}, "indexes": [], "pyproject_toml": { @@ -1745,6 +1842,7 @@ mod tests { "pyproject_toml": "[PYPROJECT_TOML]" } }, + "required_members": [], "sources": {}, "indexes": [], "pyproject_toml": { @@ -1825,6 +1923,10 @@ mod tests { "pyproject_toml": "[PYPROJECT_TOML]" } }, + "required_members": [ + "bird-feeder", + "seeds" + ], "sources": { "bird-feeder": [ { @@ -1946,6 +2048,10 @@ mod tests { "pyproject_toml": "[PYPROJECT_TOML]" } }, + "required_members": [ + "bird-feeder", + "seeds" + ], "sources": {}, "indexes": [], "pyproject_toml": { @@ -2013,6 +2119,7 @@ mod tests { "pyproject_toml": "[PYPROJECT_TOML]" } }, + "required_members": [], "sources": {}, "indexes": [], "pyproject_toml": { @@ -2147,6 +2254,7 @@ mod tests { "pyproject_toml": "[PYPROJECT_TOML]" } }, + "required_members": [], "sources": {}, "indexes": [], "pyproject_toml": { @@ -2254,6 +2362,7 @@ mod tests { "pyproject_toml": "[PYPROJECT_TOML]" } }, + "required_members": [], "sources": {}, "indexes": [], "pyproject_toml": { @@ -2375,6 +2484,7 @@ mod tests { "pyproject_toml": "[PYPROJECT_TOML]" } }, + "required_members": [], "sources": {}, "indexes": [], "pyproject_toml": { @@ -2470,6 +2580,7 @@ mod tests { "pyproject_toml": "[PYPROJECT_TOML]" } }, + "required_members": [], "sources": {}, "indexes": [], "pyproject_toml": { diff --git a/crates/uv/src/commands/build_frontend.rs b/crates/uv/src/commands/build_frontend.rs index fd6ed73d7..a830f7aef 100644 --- a/crates/uv/src/commands/build_frontend.rs +++ b/crates/uv/src/commands/build_frontend.rs @@ -263,7 +263,7 @@ async fn build_impl( .get(package) .ok_or_else(|| anyhow::anyhow!("Package `{package}` not found in workspace"))?; - if !package.pyproject_toml().is_package() { + if !package.pyproject_toml().is_package(true) { let name = &package.project().name; let pyproject_toml = package.root().join("pyproject.toml"); return Err(anyhow::anyhow!( @@ -300,7 +300,7 @@ async fn build_impl( let packages: Vec<_> = workspace .packages() .values() - .filter(|package| package.pyproject_toml().is_package()) + .filter(|package| package.pyproject_toml().is_package(true)) .map(|package| AnnotatedSource { source: Source::Directory(Cow::Borrowed(package.root())), package: Some(package.project().name.clone()), diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index 833e59a13..e23bd97c2 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -444,6 +444,7 @@ async fn do_lock( // Collect the requirements, etc. let members = target.members(); let packages = target.packages(); + let required_members = target.required_members(); let requirements = target.requirements(); let overrides = target.overrides(); let constraints = target.constraints(); @@ -693,6 +694,7 @@ async fn do_lock( target.install_path(), packages, &members, + required_members, &requirements, &dependency_groups, &constraints, @@ -906,6 +908,7 @@ impl ValidatedLock { install_path: &Path, packages: &BTreeMap, members: &[PackageName], + required_members: &BTreeSet, requirements: &[Requirement], dependency_groups: &BTreeMap>, constraints: &[Requirement], @@ -1117,6 +1120,7 @@ impl ValidatedLock { install_path, packages, members, + required_members, requirements, constraints, overrides, diff --git a/crates/uv/src/commands/project/lock_target.rs b/crates/uv/src/commands/project/lock_target.rs index 4618b3b84..55a726bf4 100644 --- a/crates/uv/src/commands/project/lock_target.rs +++ b/crates/uv/src/commands/project/lock_target.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; use std::path::{Path, PathBuf}; use itertools::Either; @@ -154,6 +154,18 @@ impl<'lock> LockTarget<'lock> { } } + /// Return the set of required workspace members, i.e., those that are required by other + /// members. + pub(crate) fn required_members(self) -> &'lock BTreeSet { + match self { + Self::Workspace(workspace) => workspace.required_members(), + Self::Script(_) => { + static EMPTY: BTreeSet = BTreeSet::new(); + &EMPTY + } + } + } + /// Returns the set of supported environments for the [`LockTarget`]. pub(crate) fn environments(self) -> Option<&'lock SupportedEnvironments> { match self { diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index 40aa1b352..8d2dd9629 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -117,7 +117,7 @@ pub(crate) async fn sync( // TODO(lucab): improve warning content // if project.workspace().pyproject_toml().has_scripts() - && !project.workspace().pyproject_toml().is_package() + && !project.workspace().pyproject_toml().is_package(true) { warn_user!( "Skipping installation of entry points (`project.scripts`) because this project is not packaged; to install entry points, set `tool.uv.package = true` or define a `build-system`" diff --git a/crates/uv/tests/it/edit.rs b/crates/uv/tests/it/edit.rs index 70b8d6e50..aa494435c 100644 --- a/crates/uv/tests/it/edit.rs +++ b/crates/uv/tests/it/edit.rs @@ -10362,7 +10362,7 @@ fn add_self() -> Result<()> { filters => context.filters(), }, { assert_snapshot!( - pyproject_toml, @r###" + pyproject_toml, @r#" [project] name = "anyio" version = "0.1.0" @@ -10377,7 +10377,7 @@ fn add_self() -> Result<()> { [tool.uv.sources] anyio = { workspace = true } - "### + "# ); }); @@ -10398,7 +10398,7 @@ fn add_self() -> Result<()> { filters => context.filters(), }, { assert_snapshot!( - pyproject_toml, @r###" + pyproject_toml, @r#" [project] name = "anyio" version = "0.1.0" @@ -10418,7 +10418,7 @@ fn add_self() -> Result<()> { dev = [ "anyio[types]", ] - "### + "# ); }); @@ -13173,7 +13173,9 @@ fn add_path_with_existing_workspace() -> Result<()> { ----- stderr ----- Added `dep` to workspace members Resolved 3 packages in [TIME] - Audited in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + dep==0.1.0 (from file://[TEMP_DIR]/dep) "); let pyproject_toml = context.read("pyproject.toml"); @@ -13250,7 +13252,9 @@ fn add_path_with_workspace() -> Result<()> { ----- stderr ----- Added `dep` to workspace members Resolved 2 packages in [TIME] - Audited in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + dep==0.1.0 (from file://[TEMP_DIR]/dep) "); let pyproject_toml = context.read("pyproject.toml"); @@ -13316,7 +13320,9 @@ fn add_path_within_workspace_defaults_to_workspace() -> Result<()> { ----- stderr ----- Added `dep` to workspace members Resolved 2 packages in [TIME] - Audited in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + dep==0.1.0 (from file://[TEMP_DIR]/dep) "); let pyproject_toml = context.read("pyproject.toml"); diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index 75d81b4c0..ff9b711b7 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -12064,10 +12064,6 @@ fn lock_remove_member() -> Result<()> { requires-python = ">=3.12" dependencies = ["leaf"] - [build-system] - requires = ["setuptools>=42"] - build-backend = "setuptools.build_meta" - [tool.uv.workspace] members = ["leaf"] @@ -12130,7 +12126,7 @@ fn lock_remove_member() -> Result<()> { [[package]] name = "leaf" version = "0.1.0" - source = { virtual = "leaf" } + source = { editable = "leaf" } dependencies = [ { name = "anyio" }, ] @@ -12141,13 +12137,13 @@ fn lock_remove_member() -> Result<()> { [[package]] name = "project" version = "0.1.0" - source = { editable = "." } + source = { virtual = "." } dependencies = [ { name = "leaf" }, ] [package.metadata] - requires-dist = [{ name = "leaf", virtual = "leaf" }] + requires-dist = [{ name = "leaf", editable = "leaf" }] [[package]] name = "sniffio" @@ -12162,16 +12158,124 @@ fn lock_remove_member() -> Result<()> { }); // Re-run with `--locked`. - uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r###" + uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Resolved 5 packages in [TIME] - "###); + "); - // Remove the member. + // Remove the member as a dependency (retain it as a workspace member) + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + + [tool.uv.workspace] + members = ["leaf"] + + [tool.uv.sources] + leaf = { workspace = true } + "#, + )?; + + // Re-run with `--locked`. This should fail. + uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + Resolved 5 packages in [TIME] + The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + "); + + // Re-run without `--locked`. + uv_snapshot!(context.filters(), context.lock(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 5 packages in [TIME] + "); + + let lock = context.read("uv.lock"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + lock, @r#" + version = 1 + revision = 2 + requires-python = ">=3.12" + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [manifest] + members = [ + "leaf", + "project", + ] + + [[package]] + name = "anyio" + version = "4.3.0" + source = { registry = "https://pypi.org/simple" } + dependencies = [ + { name = "idna" }, + { name = "sniffio" }, + ] + sdist = { url = "https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6", size = 159642, upload-time = "2024-02-19T08:36:28.641Z" } + wheels = [ + { url = "https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8", size = 85584, upload-time = "2024-02-19T08:36:26.842Z" }, + ] + + [[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 = "leaf" + version = "0.1.0" + source = { editable = "leaf" } + dependencies = [ + { name = "anyio" }, + ] + + [package.metadata] + requires-dist = [{ name = "anyio", specifier = ">3" }] + + [[package]] + name = "project" + version = "0.1.0" + source = { virtual = "." } + + [[package]] + name = "sniffio" + version = "1.3.1" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } + wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, + ] + "# + ); + }); + + // Remove the member entirely pyproject_toml.write_str( r#" [project] @@ -12238,7 +12342,7 @@ fn lock_remove_member() -> Result<()> { /// This test would fail if we didn't write the list of workspace members to the lockfile, since /// we wouldn't be able to determine that a new member was added. #[test] -fn lock_add_member() -> Result<()> { +fn lock_add_member_with_build_system() -> Result<()> { let context = TestContext::new("3.12"); // Create a workspace, but don't add the member. @@ -12449,6 +12553,339 @@ fn lock_add_member() -> Result<()> { Ok(()) } +#[test] +fn lock_add_member_without_build_system() -> Result<()> { + let context = TestContext::new("3.12"); + + // Create a workspace, but don't add the member. + 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 = [] + + [tool.uv.workspace] + members = [] + "#, + )?; + + uv_snapshot!(context.filters(), context.lock(), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + "###); + + let lock = context.read("uv.lock"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + lock, @r#" + version = 1 + revision = 2 + requires-python = ">=3.12" + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [[package]] + name = "project" + version = "0.1.0" + source = { virtual = "." } + "# + ); + }); + + // Re-run with `--locked`. + uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + "###); + + // Create a workspace member. + let leaf = context.temp_dir.child("leaf"); + leaf.child("pyproject.toml").write_str( + r#" + [project] + name = "leaf" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["anyio>3"] + "#, + )?; + + // Add the member to the workspace, but not as a dependency of the root. + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + + [tool.uv.workspace] + members = ["leaf"] + "#, + )?; + + // Re-run with `--locked`. This should fail. + uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + Resolved 5 packages in [TIME] + The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + "); + + // Re-run with `--offline`. This should also fail, during the resolve phase. + uv_snapshot!(context.filters(), context.lock().arg("--locked").arg("--offline").arg("--no-cache"), @r###" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + × No solution found when resolving dependencies: + ╰─▶ Because anyio was not found in the cache and leaf depends on anyio>3, we can conclude that leaf's requirements are unsatisfiable. + And because your workspace requires leaf, we can conclude that your workspace's requirements are unsatisfiable. + + hint: Packages were unavailable because the network was disabled. When the network is disabled, registry packages may only be read from the cache. + "###); + + // Re-run without `--locked`. + uv_snapshot!(context.filters(), context.lock(), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 5 packages in [TIME] + Added anyio v4.3.0 + Added idna v3.6 + Added leaf v0.1.0 + Added sniffio v1.3.1 + "###); + + // Re-run with `--locked`. + uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 5 packages in [TIME] + "###); + + let lock = context.read("uv.lock"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + lock, @r#" + version = 1 + revision = 2 + requires-python = ">=3.12" + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [manifest] + members = [ + "leaf", + "project", + ] + + [[package]] + name = "anyio" + version = "4.3.0" + source = { registry = "https://pypi.org/simple" } + dependencies = [ + { name = "idna" }, + { name = "sniffio" }, + ] + sdist = { url = "https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6", size = 159642, upload-time = "2024-02-19T08:36:28.641Z" } + wheels = [ + { url = "https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8", size = 85584, upload-time = "2024-02-19T08:36:26.842Z" }, + ] + + [[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 = "leaf" + version = "0.1.0" + source = { virtual = "leaf" } + dependencies = [ + { name = "anyio" }, + ] + + [package.metadata] + requires-dist = [{ name = "anyio", specifier = ">3" }] + + [[package]] + name = "project" + version = "0.1.0" + source = { virtual = "." } + + [[package]] + name = "sniffio" + version = "1.3.1" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } + wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, + ] + "# + ); + }); + + // Add the member to the workspace, as a dependency of the root. + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["leaf"] + + [tool.uv.workspace] + members = ["leaf"] + + [tool.uv.sources] + leaf = { workspace = true } + "#, + )?; + + // Re-run with `--locked`. This should fail. + uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + Resolved 5 packages in [TIME] + The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + "); + + // Re-run without `--locked`. + uv_snapshot!(context.filters(), context.lock(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 5 packages in [TIME] + "); + + // Re-run with `--locked`. + uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 5 packages in [TIME] + "); + + let lock = context.read("uv.lock"); + + // It should change from a virtual to an editable source + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + lock, @r#" + version = 1 + revision = 2 + requires-python = ">=3.12" + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [manifest] + members = [ + "leaf", + "project", + ] + + [[package]] + name = "anyio" + version = "4.3.0" + source = { registry = "https://pypi.org/simple" } + dependencies = [ + { name = "idna" }, + { name = "sniffio" }, + ] + sdist = { url = "https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6", size = 159642, upload-time = "2024-02-19T08:36:28.641Z" } + wheels = [ + { url = "https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8", size = 85584, upload-time = "2024-02-19T08:36:26.842Z" }, + ] + + [[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 = "leaf" + version = "0.1.0" + source = { editable = "leaf" } + dependencies = [ + { name = "anyio" }, + ] + + [package.metadata] + requires-dist = [{ name = "anyio", specifier = ">3" }] + + [[package]] + name = "project" + version = "0.1.0" + source = { virtual = "." } + dependencies = [ + { name = "leaf" }, + ] + + [package.metadata] + requires-dist = [{ name = "leaf", editable = "leaf" }] + + [[package]] + name = "sniffio" + version = "1.3.1" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } + wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, + ] + "# + ); + }); + + Ok(()) +} + /// Lock a `pyproject.toml`, then add a dependency that's already included in the resolution. /// In theory, we shouldn't need to re-resolve, but based on our current strategy, we don't accept /// the existing lockfile. diff --git a/crates/uv/tests/it/lock_conflict.rs b/crates/uv/tests/it/lock_conflict.rs index bf1bc1eac..d67736c88 100644 --- a/crates/uv/tests/it/lock_conflict.rs +++ b/crates/uv/tests/it/lock_conflict.rs @@ -1094,18 +1094,19 @@ fn extra_unconditional() -> Result<()> { "###); // This is fine because we are only enabling one // extra, and thus, there is no conflict. - uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - Prepared 3 packages in [TIME] - Installed 3 packages in [TIME] + Prepared 4 packages in [TIME] + Installed 4 packages in [TIME] + anyio==4.1.0 + idna==3.6 + + proxy1==0.1.0 (from file://[TEMP_DIR]/proxy1) + sniffio==1.3.1 - "###); + "); // And same thing for the other extra. root_pyproject_toml.write_str( @@ -1215,18 +1216,19 @@ fn extra_unconditional_non_conflicting() -> Result<()> { // `uv sync` wasn't correctly propagating extras in a way // that would satisfy the conflict markers that got added // to the `proxy1[extra1]` dependency. - uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - Prepared 3 packages in [TIME] - Installed 3 packages in [TIME] + Prepared 4 packages in [TIME] + Installed 4 packages in [TIME] + anyio==4.1.0 + idna==3.6 + + proxy1==0.1.0 (from file://[TEMP_DIR]/proxy1) + sniffio==1.3.1 - "###); + "); Ok(()) } @@ -1301,16 +1303,17 @@ fn extra_unconditional_in_optional() -> Result<()> { "###); // This should install `sortedcontainers==2.3.0`. - uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--extra=x1"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--extra=x1"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - Prepared 1 package in [TIME] - Installed 1 package in [TIME] + Prepared 2 packages in [TIME] + Installed 2 packages in [TIME] + + proxy1==0.1.0 (from file://[TEMP_DIR]/proxy1) + sortedcontainers==2.3.0 - "###); + "); // This should install `sortedcontainers==2.4.0`. uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--extra=x2"), @r###" @@ -4460,19 +4463,20 @@ conflicts = [ error: Extra `x2` is not defined in the project's `optional-dependencies` table "###); - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Resolved 7 packages in [TIME] - Prepared 3 packages in [TIME] - Installed 3 packages in [TIME] + Prepared 4 packages in [TIME] + Installed 4 packages in [TIME] + anyio==4.3.0 + idna==3.6 + + proxy1==0.1.0 (from file://[TEMP_DIR]/proxy1) + sniffio==1.3.1 - "###); + "); let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap(); insta::with_settings!({ @@ -4558,14 +4562,14 @@ conflicts = [ requires-dist = [ { name = "anyio", specifier = ">=4" }, { name = "idna", marker = "extra == 'x1'", specifier = "==3.6" }, - { name = "proxy1", virtual = "proxy1" }, + { name = "proxy1", editable = "proxy1" }, ] provides-extras = ["x1"] [[package]] name = "proxy1" version = "0.1.0" - source = { virtual = "proxy1" } + source = { editable = "proxy1" } [package.optional-dependencies] x2 = [ diff --git a/crates/uv/tests/it/pip_compile.rs b/crates/uv/tests/it/pip_compile.rs index ac3549874..69da12fd6 100644 --- a/crates/uv/tests/it/pip_compile.rs +++ b/crates/uv/tests/it/pip_compile.rs @@ -15772,18 +15772,18 @@ fn project_and_group_workspace_inherit() -> Result<()> { ----- stdout ----- # This file was autogenerated by uv via the following command: # uv pip compile --cache-dir [CACHE_DIR] --group packages/mysubproject/pyproject.toml:foo + -e file://[TEMP_DIR]/packages/pytest + # via mysubproject (packages/mysubproject/pyproject.toml:foo) + -e file://[TEMP_DIR]/packages/sniffio + # via + # mysubproject (packages/mysubproject/pyproject.toml:foo) + # anyio anyio==4.3.0 # via mysubproject (packages/mysubproject/pyproject.toml:foo) idna==3.6 # via anyio iniconfig==2.0.0 # via mysubproject (packages/mysubproject/pyproject.toml:foo) - pytest @ file://[TEMP_DIR]/packages/pytest - # via mysubproject (packages/mysubproject/pyproject.toml:foo) - sniffio @ file://[TEMP_DIR]/packages/sniffio - # via - # mysubproject (packages/mysubproject/pyproject.toml:foo) - # anyio ----- stderr ----- Resolved 5 packages in [TIME] diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index bb3546e22..5a8d79447 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -3565,6 +3565,101 @@ fn sync_ignore_extras_check_when_no_provides_extras() -> Result<()> { Ok(()) } +#[test] +fn sync_workspace_members_with_transitive_dependencies() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [tool.uv.workspace] + members = [ + "packages/*", + ] + "#, + )?; + + let packages = context.temp_dir.child("packages"); + packages.create_dir_all()?; + + // Create three workspace members with transitive dependency from + // pkg-c -> pkg-b -> pkg-a + let pkg_a = packages.child("pkg-a"); + pkg_a.create_dir_all()?; + let pkg_a_pyproject_toml = pkg_a.child("pyproject.toml"); + pkg_a_pyproject_toml.write_str( + r#" + [project] + name = "pkg-a" + version = "0.0.1" + requires-python = ">=3.12" + dependencies = ["anyio"] + "#, + )?; + + let pkg_b = packages.child("pkg-b"); + pkg_b.create_dir_all()?; + let pkg_b_pyproject_toml = pkg_b.child("pyproject.toml"); + pkg_b_pyproject_toml.write_str( + r#" + [project] + name = "pkg-b" + version = "0.0.1" + requires-python = ">=3.12" + dependencies = ["pkg-a"] + + [tool.uv.sources] + pkg-a = { workspace = true } + "#, + )?; + + let pkg_c = packages.child("pkg-c"); + pkg_c.create_dir_all()?; + let pkg_c_pyproject_toml = pkg_c.child("pyproject.toml"); + pkg_c_pyproject_toml.write_str( + r#" + [project] + name = "pkg-c" + version = "0.0.1" + requires-python = ">=3.12" + dependencies = ["pkg-b"] + + [tool.uv.sources] + pkg-b = { workspace = true } + "#, + )?; + + // Syncing should build the two transitive dependencies pkg-a and pkg-b, + // but not pkg-c, which is not a dependency. + uv_snapshot!(context.filters(), context.sync(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 6 packages in [TIME] + Prepared 5 packages in [TIME] + Installed 5 packages in [TIME] + + anyio==4.3.0 + + idna==3.6 + + pkg-a==0.0.1 (from file://[TEMP_DIR]/packages/pkg-a) + + pkg-b==0.0.1 (from file://[TEMP_DIR]/packages/pkg-b) + + sniffio==1.3.1 + "); + + // The lockfile should be valid. + uv_snapshot!(context.filters(), context.lock().arg("--check"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 6 packages in [TIME] + "); + + Ok(()) +} + #[test] fn sync_non_existent_extra_workspace_member() -> Result<()> { let context = TestContext::new("3.12"); @@ -3626,9 +3721,10 @@ fn sync_non_existent_extra_workspace_member() -> Result<()> { ----- stderr ----- Resolved 5 packages in [TIME] - Prepared 3 packages in [TIME] - Installed 3 packages in [TIME] + Prepared 4 packages in [TIME] + Installed 4 packages in [TIME] + anyio==4.3.0 + + child==0.1.0 (from file://[TEMP_DIR]/child) + idna==3.6 + sniffio==1.3.1 "); diff --git a/docs/concepts/projects/dependencies.md b/docs/concepts/projects/dependencies.md index bf11e7174..52a71fd04 100644 --- a/docs/concepts/projects/dependencies.md +++ b/docs/concepts/projects/dependencies.md @@ -808,9 +808,9 @@ $ uv add --no-editable ./path/foo uv allows dependencies to be "virtual", in which the dependency itself is not installed as a [package](./config.md#project-packaging), but its dependencies are. -By default, only workspace members without build systems declared are virtual. +By default, dependencies are never virtual. -A dependency with a [`path` source](#path) is not virtual unless it explicitly sets +A dependency with a [`path` source](#path) can be virtual if it explicitly sets [`tool.uv.package = false`](../../reference/settings.md#package). Unlike working _in_ the dependent project with uv, the package will be built even if a [build system](./config.md#build-systems) is not declared. @@ -825,8 +825,8 @@ dependencies = ["bar"] bar = { path = "../projects/bar", package = false } ``` -Similarly, if a dependency sets `tool.uv.package = false`, it can be overridden by declaring -`package = true` on the source: +If a dependency sets `tool.uv.package = false`, it can be overridden by declaring `package = true` +on the source: ```toml title="pyproject.toml" [project] @@ -836,6 +836,52 @@ dependencies = ["bar"] bar = { path = "../projects/bar", package = true } ``` +Similarly, a dependency with a [`workspace` source](#workspace-member) can be virtual if it +explicitly sets [`tool.uv.package = false`](../../reference/settings.md#package). The workspace +member will be built even if a [build system](./config.md#build-systems) is not declared. + +Workspace members that are _not_ dependencies can be virtual by default, e.g., if the parent +`pyproject.toml` is: + +```toml title="pyproject.toml" +[project] +name = "parent" +version = "1.0.0" +dependencies = [] + +[tool.uv.workspace] +members = ["child"] +``` + +And the child `pyproject.toml` excluded a build system: + +```toml title="pyproject.toml" +[project] +name = "child" +version = "1.0.0" +dependencies = ["anyio"] +``` + +Then the `child` workspace member would not be installed, but the transitive dependency `anyio` +would be. + +In contrast, if the parent declared a dependency on `child`: + +```toml title="pyproject.toml" +[project] +name = "parent" +version = "1.0.0" +dependencies = ["child"] + +[tool.uv.sources] +child = { workspace = true } + +[tool.uv.workspace] +members = ["child"] +``` + +Then `child` would be built and installed. + ## Dependency specifiers uv uses standard From 5b716c4e50f2a4e829de3f474125cdac8de11c79 Mon Sep 17 00:00:00 2001 From: konsti Date: Thu, 17 Jul 2025 22:37:35 +0200 Subject: [PATCH 256/349] Add missing trailing newline to outdated error (#14689) Unlike the other branch in match, which uses a fully formatted error, we need to print the newline ourselves. Before (top) and after (bottom): image --- crates/uv/src/commands/diagnostics.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/uv/src/commands/diagnostics.rs b/crates/uv/src/commands/diagnostics.rs index 02412d683..f24aa3406 100644 --- a/crates/uv/src/commands/diagnostics.rs +++ b/crates/uv/src/commands/diagnostics.rs @@ -128,7 +128,7 @@ impl OperationDiagnostic { None } pip::operations::Error::OutdatedEnvironment => { - anstream::eprint!("{}", err); + anstream::eprintln!("{}", err); None } err => Some(err), From ac35377132954c9ee0fd60d5a464dab4490966c0 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 17 Jul 2025 15:52:31 -0500 Subject: [PATCH 257/349] Fix rendering of `uv venv --clear` hint in bash (#14691) Closes https://github.com/astral-sh/uv/issues/14688 --- crates/uv-console/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/uv-console/src/lib.rs b/crates/uv-console/src/lib.rs index 24c5eea16..1da7efdf1 100644 --- a/crates/uv-console/src/lib.rs +++ b/crates/uv-console/src/lib.rs @@ -84,6 +84,9 @@ fn confirm_inner( if hint.is_some() { term.clear_last_lines(2)?; + // It's not clear why we need to clear to the end of the screen here, but it fixes lingering + // display of the hint on `bash` (the issue did not reproduce on `zsh`). + term.clear_to_end_of_screen()?; } else { term.clear_line()?; } From 1a339b76e841188a04af9dbd1ad87136cd301122 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 17 Jul 2025 17:07:48 -0500 Subject: [PATCH 258/349] Add release notes and bump version for 0.8.0 (#14690) [Rendered](https://github.com/astral-sh/uv/blob/zb/release-notes/CHANGELOG.md) --- CHANGELOG.md | 197 +++++++++++++++++--- Cargo.lock | 6 +- crates/uv-build-backend/src/lib.rs | 2 +- crates/uv-build/Cargo.toml | 2 +- crates/uv-build/pyproject.toml | 2 +- crates/uv-version/Cargo.toml | 2 +- crates/uv/Cargo.toml | 2 +- docs/concepts/build-backend.md | 2 +- docs/getting-started/installation.md | 4 +- docs/guides/integration/aws-lambda.md | 4 +- docs/guides/integration/docker.md | 12 +- docs/guides/integration/github.md | 2 +- docs/guides/integration/pre-commit.md | 10 +- scripts/packages/built-by-uv/pyproject.toml | 2 +- 14 files changed, 199 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87cf0c9e8..351edc326 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,155 @@ +## 0.8.0 + +Since we released uv [0.7.0](https://github.com/astral-sh/uv/releases/tag/0.5.0) in April, we've accumulated various changes that improve correctness and user experience, but could break some workflows. This release contains those changes; many have been marked as breaking out of an abundance of caution. We expect most users to be able to upgrade without making changes. + +This release also includes the stabilization of a couple `uv python install` features, which have been available under preview since late last year. + +### Breaking changes + +- **Install Python executables into a directory on the `PATH` ([#14626](https://github.com/astral-sh/uv/pull/14626))** + + `uv python install` now installs a versioned Python executable (e.g., `python3.13`) into a directory on the `PATH` (e.g., `~/.local/bin`) by default. This behavior has been available under the `--preview` flag since [Oct 2024](https://github.com/astral-sh/uv/pull/8458). This change should not be breaking unless it shadows a Python executable elsewhere on the `PATH`. + + To install unversioned executables, i.e., `python3` and `python`, use the `--default` flag. The `--default` flag has also been in preview, but is not stabilized in this release. + + Note that these executables point to the base Python installation and only include the standard library. That means they will not include dependencies from your current project (use `uv run python` instead) and you cannot install packages into their environment (use `uvx --with python` instead). + + As with tool installation, the target directory respects common variables like `XDG_BIN_HOME` and can be overridden with a `UV_PYTHON_BIN_DIR` variable. + + You can opt out of this behavior with `uv python install --no-bin` or `UV_PYTHON_INSTALL_BIN=0`. + + See the [documentation on installing Python executables](https://docs.astral.sh/uv/concepts/python-versions/#installing-python-executables) for more details. + +- **Register Python versions with the Windows registry ([#14625](https://github.com/astral-sh/uv/pull/14625))** + + `uv python install` now registers the installed Python version with the Windows Registry as specified by [PEP 514](https://peps.python.org/pep-0514/). This allows using uv installed Python versions via the `py` launcher. This behavior has been available under the `--preview` flag since [Jan 2025](https://github.com/astral-sh/uv/pull/10634). This change should not be breaking, as using the uv Python versions with `py` requires explicit opt in. + + You can opt out of this behavior with `uv python install --no-registry` or `UV_PYTHON_INSTALL_REGISTRY=0`. + +- **Prompt before removing an existing directory in `uv venv` ([#14309](https://github.com/astral-sh/uv/pull/14309))** + + Previously, `uv venv` would remove an existing virtual environment without confirmation. While this is consistent with the behavior of project commands (e.g., `uv sync`), it's surprising to users that are using imperative workflows (i.e., `uv pip`). Now, `uv venv` will prompt for confirmation before removing an existing virtual environment. **If not in an interactive context, uv will still remove the virtual environment for backwards compatibility. However, this behavior is likely to change in a future release.** + + The behavior for other commands (e.g., `uv sync`) is unchanged. + + You can opt out of this behavior by setting `UV_VENV_CLEAR=1` or passing the `--clear` flag. + +- **Validate that discovered interpreters meet the Python preference ([#7934](https://github.com/astral-sh/uv/pull/7934))** + + uv allows opting out of its managed Python versions with the `--no-managed-python` and `python-preference` options. + + Previously, uv would not enforce this option for Python interpreters discovered on the `PATH`. For example, if a symlink to a managed Python interpreter was created, uv would allow it to be used even if `--no-managed-python` was provided. Now, uv ignores Python interpreters that do not match the Python preference _unless_ they are in an active virtual environment or are explicitly requested, e.g., with `--python /path/to/python3.13`. + + Similarly, uv would previously not invalidate existing project environments if they did not match the Python preference. Now, uv will invalidate and recreate project environments when the Python preference changes. + + You can opt out of this behavior by providing the explicit path to the Python interpreter providing `--managed-python` / `--no-managed-python` matching the interpreter you want. + +- **Install dependencies without build systems when they are `path` sources ([#14413](https://github.com/astral-sh/uv/pull/14413))** + + When working on a project, uv uses the [presence of a build system](https://docs.astral.sh/uv/concepts/projects/config/#build-systems) to determine if it should be built and installed into the environment. However, when a project is a dependency of another project, it can be surprising for the dependency to be missing from the environment. + + Previously, uv would not build and install dependencies with [`path` sources](https://docs.astral.sh/uv/concepts/projects/dependencies/#path) unless they declared a build system or set `tool.uv.package = true`. Now, dependencies with `path` sources are built and installed regardless of the presence of a build system. If a build system is not present, the `setuptools.build_meta:__legacy__ ` backend will be used (per [PEP 517](https://peps.python.org/pep-0517/#source-trees)). + + You can opt out of this behavior by setting `package = false` in the source declaration, e.g.: + + ```toml + [tool.uv.sources] + foo = { path = "./foo", package = false } + ``` + + Or, by setting `tool.uv.package = false` in the dependent `pyproject.toml`. + + See the documentation on [virtual dependencies](https://docs.astral.sh/uv/concepts/projects/dependencies/#virtual-dependencies) for details. + +- **Install dependencies without build systems when they are workspace members ([#14663](https://github.com/astral-sh/uv/pull/14663))** + + As described above for dependencies with `path` sources, uv previously would not build and install workspace members that did not declare a build system. Now, uv will build and install workspace members that are a dependency of _another_ workspace member regardless of the presence of a build system. The behavior is unchanged for workspace members that are not included in the `project.dependencies`, `project.optional-dependencies`, or `dependency-groups` tables of another workspace member. + + You can opt out of this behavior by setting `tool.uv.package = false` in the workspace member's `pyproject.toml`. + + See the documentation on [virtual dependencies](https://docs.astral.sh/uv/concepts/projects/dependencies/#virtual-dependencies) for details. + +- **Bump `--python-platform linux` to `manylinux_2_28` ([#14300](https://github.com/astral-sh/uv/pull/14300))** + + uv allows performing [platform-specific resolution](https://docs.astral.sh/uv/concepts/resolution/#platform-specific-resolution) for explicit targets and provides short aliases, e.g., `linux`, for common targets. + + Previously, the default target for `--python-platform linux` was `manylinux_2_17`, which is compatible with most Linux distributions from 2014 or newer. We now default to `manylinux_2_28`, which is compatible with most Linux distributions from 2017 or newer. This change follows the lead of other tools, such as `cibuildwheel`, which changed their default to `manylinux_2_28` in [Mar 2025](https://github.com/pypa/cibuildwheel/pull/2330). + + This change only affects users requesting a specific target platform. Otherwise, uv detects the `manylinux` target from your local glibc version. + + You can opt out of this behavior by using `--python-platform x86_64-manylinux_2_17` instead. + +- **Remove `uv version` fallback ([#14161](https://github.com/astral-sh/uv/pull/14161))** + + In [Apr 2025](https://github.com/astral-sh/uv/pull/12349), uv changed the `uv version` command to an interface for viewing and updating the version of the current project. However, when outside a project, `uv version` would continue to display uv's version for backwards compatibility. Now, when used outside of a project, `uv version` will fail. + + You cannot opt out of this behavior. Use `uv self version` instead. + +- **Require `--global` for removal of the global Python pin ([#14169](https://github.com/astral-sh/uv/pull/14169))** + + Previously, `uv python pin --rm` would allow you to remove the global Python pin without opt in. Now, uv requires the `--global` flag to remove the global Python pin. + + You cannot opt out of this behavior. Use the `--global` flag instead. + +- **Support conflicting editable settings across groups ([#14197](https://github.com/astral-sh/uv/pull/14197))** + + Previously, uv would always treat a package as editable if any requirement requested it as editable. However, this prevented users from declaring `path` sources that toggled the `editable` setting across dependency groups. Now, uv allows declaring different `editable` values for conflicting groups. However, if a project includes a path dependency twice, once with `editable = true` and once without any editable annotation, those are now considered conflicting, and uv will exit with an error. + + You cannot opt out of this behavior. Use consistent `editable` settings or [mark groups as conflicting](https://docs.astral.sh/uv/concepts/projects/config/#conflicting-dependencies). + +- **Make `uv_build` the default build backend in `uv init` ([#14661](https://github.com/astral-sh/uv/pull/14661))** + + The uv build backend (`uv_build`) was [stabilized in uv 0.7.19](https://github.com/astral-sh/uv/releases/tag/0.7.19). Now, it is the default build backend for `uv init --package` and `uv init --lib`. Previously, `hatchling` was the default build backend. A build backend is still not used without opt-in in `uv init`, but we expect to change this in a future release. + + You can opt out of this behavior with `uv init --build-backend hatchling`. + +- **Set default `UV_TOOL_BIN_DIR` on Docker images ([#13391](https://github.com/astral-sh/uv/pull/13391))** + + Previously, `UV_TOOL_BIN_DIR` was not set in Docker images which meant that `uv tool install` did not install tools into a directory on the `PATH` without additional configuration. Now, `UV_TOOL_BIN_DIR` is set to `/usr/local/bin` in all Docker derived images. + + When the default image user is overridden (e.g. `USER `) with a less privileged user, this may cause `uv tool install` to fail. + + You can opt out of this behavior by setting an alternative `UV_TOOL_BIN_DIR`. + +- **Update `--check` to return an exit code of 1 ([#14167](https://github.com/astral-sh/uv/pull/14167))** + + uv uses an exit code of 1 to indicate a "successful failure" and an exit code of 2 to indicate an "error". + + Previously, `uv lock --check` and `uv sync --check` would exit with a code of 2 when the lockfile or environment were outdated. Now, uv will exit with a code of 1. + + You cannot opt out of this behavior. + +- **Use an ephemeral environment for `uv run --with` invocations ([#14447](https://github.com/astral-sh/uv/pull/14447))** + + When using `uv run --with`, uv layers the requirements requested using `--with` into another virtual environment and caches it. Previously, uv would invoke the Python interpreter in this layered environment. However, this allows poisoning the cached environment and introduces race conditions for concurrent invocations. Now, uv will layer _another_ empty virtual environment on top of the cached environment and invoke the Python interpreter there. This should only cause breakage in cases where the environment is being inspected at runtime. + + You cannot opt out of this behavior. + +- **Restructure the `uv venv` command output and exit codes ([#14546](https://github.com/astral-sh/uv/pull/14546))** + + Previously, uv used `miette` to format the `uv venv` output. However, this was inconsistent with most of the uv CLI. Now, the output is a little different and the exit code has switched from 1 to 2 for some error cases. + + You cannot opt out of this behavior. + +- **Default to `--workspace` when adding subdirectories ([#14529](https://github.com/astral-sh/uv/pull/14529))** + + When using `uv add` to add a subdirectory in a workspace, uv now defaults to adding the target as a workspace member. + + You can opt out of this behavior by providing `--no-workspace`. + +- **Add missing validations for disallowed `uv.toml` fields ([#14322](https://github.com/astral-sh/uv/pull/14322))** + + uv does not allow some settings in the `uv.toml`. Previously, some settings were silently ignored when present in the `uv.toml`. Now, uv will error. + + You cannot opt out of this behavior. Use `--no-config` or remove the invalid settings. + +### Configuration + +- Add support for toggling Python bin and registry install options via env vars ([#14662](https://github.com/astral-sh/uv/pull/14662)) + ## 0.7.22 ### Python @@ -153,7 +302,7 @@ See the [python-build-standalone release](https://github.com/astral-sh/python-bu ### Python - Added arm64 Windows Python 3.11, 3.12, 3.13, and 3.14 - + These are not downloaded by default, since x86-64 Python has broader ecosystem support on Windows. However, they can be requested with `cpython--windows-aarch64`. @@ -633,11 +782,11 @@ This release contains various changes that improve correctness and user experien ### Breaking changes - **Update `uv version` to display and update project versions ([#12349](https://github.com/astral-sh/uv/pull/12349))** - + Previously, `uv version` displayed uv's version. Now, `uv version` will display or update the project's version. This interface was [heavily requested](https://github.com/astral-sh/uv/issues/6298) and, after much consideration, we decided that transitioning the top-level command was the best option. - + Here's a brief example: - + ```console $ uv init example Initialized project `example` at `./example` @@ -649,72 +798,72 @@ This release contains various changes that improve correctness and user experien $ uv version --short 1.0.0 ``` - + If used outside of a project, uv will fallback to showing its own version still: - + ```console $ uv version warning: failed to read project: No `pyproject.toml` found in current directory or any parent directory running `uv self version` for compatibility with old `uv version` command. this fallback will be removed soon, pass `--preview` to make this an error. - + uv 0.7.0 (4433f41c9 2025-04-29) ``` - + As described in the warning, `--preview` can be used to error instead: - + ```console $ uv version --preview error: No `pyproject.toml` found in current directory or any parent directory ``` - + The previous functionality of `uv version` was moved to `uv self version`. - **Avoid fallback to subsequent indexes on authentication failure ([#12805](https://github.com/astral-sh/uv/pull/12805))** - + When using the `first-index` strategy (the default), uv will stop searching indexes for a package once it is found on a single index. Previously, uv considered a package as "missing" from an index during authentication failures, such as an HTTP 401 or HTTP 403 (normally, missing packages are represented by an HTTP 404). This behavior was motivated by unusual responses from some package indexes, but reduces the safety of uv's index strategy when authentication fails. Now, uv will consider an authentication failure as a stop-point when searching for a package across indexes. The `index.ignore-error-codes` option can be used to recover the existing behavior, e.g.: - + ```toml [[tool.uv.index]] name = "pytorch" url = "https://download.pytorch.org/whl/cpu" ignore-error-codes = [401, 403] ``` - + Since PyTorch's indexes always return a HTTP 403 for missing packages, uv special-cases indexes on the `pytorch.org` domain to ignore that error code by default. - **Require the command in `uvx ` to be available in the Python environment ([#11603](https://github.com/astral-sh/uv/pull/11603))** - + Previously, `uvx` would attempt to execute a command even if it was not provided by a Python package. For example, if we presume `foo` is an empty Python package which provides no command, `uvx foo` would invoke the `foo` command on the `PATH` (if present). Now, uv will error early if the `foo` executable is not provided by the requested Python package. This check is not enforced when `--from` is used, so patterns like `uvx --from foo bash -c "..."` are still valid. uv also still allows `uvx foo` where the `foo` executable is provided by a dependency of `foo` instead of `foo` itself, as this is fairly common for packages which depend on a dedicated package for their command-line interface. - **Use index URL instead of package URL for keyring credential lookups ([#12651](https://github.com/astral-sh/uv/pull/12651))** - + When determining credentials for querying a package URL, uv previously sent the full URL to the `keyring` command. However, some keyring plugins expect to receive the *index URL* (which is usually a parent of the package URL). Now, uv requests credentials for the index URL instead. This behavior matches `pip`. - **Remove `--version` from subcommands ([#13108](https://github.com/astral-sh/uv/pull/13108))** - + Previously, uv allowed the `--version` flag on arbitrary subcommands, e.g., `uv run --version`. However, the `--version` flag is useful for other operations since uv is a package manager. Consequently, we've removed the `--version` flag from subcommands — it is only available as `uv --version`. - **Omit Python 3.7 downloads from managed versions ([#13022](https://github.com/astral-sh/uv/pull/13022))** - + Python 3.7 is EOL and not formally supported by uv; however, Python 3.7 was previously available for download on a subset of platforms. - **Reject non-PEP 751 TOML files in install, compile, and export commands ([#13120](https://github.com/astral-sh/uv/pull/13120), [#13119](https://github.com/astral-sh/uv/pull/13119))** - + Previously, uv treated arbitrary `.toml` files passed to commands (e.g., `uv pip install -r foo.toml` or `uv pip compile -o foo.toml`) as `requirements.txt`-formatted files. Now, uv will error instead. If using PEP 751 lockfiles, use the standardized format for custom names instead, e.g., `pylock.foo.toml`. - **Ignore arbitrary Python requests in version files ([#12909](https://github.com/astral-sh/uv/pull/12909))** - + uv allows arbitrary strings to be used for Python version requests, in which they are treated as an executable name to search for in the `PATH`. However, using this form of request in `.python-version` files is non-standard and conflicts with `pyenv-virtualenv` which writes environment names to `.python-version` files. In this release, uv will now ignore requests that are arbitrary strings when found in `.python-version` files. - **Error on unknown dependency object specifiers ([12811](https://github.com/astral-sh/uv/pull/12811))** - + The `[dependency-groups]` entries can include "object specifiers", e.g. `set-phasers-to = ...` in: - + ```toml [dependency-groups] foo = ["pyparsing"] bar = [{set-phasers-to = "stun"}] ``` - + However, the only current spec-compliant object specifier is `include-group`. Previously, uv would ignore unknown object specifiers. Now, uv will error. - **Make `--frozen` and `--no-sources` conflicting options ([#12671](https://github.com/astral-sh/uv/pull/12671))** - + Using `--no-sources` always requires a new resolution and `--frozen` will always fail when used with it. Now, this conflict is encoded in the CLI options for clarity. - **Treat empty `UV_PYTHON_INSTALL_DIR` and `UV_TOOL_DIR` as unset ([#12907](https://github.com/astral-sh/uv/pull/12907), [#12905](https://github.com/astral-sh/uv/pull/12905))** - + Previously, these variables were treated as set to the current working directory when set to an empty string. Now, uv will ignore these variables when empty. This matches uv's behavior for other environment variables which configure directories. ### Enhancements diff --git a/Cargo.lock b/Cargo.lock index 0900699cb..78429b08f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4633,7 +4633,7 @@ dependencies = [ [[package]] name = "uv" -version = "0.7.22" +version = "0.8.0" dependencies = [ "anstream", "anyhow", @@ -4799,7 +4799,7 @@ dependencies = [ [[package]] name = "uv-build" -version = "0.7.22" +version = "0.8.0" dependencies = [ "anyhow", "uv-build-backend", @@ -5992,7 +5992,7 @@ dependencies = [ [[package]] name = "uv-version" -version = "0.7.22" +version = "0.8.0" [[package]] name = "uv-virtualenv" diff --git a/crates/uv-build-backend/src/lib.rs b/crates/uv-build-backend/src/lib.rs index 2ec11aeeb..8add8dda3 100644 --- a/crates/uv-build-backend/src/lib.rs +++ b/crates/uv-build-backend/src/lib.rs @@ -557,7 +557,7 @@ mod tests { // Check that the source dist is reproducible across platforms. assert_snapshot!( format!("{:x}", sha2::Sha256::digest(fs_err::read(&source_dist_path).unwrap())), - @"dab46bcc4d66960a11cfdc19604512a8e1a3241a67536f7e962166760e9c575c" + @"9a7f7181c5e69ac14e411a2500fed153a1e6ea41cd5da6f24f226c4cddacf6b7" ); // Check both the files we report and the actual files assert_snapshot!(format_file_list(build.source_dist_list_files, src.path()), @r" diff --git a/crates/uv-build/Cargo.toml b/crates/uv-build/Cargo.toml index 8014fa445..dcf61a435 100644 --- a/crates/uv-build/Cargo.toml +++ b/crates/uv-build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-build" -version = "0.7.22" +version = "0.8.0" edition.workspace = true rust-version.workspace = true homepage.workspace = true diff --git a/crates/uv-build/pyproject.toml b/crates/uv-build/pyproject.toml index 1a78d34dc..53bcbf49b 100644 --- a/crates/uv-build/pyproject.toml +++ b/crates/uv-build/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uv-build" -version = "0.7.22" +version = "0.8.0" description = "The uv build backend" authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }] requires-python = ">=3.8" diff --git a/crates/uv-version/Cargo.toml b/crates/uv-version/Cargo.toml index e1a424af8..02f940b30 100644 --- a/crates/uv-version/Cargo.toml +++ b/crates/uv-version/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-version" -version = "0.7.22" +version = "0.8.0" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml index ff389f033..d160cce7b 100644 --- a/crates/uv/Cargo.toml +++ b/crates/uv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv" -version = "0.7.22" +version = "0.8.0" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } diff --git a/docs/concepts/build-backend.md b/docs/concepts/build-backend.md index d2edf1bad..d29420085 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.7.22,<0.8.0"] +requires = ["uv_build>=0.8.0,<0.9.0"] build-backend = "uv_build" ``` diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 3e31a5003..5e8165824 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.7.22/install.sh | sh + $ curl -LsSf https://astral.sh/uv/0.8.0/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.7.22/install.ps1 | iex" + PS> powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/0.8.0/install.ps1 | iex" ``` !!! tip diff --git a/docs/guides/integration/aws-lambda.md b/docs/guides/integration/aws-lambda.md index 14224b3fe..d9fc06d29 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.7.22 AS uv +FROM ghcr.io/astral-sh/uv:0.8.0 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.7.22 AS uv +FROM ghcr.io/astral-sh/uv:0.8.0 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 a75228723..0eeaed62d 100644 --- a/docs/guides/integration/docker.md +++ b/docs/guides/integration/docker.md @@ -31,8 +31,8 @@ $ 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.7.22` -- `ghcr.io/astral-sh/uv:{major}.{minor}`, e.g., `ghcr.io/astral-sh/uv:0.7` (the latest patch +- `ghcr.io/astral-sh/uv:{major}.{minor}.{patch}`, e.g., `ghcr.io/astral-sh/uv:0.8.0` +- `ghcr.io/astral-sh/uv:{major}.{minor}`, e.g., `ghcr.io/astral-sh/uv:0.8` (the latest patch version) And the following derived images are available: @@ -75,7 +75,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.7.22-alpine`. +`ghcr.io/astral-sh/uv:{major}.{minor}-{base}`, e.g., `ghcr.io/astral-sh/uv:0.8.0-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. @@ -116,7 +116,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.7.22 /uv /uvx /bin/ +COPY --from=ghcr.io/astral-sh/uv:0.8.0 /uv /uvx /bin/ ``` !!! tip @@ -134,7 +134,7 @@ COPY --from=ghcr.io/astral-sh/uv:0.7.22 /uv /uvx /bin/ Or, with the installer: ```dockerfile -ADD https://astral.sh/uv/0.7.22/install.sh /uv-installer.sh +ADD https://astral.sh/uv/0.8.0/install.sh /uv-installer.sh ``` ### Installing a project @@ -560,5 +560,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.7.22`, or (even better) the specific image digest, + version tag, e.g., `ghcr.io/astral-sh/uv:0.8.0`, 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 956b47660..15d26b280 100644 --- a/docs/guides/integration/github.md +++ b/docs/guides/integration/github.md @@ -47,7 +47,7 @@ jobs: uses: astral-sh/setup-uv@v6 with: # Install a specific version of uv. - version: "0.7.22" + version: "0.8.0" ``` ## Setting up Python diff --git a/docs/guides/integration/pre-commit.md b/docs/guides/integration/pre-commit.md index d2598fed8..bbc21ab45 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.7.22 + rev: 0.8.0 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.7.22 + rev: 0.8.0 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.7.22 + rev: 0.8.0 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.7.22 + rev: 0.8.0 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.7.22 + rev: 0.8.0 hooks: # Compile requirements - id: pip-compile diff --git a/scripts/packages/built-by-uv/pyproject.toml b/scripts/packages/built-by-uv/pyproject.toml index f9f893485..b1914e071 100644 --- a/scripts/packages/built-by-uv/pyproject.toml +++ b/scripts/packages/built-by-uv/pyproject.toml @@ -24,5 +24,5 @@ data = "assets" headers = "header" [build-system] -requires = ["uv_build>=0.7,<0.8"] +requires = ["uv_build>=0.8,<0.9"] build-backend = "uv_build" From 1f887552f6c630c4736ec05cf834086fe95eb92f Mon Sep 17 00:00:00 2001 From: Geoffrey Thomas Date: Thu, 17 Jul 2025 18:25:03 -0400 Subject: [PATCH 259/349] CHANGELOG: manylinux_2_28 is more like 2019 (#14696) I must have Googled something too fast, sorry. glibc 2.28 came out August 2018, Fedora 29 was the earliest to ship with it in October 2018, Debian 10 shipped with it in July 2019, and CentOS 8 shipped with it in September 2019. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 351edc326..e3d8f6f17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,7 +78,7 @@ This release also includes the stabilization of a couple `uv python install` fea uv allows performing [platform-specific resolution](https://docs.astral.sh/uv/concepts/resolution/#platform-specific-resolution) for explicit targets and provides short aliases, e.g., `linux`, for common targets. - Previously, the default target for `--python-platform linux` was `manylinux_2_17`, which is compatible with most Linux distributions from 2014 or newer. We now default to `manylinux_2_28`, which is compatible with most Linux distributions from 2017 or newer. This change follows the lead of other tools, such as `cibuildwheel`, which changed their default to `manylinux_2_28` in [Mar 2025](https://github.com/pypa/cibuildwheel/pull/2330). + Previously, the default target for `--python-platform linux` was `manylinux_2_17`, which is compatible with most Linux distributions from 2014 or newer. We now default to `manylinux_2_28`, which is compatible with most Linux distributions from 2019 or newer. This change follows the lead of other tools, such as `cibuildwheel`, which changed their default to `manylinux_2_28` in [Mar 2025](https://github.com/pypa/cibuildwheel/pull/2330). This change only affects users requesting a specific target platform. Otherwise, uv detects the `manylinux` target from your local glibc version. From 0b23572941e271086485227f7c2b5c440062660f Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 17 Jul 2025 17:26:47 -0500 Subject: [PATCH 260/349] Bump version to 0.8.0 Somehow this one was missed? --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a079d53b2..1d0a1e713 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "uv" -version = "0.7.22" +version = "0.8.0" 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" From a6a5e65e0c2e6aa7d56554ab86033b7066865f51 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 17 Jul 2025 18:11:22 -0500 Subject: [PATCH 261/349] Edits to the 0.8 changelog entry (#14698) --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3d8f6f17..b80747ed1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ ## 0.8.0 -Since we released uv [0.7.0](https://github.com/astral-sh/uv/releases/tag/0.5.0) in April, we've accumulated various changes that improve correctness and user experience, but could break some workflows. This release contains those changes; many have been marked as breaking out of an abundance of caution. We expect most users to be able to upgrade without making changes. +Since we released uv [0.7.0](https://github.com/astral-sh/uv/releases/tag/0.7.0) in April, we've accumulated various changes that improve correctness and user experience, but could break some workflows. This release contains those changes; many have been marked as breaking out of an abundance of caution. We expect most users to be able to upgrade without making changes. This release also includes the stabilization of a couple `uv python install` features, which have been available under preview since late last year. @@ -25,7 +25,7 @@ This release also includes the stabilization of a couple `uv python install` fea See the [documentation on installing Python executables](https://docs.astral.sh/uv/concepts/python-versions/#installing-python-executables) for more details. -- **Register Python versions with the Windows registry ([#14625](https://github.com/astral-sh/uv/pull/14625))** +- **Register Python versions with the Windows Registry ([#14625](https://github.com/astral-sh/uv/pull/14625))** `uv python install` now registers the installed Python version with the Windows Registry as specified by [PEP 514](https://peps.python.org/pep-0514/). This allows using uv installed Python versions via the `py` launcher. This behavior has been available under the `--preview` flag since [Jan 2025](https://github.com/astral-sh/uv/pull/10634). This change should not be breaking, as using the uv Python versions with `py` requires explicit opt in. From e724ddc63f14b9378672c16433dbfba534c6cb84 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 17 Jul 2025 21:27:54 -0400 Subject: [PATCH 262/349] Allow `--config-settings-package` to apply configuration settings at the package level (#14573) ## Summary Closes https://github.com/astral-sh/uv/issues/14564. Closes https://github.com/astral-sh/uv/issues/10940. --- crates/uv-bench/benches/uv.rs | 6 +- crates/uv-cli/src/lib.rs | 37 +++- crates/uv-cli/src/options.rs | 32 ++- .../uv-configuration/src/config_settings.rs | 184 ++++++++++++++++++ crates/uv-dispatch/src/lib.rs | 25 ++- .../src/index/built_wheel_index.rs | 42 ++-- crates/uv-distribution/src/source/mod.rs | 51 +++-- crates/uv-installer/src/plan.rs | 11 +- crates/uv-settings/src/combine.rs | 15 +- crates/uv-settings/src/settings.rs | 34 +++- crates/uv-types/src/traits.rs | 7 +- crates/uv/src/commands/build_frontend.rs | 6 +- crates/uv/src/commands/pip/compile.rs | 5 +- crates/uv/src/commands/pip/install.rs | 6 +- crates/uv/src/commands/pip/operations.rs | 4 +- crates/uv/src/commands/pip/sync.rs | 6 +- crates/uv/src/commands/project/add.rs | 1 + crates/uv/src/commands/project/lock.rs | 2 + crates/uv/src/commands/project/mod.rs | 10 + crates/uv/src/commands/project/sync.rs | 3 + crates/uv/src/commands/project/tree.rs | 1 + crates/uv/src/commands/venv.rs | 4 +- crates/uv/src/lib.rs | 3 + crates/uv/src/settings.rs | 22 ++- crates/uv/tests/it/pip_install.rs | 119 ++++++++++- crates/uv/tests/it/show_settings.rs | 108 +++++++++- crates/uv/tests/it/sync.rs | 143 ++++++++++++++ docs/reference/cli.md | 15 ++ docs/reference/settings.md | 54 +++++ uv.schema.json | 29 +++ 30 files changed, 927 insertions(+), 58 deletions(-) diff --git a/crates/uv-bench/benches/uv.rs b/crates/uv-bench/benches/uv.rs index 9bdd7adb9..8380ccd60 100644 --- a/crates/uv-bench/benches/uv.rs +++ b/crates/uv-bench/benches/uv.rs @@ -86,8 +86,8 @@ mod resolver { use uv_cache::Cache; use uv_client::RegistryClient; use uv_configuration::{ - BuildOptions, Concurrency, ConfigSettings, Constraints, IndexStrategy, PreviewMode, - SourceStrategy, + BuildOptions, Concurrency, ConfigSettings, Constraints, IndexStrategy, + PackageConfigSettings, PreviewMode, SourceStrategy, }; use uv_dispatch::{BuildDispatch, SharedState}; use uv_distribution::DistributionDatabase; @@ -144,6 +144,7 @@ mod resolver { let build_options = BuildOptions::default(); let concurrency = Concurrency::default(); let config_settings = ConfigSettings::default(); + let config_settings_package = PackageConfigSettings::default(); let exclude_newer = Some( jiff::civil::date(2024, 9, 1) .to_zoned(jiff::tz::TimeZone::UTC) @@ -184,6 +185,7 @@ mod resolver { state, IndexStrategy::default(), &config_settings, + &config_settings_package, build_isolation, LinkMode::default(), &build_options, diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 9d7cfa6e0..d6560014f 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -10,8 +10,9 @@ use clap::{Args, Parser, Subcommand}; use uv_cache::CacheArgs; use uv_configuration::{ - ConfigSettingEntry, ExportFormat, IndexStrategy, KeyringProviderType, PackageNameSpecifier, - ProjectBuildBackend, TargetTriple, TrustedHost, TrustedPublishing, VersionControlSystem, + ConfigSettingEntry, ConfigSettingPackageEntry, ExportFormat, IndexStrategy, + KeyringProviderType, PackageNameSpecifier, ProjectBuildBackend, TargetTriple, TrustedHost, + TrustedPublishing, VersionControlSystem, }; use uv_distribution_types::{Index, IndexUrl, Origin, PipExtraIndex, PipFindLinks, PipIndex}; use uv_normalize::{ExtraName, GroupName, PackageName, PipGroupName}; @@ -4693,6 +4694,14 @@ pub struct ToolUpgradeArgs { )] pub config_setting: Option>, + /// Settings to pass to the PEP 517 build backend for a specific package, specified as `PACKAGE:KEY=VALUE` pairs. + #[arg( + long, + alias = "config-settings-package", + help_heading = "Build options" + )] + pub config_setting_package: Option>, + /// Disable isolation when building source distributions. /// /// Assumes that build dependencies specified by PEP 518 are already installed. @@ -5484,6 +5493,14 @@ pub struct InstallerArgs { )] pub config_setting: Option>, + /// Settings to pass to the PEP 517 build backend for a specific package, specified as `PACKAGE:KEY=VALUE` pairs. + #[arg( + long, + alias = "config-settings-package", + help_heading = "Build options" + )] + pub config_settings_package: Option>, + /// Disable isolation when building source distributions. /// /// Assumes that build dependencies specified by PEP 518 are already installed. @@ -5671,6 +5688,14 @@ pub struct ResolverArgs { )] pub config_setting: Option>, + /// Settings to pass to the PEP 517 build backend for a specific package, specified as `PACKAGE:KEY=VALUE` pairs. + #[arg( + long, + alias = "config-settings-package", + help_heading = "Build options" + )] + pub config_settings_package: Option>, + /// Disable isolation when building source distributions. /// /// Assumes that build dependencies specified by PEP 518 are already installed. @@ -5860,6 +5885,14 @@ pub struct ResolverInstallerArgs { )] pub config_setting: Option>, + /// Settings to pass to the PEP 517 build backend for a specific package, specified as `PACKAGE:KEY=VALUE` pairs. + #[arg( + long, + alias = "config-settings-package", + help_heading = "Build options" + )] + pub config_settings_package: Option>, + /// Disable isolation when building source distributions. /// /// Assumes that build dependencies specified by PEP 518 are already installed. diff --git a/crates/uv-cli/src/options.rs b/crates/uv-cli/src/options.rs index f522022a1..d2e651a19 100644 --- a/crates/uv-cli/src/options.rs +++ b/crates/uv-cli/src/options.rs @@ -1,7 +1,7 @@ use anstream::eprintln; use uv_cache::Refresh; -use uv_configuration::ConfigSettings; +use uv_configuration::{ConfigSettings, PackageConfigSettings}; use uv_resolver::PrereleaseMode; use uv_settings::{Combine, PipOptions, ResolverInstallerOptions, ResolverOptions}; use uv_warnings::owo_colors::OwoColorize; @@ -62,6 +62,7 @@ impl From for PipOptions { pre, fork_strategy, config_setting, + config_settings_package, no_build_isolation, no_build_isolation_package, build_isolation, @@ -84,6 +85,11 @@ impl From for PipOptions { }, config_settings: config_setting .map(|config_settings| config_settings.into_iter().collect::()), + config_settings_package: config_settings_package.map(|config_settings| { + config_settings + .into_iter() + .collect::() + }), no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"), no_build_isolation_package: Some(no_build_isolation_package), exclude_newer, @@ -104,6 +110,7 @@ impl From for PipOptions { index_strategy, keyring_provider, config_setting, + config_settings_package, no_build_isolation, build_isolation, exclude_newer, @@ -120,6 +127,11 @@ impl From for PipOptions { keyring_provider, config_settings: config_setting .map(|config_settings| config_settings.into_iter().collect::()), + config_settings_package: config_settings_package.map(|config_settings| { + config_settings + .into_iter() + .collect::() + }), no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"), exclude_newer, link_mode, @@ -147,6 +159,7 @@ impl From for PipOptions { pre, fork_strategy, config_setting, + config_settings_package, no_build_isolation, no_build_isolation_package, build_isolation, @@ -173,6 +186,11 @@ impl From for PipOptions { fork_strategy, config_settings: config_setting .map(|config_settings| config_settings.into_iter().collect::()), + config_settings_package: config_settings_package.map(|config_settings| { + config_settings + .into_iter() + .collect::() + }), no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"), no_build_isolation_package: Some(no_build_isolation_package), exclude_newer, @@ -260,6 +278,7 @@ pub fn resolver_options( pre, fork_strategy, config_setting, + config_settings_package, no_build_isolation, no_build_isolation_package, build_isolation, @@ -321,6 +340,11 @@ pub fn resolver_options( dependency_metadata: None, config_settings: config_setting .map(|config_settings| config_settings.into_iter().collect::()), + config_settings_package: config_settings_package.map(|config_settings| { + config_settings + .into_iter() + .collect::() + }), no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"), no_build_isolation_package: Some(no_build_isolation_package), exclude_newer, @@ -353,6 +377,7 @@ pub fn resolver_installer_options( pre, fork_strategy, config_setting, + config_settings_package, no_build_isolation, no_build_isolation_package, build_isolation, @@ -428,6 +453,11 @@ pub fn resolver_installer_options( dependency_metadata: None, config_settings: config_setting .map(|config_settings| config_settings.into_iter().collect::()), + config_settings_package: config_settings_package.map(|config_settings| { + config_settings + .into_iter() + .collect::() + }), no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"), no_build_isolation_package: if no_build_isolation_package.is_empty() { None diff --git a/crates/uv-configuration/src/config_settings.rs b/crates/uv-configuration/src/config_settings.rs index cd1d67196..c6238deb2 100644 --- a/crates/uv-configuration/src/config_settings.rs +++ b/crates/uv-configuration/src/config_settings.rs @@ -3,6 +3,7 @@ use std::{ str::FromStr, }; use uv_cache_key::CacheKeyHasher; +use uv_normalize::PackageName; #[derive(Debug, Clone)] pub struct ConfigSettingEntry { @@ -28,6 +29,32 @@ impl FromStr for ConfigSettingEntry { } } +#[derive(Debug, Clone)] +pub struct ConfigSettingPackageEntry { + /// The package name to apply the setting to. + package: PackageName, + /// The config setting entry. + setting: ConfigSettingEntry, +} + +impl FromStr for ConfigSettingPackageEntry { + type Err = String; + + fn from_str(s: &str) -> Result { + let Some((package_str, config_str)) = s.split_once(':') else { + return Err(format!( + "Invalid config setting: {s} (expected `PACKAGE:KEY=VALUE`)" + )); + }; + + let package = PackageName::from_str(package_str.trim()) + .map_err(|e| format!("Invalid package name: {e}"))?; + let setting = ConfigSettingEntry::from_str(config_str)?; + + Ok(Self { package, setting }) + } +} + #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema), schemars(untagged))] enum ConfigSettingValue { @@ -212,6 +239,111 @@ impl<'de> serde::Deserialize<'de> for ConfigSettings { } } +/// Settings to pass to PEP 517 build backends on a per-package basis. +#[derive(Debug, Default, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +pub struct PackageConfigSettings(BTreeMap); + +impl FromIterator for PackageConfigSettings { + fn from_iter>(iter: T) -> Self { + let mut package_configs: BTreeMap> = BTreeMap::new(); + + for entry in iter { + package_configs + .entry(entry.package) + .or_default() + .push(entry.setting); + } + + let configs = package_configs + .into_iter() + .map(|(package, entries)| (package, entries.into_iter().collect())) + .collect(); + + Self(configs) + } +} + +impl PackageConfigSettings { + /// Returns the config settings for a specific package, if any. + pub fn get(&self, package: &PackageName) -> Option<&ConfigSettings> { + self.0.get(package) + } + + /// Returns `true` if there are no package-specific settings. + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Merge two sets of package config settings, with the values in `self` taking precedence. + #[must_use] + pub fn merge(mut self, other: PackageConfigSettings) -> PackageConfigSettings { + for (package, settings) in other.0 { + match self.0.entry(package) { + Entry::Vacant(vacant) => { + vacant.insert(settings); + } + Entry::Occupied(mut occupied) => { + let merged = occupied.get().clone().merge(settings); + occupied.insert(merged); + } + } + } + self + } +} + +impl uv_cache_key::CacheKey for PackageConfigSettings { + fn cache_key(&self, state: &mut CacheKeyHasher) { + for (package, settings) in &self.0 { + package.to_string().cache_key(state); + settings.cache_key(state); + } + } +} + +impl serde::Serialize for PackageConfigSettings { + fn serialize(&self, serializer: S) -> Result { + use serde::ser::SerializeMap; + + let mut map = serializer.serialize_map(Some(self.0.len()))?; + for (key, value) in &self.0 { + map.serialize_entry(&key.to_string(), value)?; + } + map.end() + } +} + +impl<'de> serde::Deserialize<'de> for PackageConfigSettings { + fn deserialize>(deserializer: D) -> Result { + struct Visitor; + + impl<'de> serde::de::Visitor<'de> for Visitor { + type Value = PackageConfigSettings; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a map from package name to config settings") + } + + fn visit_map>( + self, + mut map: A, + ) -> Result { + let mut config = BTreeMap::default(); + while let Some((key, value)) = map.next_entry::()? { + let package = PackageName::from_str(&key).map_err(|e| { + serde::de::Error::custom(format!("Invalid package name: {e}")) + })?; + config.insert(package, value); + } + Ok(PackageConfigSettings(config)) + } + } + + deserializer.deserialize_map(Visitor) + } +} + #[cfg(test)] mod tests { use super::*; @@ -291,4 +423,56 @@ mod tests { ); assert_eq!(settings.escape_for_python(), r#"{"key":"val\\1 {}value"}"#); } + + #[test] + fn parse_config_setting_package_entry() { + // Test valid parsing + let entry = ConfigSettingPackageEntry::from_str("numpy:editable_mode=compat").unwrap(); + assert_eq!(entry.package.as_ref(), "numpy"); + assert_eq!(entry.setting.key, "editable_mode"); + assert_eq!(entry.setting.value, "compat"); + + // Test with package name containing hyphens + let entry = ConfigSettingPackageEntry::from_str("my-package:some_key=value").unwrap(); + assert_eq!(entry.package.as_ref(), "my-package"); + assert_eq!(entry.setting.key, "some_key"); + assert_eq!(entry.setting.value, "value"); + + // Test with spaces around values + let entry = ConfigSettingPackageEntry::from_str(" numpy : key = value ").unwrap(); + assert_eq!(entry.package.as_ref(), "numpy"); + assert_eq!(entry.setting.key, "key"); + assert_eq!(entry.setting.value, "value"); + } + + #[test] + fn collect_config_settings_package() { + let settings: PackageConfigSettings = vec![ + ConfigSettingPackageEntry::from_str("numpy:editable_mode=compat").unwrap(), + ConfigSettingPackageEntry::from_str("numpy:another_key=value").unwrap(), + ConfigSettingPackageEntry::from_str("scipy:build_option=fast").unwrap(), + ] + .into_iter() + .collect(); + + let numpy_settings = settings + .get(&PackageName::from_str("numpy").unwrap()) + .unwrap(); + assert_eq!( + numpy_settings.0.get("editable_mode"), + Some(&ConfigSettingValue::String("compat".to_string())) + ); + assert_eq!( + numpy_settings.0.get("another_key"), + Some(&ConfigSettingValue::String("value".to_string())) + ); + + let scipy_settings = settings + .get(&PackageName::from_str("scipy").unwrap()) + .unwrap(); + assert_eq!( + scipy_settings.0.get("build_option"), + Some(&ConfigSettingValue::String("fast".to_string())) + ); + } } diff --git a/crates/uv-dispatch/src/lib.rs b/crates/uv-dispatch/src/lib.rs index 874e412e5..2e34b583d 100644 --- a/crates/uv-dispatch/src/lib.rs +++ b/crates/uv-dispatch/src/lib.rs @@ -17,8 +17,8 @@ use uv_build_frontend::{SourceBuild, SourceBuildContext}; use uv_cache::Cache; use uv_client::RegistryClient; use uv_configuration::{ - BuildKind, BuildOptions, ConfigSettings, Constraints, IndexStrategy, PreviewMode, Reinstall, - SourceStrategy, + BuildKind, BuildOptions, ConfigSettings, Constraints, IndexStrategy, PackageConfigSettings, + PreviewMode, Reinstall, SourceStrategy, }; use uv_configuration::{BuildOutput, Concurrency}; use uv_distribution::DistributionDatabase; @@ -91,6 +91,7 @@ pub struct BuildDispatch<'a> { link_mode: uv_install_wheel::LinkMode, build_options: &'a BuildOptions, config_settings: &'a ConfigSettings, + config_settings_package: &'a PackageConfigSettings, hasher: &'a HashStrategy, exclude_newer: Option, source_build_context: SourceBuildContext, @@ -113,6 +114,7 @@ impl<'a> BuildDispatch<'a> { shared_state: SharedState, index_strategy: IndexStrategy, config_settings: &'a ConfigSettings, + config_settings_package: &'a PackageConfigSettings, build_isolation: BuildIsolation<'a>, link_mode: uv_install_wheel::LinkMode, build_options: &'a BuildOptions, @@ -134,6 +136,7 @@ impl<'a> BuildDispatch<'a> { dependency_metadata, index_strategy, config_settings, + config_settings_package, build_isolation, link_mode, build_options, @@ -200,6 +203,10 @@ impl BuildContext for BuildDispatch<'_> { self.config_settings } + fn config_settings_package(&self) -> &PackageConfigSettings { + self.config_settings_package + } + fn sources(&self) -> SourceStrategy { self.sources } @@ -295,6 +302,7 @@ impl BuildContext for BuildDispatch<'_> { self.hasher, self.index_locations, self.config_settings, + self.config_settings_package, self.cache(), venv, tags, @@ -418,6 +426,17 @@ impl BuildContext for BuildDispatch<'_> { build_stack.insert(dist.distribution_id()); } + // Get package-specific config settings if available; otherwise, use global settings. + let config_settings = if let Some(name) = dist_name { + if let Some(package_settings) = self.config_settings_package.get(name) { + package_settings.clone().merge(self.config_settings.clone()) + } else { + self.config_settings.clone() + } + } else { + self.config_settings.clone() + }; + let builder = SourceBuild::setup( source, subdirectory, @@ -431,7 +450,7 @@ impl BuildContext for BuildDispatch<'_> { self.index_locations, sources, self.workspace_cache(), - self.config_settings.clone(), + config_settings, self.build_isolation, &build_stack, build_kind, diff --git a/crates/uv-distribution/src/index/built_wheel_index.rs b/crates/uv-distribution/src/index/built_wheel_index.rs index 9752e7e4f..90ce5deed 100644 --- a/crates/uv-distribution/src/index/built_wheel_index.rs +++ b/crates/uv-distribution/src/index/built_wheel_index.rs @@ -1,10 +1,12 @@ +use std::borrow::Cow; use uv_cache::{Cache, CacheBucket, CacheShard, WheelCache}; use uv_cache_info::CacheInfo; use uv_cache_key::cache_digest; -use uv_configuration::ConfigSettings; +use uv_configuration::{ConfigSettings, PackageConfigSettings}; use uv_distribution_types::{ DirectUrlSourceDist, DirectorySourceDist, GitSourceDist, Hashed, PathSourceDist, }; +use uv_normalize::PackageName; use uv_platform_tags::Tags; use uv_types::HashStrategy; @@ -18,7 +20,8 @@ pub struct BuiltWheelIndex<'a> { cache: &'a Cache, tags: &'a Tags, hasher: &'a HashStrategy, - build_configuration: &'a ConfigSettings, + config_settings: &'a ConfigSettings, + config_settings_package: &'a PackageConfigSettings, } impl<'a> BuiltWheelIndex<'a> { @@ -27,13 +30,15 @@ impl<'a> BuiltWheelIndex<'a> { cache: &'a Cache, tags: &'a Tags, hasher: &'a HashStrategy, - build_configuration: &'a ConfigSettings, + config_settings: &'a ConfigSettings, + config_settings_package: &'a PackageConfigSettings, ) -> Self { Self { cache, tags, hasher, - build_configuration, + config_settings, + config_settings_package, } } @@ -63,10 +68,11 @@ impl<'a> BuiltWheelIndex<'a> { let cache_shard = cache_shard.shard(revision.id()); // If there are build settings, we need to scope to a cache shard. - let cache_shard = if self.build_configuration.is_empty() { + let config_settings = self.config_settings_for(&source_dist.name); + let cache_shard = if config_settings.is_empty() { cache_shard } else { - cache_shard.shard(cache_digest(self.build_configuration)) + cache_shard.shard(cache_digest(&config_settings)) }; Ok(self.find(&cache_shard)) @@ -100,10 +106,11 @@ impl<'a> BuiltWheelIndex<'a> { let cache_shard = cache_shard.shard(revision.id()); // If there are build settings, we need to scope to a cache shard. - let cache_shard = if self.build_configuration.is_empty() { + let config_settings = self.config_settings_for(&source_dist.name); + let cache_shard = if config_settings.is_empty() { cache_shard } else { - cache_shard.shard(cache_digest(self.build_configuration)) + cache_shard.shard(cache_digest(&config_settings)) }; Ok(self @@ -148,10 +155,11 @@ impl<'a> BuiltWheelIndex<'a> { let cache_shard = cache_shard.shard(revision.id()); // If there are build settings, we need to scope to a cache shard. - let cache_shard = if self.build_configuration.is_empty() { + let config_settings = self.config_settings_for(&source_dist.name); + let cache_shard = if config_settings.is_empty() { cache_shard } else { - cache_shard.shard(cache_digest(self.build_configuration)) + cache_shard.shard(cache_digest(&config_settings)) }; Ok(self @@ -174,10 +182,11 @@ impl<'a> BuiltWheelIndex<'a> { ); // If there are build settings, we need to scope to a cache shard. - let cache_shard = if self.build_configuration.is_empty() { + let config_settings = self.config_settings_for(&source_dist.name); + let cache_shard = if config_settings.is_empty() { cache_shard } else { - cache_shard.shard(cache_digest(self.build_configuration)) + cache_shard.shard(cache_digest(&config_settings)) }; self.find(&cache_shard) @@ -239,4 +248,13 @@ impl<'a> BuiltWheelIndex<'a> { candidate } + + /// Determine the [`ConfigSettings`] for the given package name. + fn config_settings_for(&self, name: &PackageName) -> Cow<'_, ConfigSettings> { + if let Some(package_settings) = self.config_settings_package.get(name) { + Cow::Owned(package_settings.clone().merge(self.config_settings.clone())) + } else { + Cow::Borrowed(self.config_settings) + } + } } diff --git a/crates/uv-distribution/src/source/mod.rs b/crates/uv-distribution/src/source/mod.rs index 1308e3d77..080a1e52d 100644 --- a/crates/uv-distribution/src/source/mod.rs +++ b/crates/uv-distribution/src/source/mod.rs @@ -29,7 +29,7 @@ use uv_cache_key::cache_digest; use uv_client::{ CacheControl, CachedClientError, Connectivity, DataWithCachePolicy, RegistryClient, }; -use uv_configuration::{BuildKind, BuildOutput, SourceStrategy}; +use uv_configuration::{BuildKind, BuildOutput, ConfigSettings, SourceStrategy}; use uv_distribution_filename::{SourceDistExtension, WheelFilename}; use uv_distribution_types::{ BuildableSource, DirectorySourceUrl, GitSourceUrl, HashPolicy, Hashed, PathSourceUrl, @@ -373,6 +373,23 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { Ok(metadata) } + /// Determine the [`ConfigSettings`] for the given package name. + fn config_settings_for(&self, name: Option<&PackageName>) -> Cow<'_, ConfigSettings> { + if let Some(name) = name { + if let Some(package_settings) = self.build_context.config_settings_package().get(name) { + Cow::Owned( + package_settings + .clone() + .merge(self.build_context.config_settings().clone()), + ) + } else { + Cow::Borrowed(self.build_context.config_settings()) + } + } else { + Cow::Borrowed(self.build_context.config_settings()) + } + } + /// Build a source distribution from a remote URL. async fn url<'data>( &self, @@ -407,11 +424,11 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { let source_dist_entry = cache_shard.entry(SOURCE); // If there are build settings, we need to scope to a cache shard. - let config_settings = self.build_context.config_settings(); + let config_settings = self.config_settings_for(source.name()); let cache_shard = if config_settings.is_empty() { cache_shard } else { - cache_shard.shard(cache_digest(config_settings)) + cache_shard.shard(cache_digest(&&config_settings)) }; // If the cache contains a compatible wheel, return it. @@ -580,11 +597,11 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { } // If there are build settings, we need to scope to a cache shard. - let config_settings = self.build_context.config_settings(); + let config_settings = self.config_settings_for(source.name()); let cache_shard = if config_settings.is_empty() { cache_shard } else { - cache_shard.shard(cache_digest(config_settings)) + cache_shard.shard(cache_digest(&config_settings)) }; // Otherwise, we either need to build the metadata. @@ -779,11 +796,11 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { let source_entry = cache_shard.entry(SOURCE); // If there are build settings, we need to scope to a cache shard. - let config_settings = self.build_context.config_settings(); + let config_settings = self.config_settings_for(source.name()); let cache_shard = if config_settings.is_empty() { cache_shard } else { - cache_shard.shard(cache_digest(config_settings)) + cache_shard.shard(cache_digest(&config_settings)) }; // If the cache contains a compatible wheel, return it. @@ -941,11 +958,11 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { } // If there are build settings, we need to scope to a cache shard. - let config_settings = self.build_context.config_settings(); + let config_settings = self.config_settings_for(source.name()); let cache_shard = if config_settings.is_empty() { cache_shard } else { - cache_shard.shard(cache_digest(config_settings)) + cache_shard.shard(cache_digest(&config_settings)) }; // Otherwise, we need to build a wheel. @@ -1083,11 +1100,11 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { let cache_shard = cache_shard.shard(revision.id()); // If there are build settings, we need to scope to a cache shard. - let config_settings = self.build_context.config_settings(); + let config_settings = self.config_settings_for(source.name()); let cache_shard = if config_settings.is_empty() { cache_shard } else { - cache_shard.shard(cache_digest(config_settings)) + cache_shard.shard(cache_digest(&config_settings)) }; // If the cache contains a compatible wheel, return it. @@ -1271,11 +1288,11 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { } // If there are build settings, we need to scope to a cache shard. - let config_settings = self.build_context.config_settings(); + let config_settings = self.config_settings_for(source.name()); let cache_shard = if config_settings.is_empty() { cache_shard } else { - cache_shard.shard(cache_digest(config_settings)) + cache_shard.shard(cache_digest(&config_settings)) }; // Otherwise, we need to build a wheel. @@ -1476,11 +1493,11 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { let _lock = cache_shard.lock().await.map_err(Error::CacheWrite)?; // If there are build settings, we need to scope to a cache shard. - let config_settings = self.build_context.config_settings(); + let config_settings = self.config_settings_for(source.name()); let cache_shard = if config_settings.is_empty() { cache_shard } else { - cache_shard.shard(cache_digest(config_settings)) + cache_shard.shard(cache_digest(&config_settings)) }; // If the cache contains a compatible wheel, return it. @@ -1779,11 +1796,11 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { } // If there are build settings, we need to scope to a cache shard. - let config_settings = self.build_context.config_settings(); + let config_settings = self.config_settings_for(source.name()); let cache_shard = if config_settings.is_empty() { cache_shard } else { - cache_shard.shard(cache_digest(config_settings)) + cache_shard.shard(cache_digest(&config_settings)) }; // Otherwise, we need to build a wheel. diff --git a/crates/uv-installer/src/plan.rs b/crates/uv-installer/src/plan.rs index e030e9b4d..69e10befc 100644 --- a/crates/uv-installer/src/plan.rs +++ b/crates/uv-installer/src/plan.rs @@ -4,7 +4,7 @@ use tracing::{debug, warn}; use uv_cache::{Cache, CacheBucket, WheelCache}; use uv_cache_info::Timestamp; -use uv_configuration::{BuildOptions, ConfigSettings, Reinstall}; +use uv_configuration::{BuildOptions, ConfigSettings, PackageConfigSettings, Reinstall}; use uv_distribution::{ BuiltWheelIndex, HttpArchivePointer, LocalArchivePointer, RegistryWheelIndex, }; @@ -52,6 +52,7 @@ impl<'a> Planner<'a> { hasher: &HashStrategy, index_locations: &IndexLocations, config_settings: &ConfigSettings, + config_settings_package: &PackageConfigSettings, cache: &Cache, venv: &PythonEnvironment, tags: &Tags, @@ -59,7 +60,13 @@ impl<'a> Planner<'a> { // Index all the already-downloaded wheels in the cache. let mut registry_index = RegistryWheelIndex::new(cache, tags, index_locations, hasher, config_settings); - let built_index = BuiltWheelIndex::new(cache, tags, hasher, config_settings); + let built_index = BuiltWheelIndex::new( + cache, + tags, + hasher, + config_settings, + config_settings_package, + ); let mut cached = vec![]; let mut remote = vec![]; diff --git a/crates/uv-settings/src/combine.rs b/crates/uv-settings/src/combine.rs index 8edbd2a05..738b00ffe 100644 --- a/crates/uv-settings/src/combine.rs +++ b/crates/uv-settings/src/combine.rs @@ -4,8 +4,8 @@ use std::path::PathBuf; use url::Url; use uv_configuration::{ - ConfigSettings, ExportFormat, IndexStrategy, KeyringProviderType, RequiredVersion, - TargetTriple, TrustedPublishing, + ConfigSettings, ExportFormat, IndexStrategy, KeyringProviderType, PackageConfigSettings, + RequiredVersion, TargetTriple, TrustedPublishing, }; use uv_distribution_types::{Index, IndexUrl, PipExtraIndex, PipFindLinks, PipIndex}; use uv_install_wheel::LinkMode; @@ -131,6 +131,17 @@ impl Combine for Option { } } +impl Combine for Option { + /// Combine two maps by merging the map in `self` with the map in `other`, if they're both + /// `Some`. + fn combine(self, other: Option) -> Option { + match (self, other) { + (Some(a), Some(b)) => Some(a.merge(b)), + (a, b) => a.or(b), + } + } +} + impl Combine for serde::de::IgnoredAny { fn combine(self, _other: Self) -> Self { self diff --git a/crates/uv-settings/src/settings.rs b/crates/uv-settings/src/settings.rs index e057cb40a..9eb765a1e 100644 --- a/crates/uv-settings/src/settings.rs +++ b/crates/uv-settings/src/settings.rs @@ -4,8 +4,8 @@ use serde::{Deserialize, Serialize}; use uv_cache_info::CacheKey; use uv_configuration::{ - ConfigSettings, IndexStrategy, KeyringProviderType, PackageNameSpecifier, RequiredVersion, - TargetTriple, TrustedHost, TrustedPublishing, + ConfigSettings, IndexStrategy, KeyringProviderType, PackageConfigSettings, + PackageNameSpecifier, RequiredVersion, TargetTriple, TrustedHost, TrustedPublishing, }; use uv_distribution_types::{ Index, IndexUrl, IndexUrlError, PipExtraIndex, PipFindLinks, PipIndex, StaticMetadata, @@ -361,6 +361,7 @@ pub struct ResolverOptions { pub fork_strategy: Option, pub dependency_metadata: Option>, pub config_settings: Option, + pub config_settings_package: Option, pub exclude_newer: Option, pub link_mode: Option, pub upgrade: Option, @@ -587,6 +588,18 @@ pub struct ResolverInstallerOptions { "# )] pub config_settings: Option, + /// Settings to pass to the [PEP 517](https://peps.python.org/pep-0517/) build backend for specific packages, + /// specified as `KEY=VALUE` pairs. + /// + /// Accepts a map from package names to string key-value pairs. + #[option( + default = "{}", + value_type = "dict", + example = r#" + config-settings-package = { numpy = { editable_mode = "compat" } } + "# + )] + pub config_settings_package: Option, /// Disable isolation when building source distributions. /// /// Assumes that build dependencies specified by [PEP 518](https://peps.python.org/pep-0518/) @@ -1333,6 +1346,16 @@ pub struct PipOptions { "# )] pub config_settings: Option, + /// Settings to pass to the [PEP 517](https://peps.python.org/pep-0517/) build backend for specific packages, + /// specified as `KEY=VALUE` pairs. + #[option( + default = "{}", + value_type = "dict", + example = r#" + config-settings-package = { numpy = { editable_mode = "compat" } } + "# + )] + pub config_settings_package: Option, /// The minimum Python version that should be supported by the resolved requirements (e.g., /// `3.8` or `3.8.17`). /// @@ -1651,6 +1674,7 @@ impl From for ResolverOptions { fork_strategy: value.fork_strategy, dependency_metadata: value.dependency_metadata, config_settings: value.config_settings, + config_settings_package: value.config_settings_package, exclude_newer: value.exclude_newer, link_mode: value.link_mode, upgrade: value.upgrade, @@ -1714,6 +1738,7 @@ pub struct ToolOptions { pub fork_strategy: Option, pub dependency_metadata: Option>, pub config_settings: Option, + pub config_settings_package: Option, pub no_build_isolation: Option, pub no_build_isolation_package: Option>, pub exclude_newer: Option, @@ -1741,6 +1766,7 @@ impl From for ToolOptions { fork_strategy: value.fork_strategy, dependency_metadata: value.dependency_metadata, config_settings: value.config_settings, + config_settings_package: value.config_settings_package, no_build_isolation: value.no_build_isolation, no_build_isolation_package: value.no_build_isolation_package, exclude_newer: value.exclude_newer, @@ -1770,6 +1796,7 @@ impl From for ResolverInstallerOptions { fork_strategy: value.fork_strategy, dependency_metadata: value.dependency_metadata, config_settings: value.config_settings, + config_settings_package: value.config_settings_package, no_build_isolation: value.no_build_isolation, no_build_isolation_package: value.no_build_isolation_package, exclude_newer: value.exclude_newer, @@ -1822,6 +1849,7 @@ pub struct OptionsWire { fork_strategy: Option, dependency_metadata: Option>, config_settings: Option, + config_settings_package: Option, no_build_isolation: Option, no_build_isolation_package: Option>, exclude_newer: Option, @@ -1911,6 +1939,7 @@ impl From for Options { fork_strategy, dependency_metadata, config_settings, + config_settings_package, no_build_isolation, no_build_isolation_package, exclude_newer, @@ -1977,6 +2006,7 @@ impl From for Options { fork_strategy, dependency_metadata, config_settings, + config_settings_package, no_build_isolation, no_build_isolation_package, exclude_newer, diff --git a/crates/uv-types/src/traits.rs b/crates/uv-types/src/traits.rs index a95367fef..e3f4ee012 100644 --- a/crates/uv-types/src/traits.rs +++ b/crates/uv-types/src/traits.rs @@ -7,7 +7,9 @@ use anyhow::Result; use rustc_hash::FxHashSet; use uv_cache::Cache; -use uv_configuration::{BuildKind, BuildOptions, BuildOutput, ConfigSettings, SourceStrategy}; +use uv_configuration::{ + BuildKind, BuildOptions, BuildOutput, ConfigSettings, PackageConfigSettings, SourceStrategy, +}; use uv_distribution_filename::DistFilename; use uv_distribution_types::{ CachedDist, DependencyMetadata, DistributionId, IndexCapabilities, IndexLocations, @@ -87,6 +89,9 @@ pub trait BuildContext { /// The [`ConfigSettings`] used to build distributions. fn config_settings(&self) -> &ConfigSettings; + /// The [`ConfigSettings`] used to build a specific package. + fn config_settings_package(&self) -> &PackageConfigSettings; + /// Whether to incorporate `tool.uv.sources` when resolving requirements. fn sources(&self) -> SourceStrategy; diff --git a/crates/uv/src/commands/build_frontend.rs b/crates/uv/src/commands/build_frontend.rs index a830f7aef..b3f9e5c89 100644 --- a/crates/uv/src/commands/build_frontend.rs +++ b/crates/uv/src/commands/build_frontend.rs @@ -16,7 +16,7 @@ use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{ BuildKind, BuildOptions, BuildOutput, Concurrency, ConfigSettings, Constraints, DependencyGroupsWithDefaults, HashCheckingMode, IndexStrategy, KeyringProviderType, - PreviewMode, SourceStrategy, + PackageConfigSettings, PreviewMode, SourceStrategy, }; use uv_dispatch::{BuildDispatch, SharedState}; use uv_distribution_filename::{ @@ -197,6 +197,7 @@ async fn build_impl( fork_strategy: _, dependency_metadata, config_setting, + config_settings_package, no_build_isolation, no_build_isolation_package, exclude_newer, @@ -357,6 +358,7 @@ async fn build_impl( dependency_metadata, *link_mode, config_setting, + config_settings_package, preview, ); async { @@ -434,6 +436,7 @@ async fn build_package( dependency_metadata: &DependencyMetadata, link_mode: LinkMode, config_setting: &ConfigSettings, + config_settings_package: &PackageConfigSettings, preview: PreviewMode, ) -> Result, Error> { let output_dir = if let Some(output_dir) = output_dir { @@ -568,6 +571,7 @@ async fn build_package( state.clone(), index_strategy, config_setting, + config_settings_package, build_isolation, link_mode, build_options, diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs index c40716763..a5116327b 100644 --- a/crates/uv/src/commands/pip/compile.rs +++ b/crates/uv/src/commands/pip/compile.rs @@ -14,7 +14,8 @@ use uv_cache::Cache; use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{ BuildOptions, Concurrency, ConfigSettings, Constraints, ExportFormat, ExtrasSpecification, - IndexStrategy, NoBinary, NoBuild, PreviewMode, Reinstall, SourceStrategy, Upgrade, + IndexStrategy, NoBinary, NoBuild, PackageConfigSettings, PreviewMode, Reinstall, + SourceStrategy, Upgrade, }; use uv_configuration::{KeyringProviderType, TargetTriple}; use uv_dispatch::{BuildDispatch, SharedState}; @@ -90,6 +91,7 @@ pub(crate) async fn pip_compile( keyring_provider: KeyringProviderType, network_settings: &NetworkSettings, config_settings: ConfigSettings, + config_settings_package: PackageConfigSettings, no_build_isolation: bool, no_build_isolation_package: Vec, build_options: BuildOptions, @@ -477,6 +479,7 @@ pub(crate) async fn pip_compile( state, index_strategy, &config_settings, + &config_settings_package, build_isolation, link_mode, &build_options, diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs index bbfe99c50..79e18bd98 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -11,7 +11,8 @@ use uv_cache::Cache; use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{ BuildOptions, Concurrency, ConfigSettings, Constraints, DryRun, ExtrasSpecification, - HashCheckingMode, IndexStrategy, PreviewMode, Reinstall, SourceStrategy, Upgrade, + HashCheckingMode, IndexStrategy, PackageConfigSettings, PreviewMode, Reinstall, SourceStrategy, + Upgrade, }; use uv_configuration::{KeyringProviderType, TargetTriple}; use uv_dispatch::{BuildDispatch, SharedState}; @@ -75,6 +76,7 @@ pub(crate) async fn pip_install( hash_checking: Option, installer_metadata: bool, config_settings: &ConfigSettings, + config_settings_package: &PackageConfigSettings, no_build_isolation: bool, no_build_isolation_package: Vec, build_options: BuildOptions, @@ -422,6 +424,7 @@ pub(crate) async fn pip_install( state.clone(), index_strategy, config_settings, + config_settings_package, build_isolation, link_mode, &build_options, @@ -513,6 +516,7 @@ pub(crate) async fn pip_install( compile, &index_locations, config_settings, + config_settings_package, &hasher, &tags, &client, diff --git a/crates/uv/src/commands/pip/operations.rs b/crates/uv/src/commands/pip/operations.rs index 55ab2aa1b..117321c14 100644 --- a/crates/uv/src/commands/pip/operations.rs +++ b/crates/uv/src/commands/pip/operations.rs @@ -13,7 +13,7 @@ use uv_cache::Cache; use uv_client::{BaseClientBuilder, RegistryClient}; use uv_configuration::{ BuildOptions, Concurrency, ConfigSettings, Constraints, DependencyGroups, DryRun, - ExtrasSpecification, Overrides, Reinstall, Upgrade, + ExtrasSpecification, Overrides, PackageConfigSettings, Reinstall, Upgrade, }; use uv_dispatch::BuildDispatch; use uv_distribution::{DistributionDatabase, SourcedDependencyGroups}; @@ -445,6 +445,7 @@ pub(crate) async fn install( compile: bool, index_urls: &IndexLocations, config_settings: &ConfigSettings, + config_settings_package: &PackageConfigSettings, hasher: &HashStrategy, tags: &Tags, client: &RegistryClient, @@ -470,6 +471,7 @@ pub(crate) async fn install( hasher, index_urls, config_settings, + config_settings_package, cache, venv, tags, diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index 6858ddad0..61999825e 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -9,7 +9,8 @@ use uv_cache::Cache; use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{ BuildOptions, Concurrency, ConfigSettings, Constraints, DryRun, ExtrasSpecification, - HashCheckingMode, IndexStrategy, PreviewMode, Reinstall, SourceStrategy, Upgrade, + HashCheckingMode, IndexStrategy, PackageConfigSettings, PreviewMode, Reinstall, SourceStrategy, + Upgrade, }; use uv_configuration::{KeyringProviderType, TargetTriple}; use uv_dispatch::{BuildDispatch, SharedState}; @@ -60,6 +61,7 @@ pub(crate) async fn pip_sync( allow_empty_requirements: bool, installer_metadata: bool, config_settings: &ConfigSettings, + config_settings_package: &PackageConfigSettings, no_build_isolation: bool, no_build_isolation_package: Vec, build_options: BuildOptions, @@ -355,6 +357,7 @@ pub(crate) async fn pip_sync( state.clone(), index_strategy, config_settings, + config_settings_package, build_isolation, link_mode, &build_options, @@ -448,6 +451,7 @@ pub(crate) async fn pip_sync( compile, &index_locations, config_settings, + config_settings_package, &hasher, &tags, &client, diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index 28cc2dcd5..12535f859 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -436,6 +436,7 @@ pub(crate) async fn add( state.clone().into_inner(), settings.resolver.index_strategy, &settings.resolver.config_setting, + &settings.resolver.config_settings_package, build_isolation, settings.resolver.link_mode, &settings.resolver.build_options, diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index e23bd97c2..706c86593 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -432,6 +432,7 @@ async fn do_lock( fork_strategy, dependency_metadata, config_setting, + config_settings_package, no_build_isolation, no_build_isolation_package, exclude_newer, @@ -674,6 +675,7 @@ async fn do_lock( state.fork().into_inner(), *index_strategy, config_setting, + config_settings_package, build_isolation, *link_mode, build_options, diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index cce02a70b..becd2a26e 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -1674,6 +1674,7 @@ pub(crate) async fn resolve_names( ResolverSettings { build_options, config_setting, + config_settings_package, dependency_metadata, exclude_newer, fork_strategy: _, @@ -1742,6 +1743,7 @@ pub(crate) async fn resolve_names( state.clone(), *index_strategy, config_setting, + config_settings_package, build_isolation, *link_mode, build_options, @@ -1832,6 +1834,7 @@ pub(crate) async fn resolve_environment( fork_strategy, dependency_metadata, config_setting, + config_settings_package, no_build_isolation, no_build_isolation_package, exclude_newer, @@ -1948,6 +1951,7 @@ pub(crate) async fn resolve_environment( state.clone().into_inner(), *index_strategy, config_setting, + config_settings_package, build_isolation, *link_mode, build_options, @@ -2013,6 +2017,7 @@ pub(crate) async fn sync_environment( keyring_provider, dependency_metadata, config_setting, + config_settings_package, no_build_isolation, no_build_isolation_package, exclude_newer, @@ -2084,6 +2089,7 @@ pub(crate) async fn sync_environment( state.clone().into_inner(), index_strategy, config_setting, + config_settings_package, build_isolation, link_mode, build_options, @@ -2106,6 +2112,7 @@ pub(crate) async fn sync_environment( compile_bytecode, index_locations, config_setting, + config_settings_package, &hasher, tags, &client, @@ -2169,6 +2176,7 @@ pub(crate) async fn update_environment( ResolverSettings { build_options, config_setting, + config_settings_package, dependency_metadata, exclude_newer, fork_strategy, @@ -2305,6 +2313,7 @@ pub(crate) async fn update_environment( state.clone(), *index_strategy, config_setting, + config_settings_package, build_isolation, *link_mode, build_options, @@ -2362,6 +2371,7 @@ pub(crate) async fn update_environment( *compile_bytecode, index_locations, config_setting, + config_settings_package, &hasher, tags, &client, diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index 8d2dd9629..adf3b61f2 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -573,6 +573,7 @@ pub(super) async fn do_sync( keyring_provider, dependency_metadata, config_setting, + config_settings_package, no_build_isolation, no_build_isolation_package, exclude_newer, @@ -709,6 +710,7 @@ pub(super) async fn do_sync( state.clone().into_inner(), index_strategy, config_setting, + config_settings_package, build_isolation, link_mode, build_options, @@ -733,6 +735,7 @@ pub(super) async fn do_sync( compile_bytecode, index_locations, config_setting, + config_settings_package, &hasher, &tags, &client, diff --git a/crates/uv/src/commands/project/tree.rs b/crates/uv/src/commands/project/tree.rs index cd1339d3e..756820dc7 100644 --- a/crates/uv/src/commands/project/tree.rs +++ b/crates/uv/src/commands/project/tree.rs @@ -200,6 +200,7 @@ pub(crate) async fn tree( fork_strategy: _, dependency_metadata: _, config_setting: _, + config_settings_package: _, no_build_isolation: _, no_build_isolation_package: _, exclude_newer: _, diff --git a/crates/uv/src/commands/venv.rs b/crates/uv/src/commands/venv.rs index 92eb1ead7..9d3b87fe1 100644 --- a/crates/uv/src/commands/venv.rs +++ b/crates/uv/src/commands/venv.rs @@ -12,7 +12,7 @@ use uv_cache::Cache; use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{ BuildOptions, Concurrency, ConfigSettings, Constraints, DependencyGroups, IndexStrategy, - KeyringProviderType, NoBinary, NoBuild, PreviewMode, SourceStrategy, + KeyringProviderType, NoBinary, NoBuild, PackageConfigSettings, PreviewMode, SourceStrategy, }; use uv_dispatch::{BuildDispatch, SharedState}; use uv_distribution_types::Requirement; @@ -269,6 +269,7 @@ pub(crate) async fn venv( let build_constraints = Constraints::default(); let build_hasher = HashStrategy::default(); let config_settings = ConfigSettings::default(); + let config_settings_package = PackageConfigSettings::default(); let sources = SourceStrategy::Disabled; // Do not allow builds @@ -286,6 +287,7 @@ pub(crate) async fn venv( state.clone(), index_strategy, &config_settings, + &config_settings_package, BuildIsolation::Isolated, link_mode, &build_options, diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 9c9b41065..0f6c9465f 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -524,6 +524,7 @@ async fn run(mut cli: Cli) -> Result { args.settings.keyring_provider, &globals.network_settings, args.settings.config_setting, + args.settings.config_settings_package, args.settings.no_build_isolation, args.settings.no_build_isolation_package, args.settings.build_options, @@ -594,6 +595,7 @@ async fn run(mut cli: Cli) -> Result { args.settings.allow_empty_requirements, globals.installer_metadata, &args.settings.config_setting, + &args.settings.config_settings_package, args.settings.no_build_isolation, args.settings.no_build_isolation_package, args.settings.build_options, @@ -745,6 +747,7 @@ async fn run(mut cli: Cli) -> Result { args.settings.hash_checking, globals.installer_metadata, &args.settings.config_setting, + &args.settings.config_settings_package, args.settings.no_build_isolation, args.settings.no_build_isolation_package, args.settings.build_options, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 1ebeecba8..aa105cf97 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -23,9 +23,9 @@ use uv_client::Connectivity; use uv_configuration::{ BuildOptions, Concurrency, ConfigSettings, DependencyGroups, DryRun, EditableMode, ExportFormat, ExtrasSpecification, HashCheckingMode, IndexStrategy, InstallOptions, - KeyringProviderType, NoBinary, NoBuild, PreviewMode, ProjectBuildBackend, Reinstall, - RequiredVersion, SourceStrategy, TargetTriple, TrustedHost, TrustedPublishing, Upgrade, - VersionControlSystem, + KeyringProviderType, NoBinary, NoBuild, PackageConfigSettings, PreviewMode, + ProjectBuildBackend, Reinstall, RequiredVersion, SourceStrategy, TargetTriple, TrustedHost, + TrustedPublishing, Upgrade, VersionControlSystem, }; use uv_distribution_types::{DependencyMetadata, Index, IndexLocations, IndexUrl, Requirement}; use uv_install_wheel::LinkMode; @@ -712,6 +712,7 @@ impl ToolUpgradeSettings { pre, fork_strategy, config_setting, + config_setting_package: config_settings_package, no_build_isolation, no_build_isolation_package, build_isolation, @@ -746,6 +747,7 @@ impl ToolUpgradeSettings { pre, fork_strategy, config_setting, + config_settings_package, no_build_isolation, no_build_isolation_package, build_isolation, @@ -2694,6 +2696,7 @@ pub(crate) struct InstallerSettingsRef<'a> { pub(crate) keyring_provider: KeyringProviderType, pub(crate) dependency_metadata: &'a DependencyMetadata, pub(crate) config_setting: &'a ConfigSettings, + pub(crate) config_settings_package: &'a PackageConfigSettings, pub(crate) no_build_isolation: bool, pub(crate) no_build_isolation_package: &'a [PackageName], pub(crate) exclude_newer: Option, @@ -2712,6 +2715,7 @@ pub(crate) struct InstallerSettingsRef<'a> { pub(crate) struct ResolverSettings { pub(crate) build_options: BuildOptions, pub(crate) config_setting: ConfigSettings, + pub(crate) config_settings_package: PackageConfigSettings, pub(crate) dependency_metadata: DependencyMetadata, pub(crate) exclude_newer: Option, pub(crate) fork_strategy: ForkStrategy, @@ -2770,6 +2774,7 @@ impl From for ResolverSettings { index_strategy: value.index_strategy.unwrap_or_default(), keyring_provider: value.keyring_provider.unwrap_or_default(), config_setting: value.config_settings.unwrap_or_default(), + config_settings_package: value.config_settings_package.unwrap_or_default(), no_build_isolation: value.no_build_isolation.unwrap_or_default(), no_build_isolation_package: value.no_build_isolation_package.unwrap_or_default(), exclude_newer: value.exclude_newer, @@ -2849,6 +2854,7 @@ impl From for ResolverInstallerSettings { NoBuild::from_args(value.no_build, value.no_build_package.unwrap_or_default()), ), config_setting: value.config_settings.unwrap_or_default(), + config_settings_package: value.config_settings_package.unwrap_or_default(), dependency_metadata: DependencyMetadata::from_entries( value.dependency_metadata.into_iter().flatten(), ), @@ -2918,6 +2924,7 @@ pub(crate) struct PipSettings { pub(crate) custom_compile_command: Option, pub(crate) generate_hashes: bool, pub(crate) config_setting: ConfigSettings, + pub(crate) config_settings_package: PackageConfigSettings, pub(crate) python_version: Option, pub(crate) python_platform: Option, pub(crate) universal: bool, @@ -2987,6 +2994,7 @@ impl PipSettings { custom_compile_command, generate_hashes, config_settings, + config_settings_package, python_version, python_platform, universal, @@ -3022,6 +3030,7 @@ impl PipSettings { fork_strategy: top_level_fork_strategy, dependency_metadata: top_level_dependency_metadata, config_settings: top_level_config_settings, + config_settings_package: top_level_config_settings_package, no_build_isolation: top_level_no_build_isolation, no_build_isolation_package: top_level_no_build_isolation_package, exclude_newer: top_level_exclude_newer, @@ -3054,6 +3063,8 @@ impl PipSettings { let fork_strategy = fork_strategy.combine(top_level_fork_strategy); let dependency_metadata = dependency_metadata.combine(top_level_dependency_metadata); let config_settings = config_settings.combine(top_level_config_settings); + let config_settings_package = + config_settings_package.combine(top_level_config_settings_package); let no_build_isolation = no_build_isolation.combine(top_level_no_build_isolation); let no_build_isolation_package = no_build_isolation_package.combine(top_level_no_build_isolation_package); @@ -3156,6 +3167,10 @@ impl PipSettings { .config_settings .combine(config_settings) .unwrap_or_default(), + config_settings_package: args + .config_settings_package + .combine(config_settings_package) + .unwrap_or_default(), torch_backend: args.torch_backend.combine(torch_backend), python_version: args.python_version.combine(python_version), python_platform: args.python_platform.combine(python_platform), @@ -3249,6 +3264,7 @@ impl<'a> From<&'a ResolverInstallerSettings> for InstallerSettingsRef<'a> { keyring_provider: settings.resolver.keyring_provider, dependency_metadata: &settings.resolver.dependency_metadata, config_setting: &settings.resolver.config_setting, + config_settings_package: &settings.resolver.config_settings_package, no_build_isolation: settings.resolver.no_build_isolation, no_build_isolation_package: &settings.resolver.no_build_isolation_package, exclude_newer: settings.resolver.exclude_newer, diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index 2a7b0f404..a977ac813 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -4054,13 +4054,13 @@ fn config_settings_path() -> Result<()> { "### ); - // When installed without `--editable_mode=compat`, the `finder.py` file should be present. + // When installed without `editable_mode=compat`, the `finder.py` file should be present. let finder = context .site_packages() .join("__editable___setuptools_editable_0_1_0_finder.py"); assert!(finder.exists()); - // Reinstalling with `--editable_mode=compat` should be a no-op; changes in build configuration + // Reinstalling with `editable_mode=compat` should be a no-op; changes in build configuration // don't invalidate the environment. uv_snapshot!(context.filters(), context.pip_install() .arg("-r") @@ -4089,7 +4089,7 @@ fn config_settings_path() -> Result<()> { - setuptools-editable==0.1.0 (from file://[WORKSPACE]/scripts/packages/setuptools_editable) "###); - // Install the editable package with `--editable_mode=compat`. We should ignore the cached + // Install the editable package with `editable_mode=compat`. We should ignore the cached // build configuration and rebuild. uv_snapshot!(context.filters(), context.pip_install() .arg("-r") @@ -4109,7 +4109,7 @@ fn config_settings_path() -> Result<()> { "### ); - // When installed without `--editable_mode=compat`, the `finder.py` file should _not_ be present. + // When installed without `editable_mode=compat`, the `finder.py` file should _not_ be present. let finder = context .site_packages() .join("__editable___setuptools_editable_0_1_0_finder.py"); @@ -11739,3 +11739,114 @@ fn install_python_preference() { Audited 1 package in [TIME] "); } + +#[test] +fn config_settings_package() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str(&format!( + "-e {}", + context + .workspace_root + .join("scripts/packages/setuptools_editable") + .display() + ))?; + + // Install the editable package. + uv_snapshot!(context.filters(), context.pip_install() + .arg("-r") + .arg("requirements.txt"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + Prepared 2 packages in [TIME] + Installed 2 packages in [TIME] + + iniconfig==2.0.0 + + setuptools-editable==0.1.0 (from file://[WORKSPACE]/scripts/packages/setuptools_editable) + "### + ); + + // When installed without `editable_mode=compat`, the `finder.py` file should be present. + let finder = context + .site_packages() + .join("__editable___setuptools_editable_0_1_0_finder.py"); + assert!(finder.exists()); + + // Uninstall the package. + uv_snapshot!(context.filters(), context.pip_uninstall() + .arg("setuptools-editable"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Uninstalled 1 package in [TIME] + - setuptools-editable==0.1.0 (from file://[WORKSPACE]/scripts/packages/setuptools_editable) + "###); + + // Install the editable package with `editable_mode=compat`, scoped to the package. + uv_snapshot!(context.filters(), context.pip_install() + .arg("-r") + .arg("requirements.txt") + .arg("--config-settings-package") + .arg("setuptools-editable:editable_mode=compat"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + setuptools-editable==0.1.0 (from file://[WORKSPACE]/scripts/packages/setuptools_editable) + " + ); + + // When installed with `editable_mode=compat`, the `finder.py` file should _not_ be present. + let finder = context + .site_packages() + .join("__editable___setuptools_editable_0_1_0_finder.py"); + assert!(!finder.exists()); + + // Uninstall the package. + uv_snapshot!(context.filters(), context.pip_uninstall() + .arg("setuptools-editable"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Uninstalled 1 package in [TIME] + - setuptools-editable==0.1.0 (from file://[WORKSPACE]/scripts/packages/setuptools_editable) + "###); + + // Install the editable package with `editable_mode=compat`, by scoped to a different package. + uv_snapshot!(context.filters(), context.pip_install() + .arg("-r") + .arg("requirements.txt") + .arg("--config-settings-package") + .arg("setuptools:editable_mode=compat") + , @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + Installed 1 package in [TIME] + + setuptools-editable==0.1.0 (from file://[WORKSPACE]/scripts/packages/setuptools_editable) + " + ); + + // When installed without `editable_mode=compat`, the `finder.py` file should be present. + let finder = context + .site_packages() + .join("__editable___setuptools_editable_0_1_0_finder.py"); + assert!(finder.exists()); + + Ok(()) +} diff --git a/crates/uv/tests/it/show_settings.rs b/crates/uv/tests/it/show_settings.rs index 2637af8ac..500e78965 100644 --- a/crates/uv/tests/it/show_settings.rs +++ b/crates/uv/tests/it/show_settings.rs @@ -203,6 +203,9 @@ fn resolve_uv_toml() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), python_version: None, python_platform: None, universal: false, @@ -385,6 +388,9 @@ fn resolve_uv_toml() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), python_version: None, python_platform: None, universal: false, @@ -568,6 +574,9 @@ fn resolve_uv_toml() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), python_version: None, python_platform: None, universal: false, @@ -783,6 +792,9 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), python_version: None, python_platform: None, universal: false, @@ -933,6 +945,9 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), python_version: None, python_platform: None, universal: false, @@ -1127,6 +1142,9 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), python_version: None, python_platform: Some( X8664UnknownLinuxGnu, @@ -1369,6 +1387,9 @@ fn resolve_index_url() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), python_version: None, python_platform: None, universal: false, @@ -1621,6 +1642,9 @@ fn resolve_index_url() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), python_version: None, python_platform: None, universal: false, @@ -1828,6 +1852,9 @@ fn resolve_find_links() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), python_version: None, python_platform: None, universal: false, @@ -2000,6 +2027,9 @@ fn resolve_top_level() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), python_version: None, python_platform: None, universal: false, @@ -2232,6 +2262,9 @@ fn resolve_top_level() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), python_version: None, python_platform: None, universal: false, @@ -2447,6 +2480,9 @@ fn resolve_top_level() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), python_version: None, python_platform: None, universal: false, @@ -2618,6 +2654,9 @@ fn resolve_user_configuration() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), python_version: None, python_platform: None, universal: false, @@ -2773,6 +2812,9 @@ fn resolve_user_configuration() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), python_version: None, python_platform: None, universal: false, @@ -2928,6 +2970,9 @@ fn resolve_user_configuration() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), python_version: None, python_platform: None, universal: false, @@ -3085,6 +3130,9 @@ fn resolve_user_configuration() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), python_version: None, python_platform: None, universal: false, @@ -3208,6 +3256,7 @@ fn resolve_tool() -> anyhow::Result<()> { fork_strategy: None, dependency_metadata: None, config_settings: None, + config_settings_package: None, no_build_isolation: None, no_build_isolation_package: None, exclude_newer: None, @@ -3234,6 +3283,9 @@ fn resolve_tool() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), dependency_metadata: DependencyMetadata( {}, ), @@ -3426,6 +3478,9 @@ fn resolve_poetry_toml() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), python_version: None, python_platform: None, universal: false, @@ -3643,6 +3698,9 @@ fn resolve_both() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), python_version: None, python_platform: None, universal: false, @@ -3950,6 +4008,9 @@ fn resolve_config_file() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), python_version: None, python_platform: None, universal: false, @@ -4004,7 +4065,7 @@ fn resolve_config_file() -> anyhow::Result<()> { | 1 | [project] | ^^^^^^^ - unknown field `project`, expected one of `required-version`, `native-tls`, `offline`, `no-cache`, `cache-dir`, `preview`, `python-preference`, `python-downloads`, `concurrent-downloads`, `concurrent-builds`, `concurrent-installs`, `index`, `index-url`, `extra-index-url`, `no-index`, `find-links`, `index-strategy`, `keyring-provider`, `allow-insecure-host`, `resolution`, `prerelease`, `fork-strategy`, `dependency-metadata`, `config-settings`, `no-build-isolation`, `no-build-isolation-package`, `exclude-newer`, `link-mode`, `compile-bytecode`, `no-sources`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `python-install-mirror`, `pypy-install-mirror`, `python-downloads-json-url`, `publish-url`, `trusted-publishing`, `check-url`, `add-bounds`, `pip`, `cache-keys`, `override-dependencies`, `constraint-dependencies`, `build-constraint-dependencies`, `environments`, `required-environments`, `conflicts`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dependency-groups`, `dev-dependencies`, `build-backend` + unknown field `project`, expected one of `required-version`, `native-tls`, `offline`, `no-cache`, `cache-dir`, `preview`, `python-preference`, `python-downloads`, `concurrent-downloads`, `concurrent-builds`, `concurrent-installs`, `index`, `index-url`, `extra-index-url`, `no-index`, `find-links`, `index-strategy`, `keyring-provider`, `allow-insecure-host`, `resolution`, `prerelease`, `fork-strategy`, `dependency-metadata`, `config-settings`, `config-settings-package`, `no-build-isolation`, `no-build-isolation-package`, `exclude-newer`, `link-mode`, `compile-bytecode`, `no-sources`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `python-install-mirror`, `pypy-install-mirror`, `python-downloads-json-url`, `publish-url`, `trusted-publishing`, `check-url`, `add-bounds`, `pip`, `cache-keys`, `override-dependencies`, `constraint-dependencies`, `build-constraint-dependencies`, `environments`, `required-environments`, `conflicts`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dependency-groups`, `dev-dependencies`, `build-backend` " ); @@ -4199,6 +4260,9 @@ fn resolve_skip_empty() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), python_version: None, python_platform: None, universal: false, @@ -4357,6 +4421,9 @@ fn resolve_skip_empty() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), python_version: None, python_platform: None, universal: false, @@ -4534,6 +4601,9 @@ fn allow_insecure_host() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), python_version: None, python_platform: None, universal: false, @@ -4772,6 +4842,9 @@ fn index_priority() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), python_version: None, python_platform: None, universal: false, @@ -4989,6 +5062,9 @@ fn index_priority() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), python_version: None, python_platform: None, universal: false, @@ -5212,6 +5288,9 @@ fn index_priority() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), python_version: None, python_platform: None, universal: false, @@ -5430,6 +5509,9 @@ fn index_priority() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), python_version: None, python_platform: None, universal: false, @@ -5655,6 +5737,9 @@ fn index_priority() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), python_version: None, python_platform: None, universal: false, @@ -5873,6 +5958,9 @@ fn index_priority() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), python_version: None, python_platform: None, universal: false, @@ -6035,6 +6123,9 @@ fn verify_hashes() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), python_version: None, python_platform: None, universal: false, @@ -6183,6 +6274,9 @@ fn verify_hashes() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), python_version: None, python_platform: None, universal: false, @@ -6329,6 +6423,9 @@ fn verify_hashes() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), python_version: None, python_platform: None, universal: false, @@ -6477,6 +6574,9 @@ fn verify_hashes() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), python_version: None, python_platform: None, universal: false, @@ -6623,6 +6723,9 @@ fn verify_hashes() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), python_version: None, python_platform: None, universal: false, @@ -6770,6 +6873,9 @@ fn verify_hashes() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), python_version: None, python_platform: None, universal: false, diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index 5a8d79447..16c4d673a 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -11126,3 +11126,146 @@ fn sync_python_preference() -> Result<()> { Ok(()) } + +#[test] +fn sync_config_settings_package() -> Result<()> { + let context = TestContext::new("3.12").with_exclude_newer("2025-07-25T00:00:00Z"); + + // Create a child project that uses `setuptools`. + let dependency = context.temp_dir.child("dependency"); + dependency.child("pyproject.toml").write_str( + r#" + [project] + name = "dependency" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + [build-system] + requires = ["setuptools>=42"] + build-backend = "setuptools.build_meta" + "#, + )?; + dependency + .child("dependency") + .child("__init__.py") + .touch()?; + + // Install the `dependency` without `editable_mode=compat`. + 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 = ["dependency"] + + [tool.uv.sources] + dependency = { path = "dependency", editable = true } + "#, + )?; + + // Lock the project + context.lock().assert().success(); + + uv_snapshot!(context.filters(), context.sync(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + dependency==0.1.0 (from file://[TEMP_DIR]/dependency) + "); + + // When installed without `editable_mode=compat`, the `finder.py` file should be present. + let finder = context + .site_packages() + .join("__editable___dependency_0_1_0_finder.py"); + assert!(finder.exists()); + + // Remove the virtual environment. + fs_err::remove_dir_all(&context.venv)?; + + // Install the `dependency` with `editable_mode=compat` scoped to the package. + 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 = ["dependency"] + + [tool.uv.sources] + dependency = { path = "dependency", editable = true } + + [tool.uv.config-settings-package] + dependency = { editable_mode = "compat" } + "#, + )?; + + uv_snapshot!(context.filters(), context.sync(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + Creating virtual environment at: .venv + Resolved 2 packages in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + dependency==0.1.0 (from file://[TEMP_DIR]/dependency) + "); + + // When installed with `editable_mode=compat`, the `finder.py` file should _not_ be present. + let finder = context + .site_packages() + .join("__editable___dependency_0_1_0_finder.py"); + assert!(!finder.exists()); + + // Remove the virtual environment. + fs_err::remove_dir_all(&context.venv)?; + + // Install the `dependency` with `editable_mode=compat` scoped to another package. + 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 = ["dependency"] + + [tool.uv.sources] + dependency = { path = "dependency", editable = true } + + [tool.uv.config-settings-package] + setuptools = { editable_mode = "compat" } + "#, + )?; + + uv_snapshot!(context.filters(), context.sync(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + Creating virtual environment at: .venv + Resolved 2 packages in [TIME] + Installed 1 package in [TIME] + + dependency==0.1.0 (from file://[TEMP_DIR]/dependency) + "); + + // When installed without `editable_mode=compat`, the `finder.py` file should be present. + let finder = context + .site_packages() + .join("__editable___dependency_0_1_0_finder.py"); + assert!(finder.exists()); + + Ok(()) +} diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 4fc832cdb..2ca95dce0 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -84,6 +84,7 @@ uv run [OPTIONS] [COMMAND]

      May also be set with the UV_COMPILE_BYTECODE environment variable.

    --config-file config-file

    The path to a uv.toml file to use for configuration.

    While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.

    May also be set with the UV_CONFIG_FILE environment variable.

    --config-setting, --config-settings, -C config-setting

    Settings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs

    +
    --config-settings-package, --config-settings-package config-settings-package

    Settings to pass to the PEP 517 build backend for a specific package, specified as PACKAGE:KEY=VALUE pairs

    --default-index default-index

    The URL of the default package index (by default: https://pypi.org/simple).

    Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.

    The index given by this flag is given lower priority than all other indexes specified via the --index flag.

    @@ -442,6 +443,7 @@ uv add [OPTIONS] >

    May also be set with the UV_COMPILE_BYTECODE environment variable.

    --config-file config-file

    The path to a uv.toml file to use for configuration.

    While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.

    May also be set with the UV_CONFIG_FILE environment variable.

    --config-setting, --config-settings, -C config-setting

    Settings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs

    +
    --config-settings-package, --config-settings-package config-settings-package

    Settings to pass to the PEP 517 build backend for a specific package, specified as PACKAGE:KEY=VALUE pairs

    --constraints, --constraint, -c constraints

    Constrain versions using the given requirements files.

    Constraints files are requirements.txt-like files that only control the version of a requirement that's installed. The constraints will not be added to the project's pyproject.toml file, but will be respected during dependency resolution.

    This is equivalent to pip's --constraint option.

    @@ -639,6 +641,7 @@ uv remove [OPTIONS] ...

    May also be set with the UV_COMPILE_BYTECODE environment variable.

    --config-file config-file

    The path to a uv.toml file to use for configuration.

    While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.

    May also be set with the UV_CONFIG_FILE environment variable.

    --config-setting, --config-settings, -C config-setting

    Settings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs

    +
    --config-settings-package, --config-settings-package config-settings-package

    Settings to pass to the PEP 517 build backend for a specific package, specified as PACKAGE:KEY=VALUE pairs

    --default-index default-index

    The URL of the default package index (by default: https://pypi.org/simple).

    Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.

    The index given by this flag is given lower priority than all other indexes specified via the --index flag.

    @@ -817,6 +820,7 @@ uv version [OPTIONS] [VALUE]

    May also be set with the UV_COMPILE_BYTECODE environment variable.

    --config-file config-file

    The path to a uv.toml file to use for configuration.

    While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.

    May also be set with the UV_CONFIG_FILE environment variable.

    --config-setting, --config-settings, -C config-setting

    Settings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs

    +
    --config-settings-package, --config-settings-package config-settings-package

    Settings to pass to the PEP 517 build backend for a specific package, specified as PACKAGE:KEY=VALUE pairs

    --default-index default-index

    The URL of the default package index (by default: https://pypi.org/simple).

    Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.

    The index given by this flag is given lower priority than all other indexes specified via the --index flag.

    @@ -1001,6 +1005,7 @@ uv sync [OPTIONS]

    May also be set with the UV_COMPILE_BYTECODE environment variable.

    --config-file config-file

    The path to a uv.toml file to use for configuration.

    While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.

    May also be set with the UV_CONFIG_FILE environment variable.

    --config-setting, --config-settings, -C config-setting

    Settings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs

    +
    --config-settings-package, --config-settings-package config-settings-package

    Settings to pass to the PEP 517 build backend for a specific package, specified as PACKAGE:KEY=VALUE pairs

    --default-index default-index

    The URL of the default package index (by default: https://pypi.org/simple).

    Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.

    The index given by this flag is given lower priority than all other indexes specified via the --index flag.

    @@ -1248,6 +1253,7 @@ uv lock [OPTIONS]
    --config-file config-file

    The path to a uv.toml file to use for configuration.

    While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.

    May also be set with the UV_CONFIG_FILE environment variable.

    --config-setting, --config-settings, -C config-setting

    Settings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs

    +
    --config-settings-package, --config-settings-package config-settings-package

    Settings to pass to the PEP 517 build backend for a specific package, specified as PACKAGE:KEY=VALUE pairs

    --default-index default-index

    The URL of the default package index (by default: https://pypi.org/simple).

    Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.

    The index given by this flag is given lower priority than all other indexes specified via the --index flag.

    @@ -1411,6 +1417,7 @@ uv export [OPTIONS]
    --config-file config-file

    The path to a uv.toml file to use for configuration.

    While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.

    May also be set with the UV_CONFIG_FILE environment variable.

    --config-setting, --config-settings, -C config-setting

    Settings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs

    +
    --config-settings-package, --config-settings-package config-settings-package

    Settings to pass to the PEP 517 build backend for a specific package, specified as PACKAGE:KEY=VALUE pairs

    --default-index default-index

    The URL of the default package index (by default: https://pypi.org/simple).

    Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.

    The index given by this flag is given lower priority than all other indexes specified via the --index flag.

    @@ -1605,6 +1612,7 @@ uv tree [OPTIONS]
    --config-file config-file

    The path to a uv.toml file to use for configuration.

    While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.

    May also be set with the UV_CONFIG_FILE environment variable.

    --config-setting, --config-settings, -C config-setting

    Settings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs

    +
    --config-settings-package, --config-settings-package config-settings-package

    Settings to pass to the PEP 517 build backend for a specific package, specified as PACKAGE:KEY=VALUE pairs

    --default-index default-index

    The URL of the default package index (by default: https://pypi.org/simple).

    Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.

    The index given by this flag is given lower priority than all other indexes specified via the --index flag.

    @@ -1863,6 +1871,7 @@ uv tool run [OPTIONS] [COMMAND]

    May also be set with the UV_COMPILE_BYTECODE environment variable.

    --config-file config-file

    The path to a uv.toml file to use for configuration.

    While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.

    May also be set with the UV_CONFIG_FILE environment variable.

    --config-setting, --config-settings, -C config-setting

    Settings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs

    +
    --config-settings-package, --config-settings-package config-settings-package

    Settings to pass to the PEP 517 build backend for a specific package, specified as PACKAGE:KEY=VALUE pairs

    --constraints, --constraint, -c constraints

    Constrain versions using the given requirements files.

    Constraints files are requirements.txt-like files that only control the version of a requirement that's installed. However, including a package in a constraints file will not trigger the installation of that package.

    This is equivalent to pip's --constraint option.

    @@ -2035,6 +2044,7 @@ uv tool install [OPTIONS]

    May also be set with the UV_COMPILE_BYTECODE environment variable.

    --config-file config-file

    The path to a uv.toml file to use for configuration.

    While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.

    May also be set with the UV_CONFIG_FILE environment variable.

    --config-setting, --config-settings, -C config-setting

    Settings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs

    +
    --config-settings-package, --config-settings-package config-settings-package

    Settings to pass to the PEP 517 build backend for a specific package, specified as PACKAGE:KEY=VALUE pairs

    --constraints, --constraint, -c constraints

    Constrain versions using the given requirements files.

    Constraints files are requirements.txt-like files that only control the version of a requirement that's installed. However, including a package in a constraints file will not trigger the installation of that package.

    This is equivalent to pip's --constraint option.

    @@ -2202,6 +2212,7 @@ uv tool upgrade [OPTIONS] ...

    May also be set with the UV_COMPILE_BYTECODE environment variable.

    --config-file config-file

    The path to a uv.toml file to use for configuration.

    While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.

    May also be set with the UV_CONFIG_FILE environment variable.

    --config-setting, --config-settings, -C config-setting

    Settings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs

    +
    --config-setting-package, --config-settings-package config-setting-package

    Settings to pass to the PEP 517 build backend for a specific package, specified as PACKAGE:KEY=VALUE pairs

    --default-index default-index

    The URL of the default package index (by default: https://pypi.org/simple).

    Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.

    The index given by this flag is given lower priority than all other indexes specified via the --index flag.

    @@ -3345,6 +3356,7 @@ uv pip compile [OPTIONS] >
    --config-file config-file

    The path to a uv.toml file to use for configuration.

    While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.

    May also be set with the UV_CONFIG_FILE environment variable.

    --config-setting, --config-settings, -C config-setting

    Settings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs

    +
    --config-settings-package, --config-settings-package config-settings-package

    Settings to pass to the PEP 517 build backend for a specific package, specified as PACKAGE:KEY=VALUE pairs

    --constraints, --constraint, -c constraints

    Constrain versions using the given requirements files.

    Constraints files are requirements.txt-like files that only control the version of a requirement that's installed. However, including a package in a constraints file will not trigger the installation of that package.

    This is equivalent to pip's --constraint option.

    @@ -3650,6 +3662,7 @@ uv pip sync [OPTIONS] ...

    May also be set with the UV_COMPILE_BYTECODE environment variable.

    --config-file config-file

    The path to a uv.toml file to use for configuration.

    While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.

    May also be set with the UV_CONFIG_FILE environment variable.

    --config-setting, --config-settings, -C config-setting

    Settings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs

    +
    --config-settings-package, --config-settings-package config-settings-package

    Settings to pass to the PEP 517 build backend for a specific package, specified as PACKAGE:KEY=VALUE pairs

    --constraints, --constraint, -c constraints

    Constrain versions using the given requirements files.

    Constraints files are requirements.txt-like files that only control the version of a requirement that's installed. However, including a package in a constraints file will not trigger the installation of that package.

    This is equivalent to pip's --constraint option.

    @@ -3900,6 +3913,7 @@ uv pip install [OPTIONS] |--editable May also be set with the UV_COMPILE_BYTECODE environment variable.

    --config-file config-file

    The path to a uv.toml file to use for configuration.

    While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.

    May also be set with the UV_CONFIG_FILE environment variable.

    --config-setting, --config-settings, -C config-setting

    Settings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs

    +
    --config-settings-package, --config-settings-package config-settings-package

    Settings to pass to the PEP 517 build backend for a specific package, specified as PACKAGE:KEY=VALUE pairs

    --constraints, --constraint, -c constraints

    Constrain versions using the given requirements files.

    Constraints files are requirements.txt-like files that only control the version of a requirement that's installed. However, including a package in a constraints file will not trigger the installation of that package.

    This is equivalent to pip's --constraint option.

    @@ -4845,6 +4859,7 @@ uv build [OPTIONS] [SRC]
    --config-file config-file

    The path to a uv.toml file to use for configuration.

    While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.

    May also be set with the UV_CONFIG_FILE environment variable.

    --config-setting, --config-settings, -C config-setting

    Settings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs

    +
    --config-settings-package, --config-settings-package config-settings-package

    Settings to pass to the PEP 517 build backend for a specific package, specified as PACKAGE:KEY=VALUE pairs

    --default-index default-index

    The URL of the default package index (by default: https://pypi.org/simple).

    Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.

    The index given by this flag is given lower priority than all other indexes specified via the --index flag.

    diff --git a/docs/reference/settings.md b/docs/reference/settings.md index bdee1e4a1..55d3f8ae4 100644 --- a/docs/reference/settings.md +++ b/docs/reference/settings.md @@ -1006,6 +1006,33 @@ specified as `KEY=VALUE` pairs. --- +### [`config-settings-package`](#config-settings-package) {: #config-settings-package } + +Settings to pass to the [PEP 517](https://peps.python.org/pep-0517/) build backend for specific packages, +specified as `KEY=VALUE` pairs. + +Accepts a map from package names to string key-value pairs. + +**Default value**: `{}` + +**Type**: `dict` + +**Example usage**: + +=== "pyproject.toml" + + ```toml + [tool.uv] + config-settings-package = { numpy = { editable_mode = "compat" } } + ``` +=== "uv.toml" + + ```toml + config-settings-package = { numpy = { editable_mode = "compat" } } + ``` + +--- + ### [`dependency-metadata`](#dependency-metadata) {: #dependency-metadata } Pre-defined static metadata for dependencies of the project (direct or transitive). When @@ -2244,6 +2271,33 @@ specified as `KEY=VALUE` pairs. --- +#### [`config-settings-package`](#pip_config-settings-package) {: #pip_config-settings-package } + + +Settings to pass to the [PEP 517](https://peps.python.org/pep-0517/) build backend for specific packages, +specified as `KEY=VALUE` pairs. + +**Default value**: `{}` + +**Type**: `dict` + +**Example usage**: + +=== "pyproject.toml" + + ```toml + [tool.uv.pip] + config-settings-package = { numpy = { editable_mode = "compat" } } + ``` +=== "uv.toml" + + ```toml + [pip] + config-settings-package = { numpy = { editable_mode = "compat" } } + ``` + +--- + #### [`custom-compile-command`](#pip_custom-compile-command) {: #pip_custom-compile-command } diff --git a/uv.schema.json b/uv.schema.json index ba89f65f4..22b30cd06 100644 --- a/uv.schema.json +++ b/uv.schema.json @@ -119,6 +119,17 @@ } ] }, + "config-settings-package": { + "description": "Settings to pass to the [PEP 517](https://peps.python.org/pep-0517/) build backend for specific packages,\nspecified as `KEY=VALUE` pairs.\n\nAccepts a map from package names to string key-value pairs.", + "anyOf": [ + { + "$ref": "#/definitions/PackageConfigSettings" + }, + { + "type": "null" + } + ] + }, "conflicts": { "description": "A list of sets of conflicting groups or extras.", "anyOf": [ @@ -1104,6 +1115,13 @@ } ] }, + "PackageConfigSettings": { + "description": "Settings to pass to PEP 517 build backends on a per-package basis.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/ConfigSettings" + } + }, "PackageName": { "description": "The normalized name of a package.\n\nConverts the name to lowercase and collapses runs of `-`, `_`, and `.` down to a single `-`.\nFor example, `---`, `.`, and `__` are all converted to a single `-`.\n\nSee: ", "type": "string" @@ -1185,6 +1203,17 @@ } ] }, + "config-settings-package": { + "description": "Settings to pass to the [PEP 517](https://peps.python.org/pep-0517/) build backend for specific packages,\nspecified as `KEY=VALUE` pairs.", + "anyOf": [ + { + "$ref": "#/definitions/PackageConfigSettings" + }, + { + "type": "null" + } + ] + }, "custom-compile-command": { "description": "The header comment to include at the top of the output file generated by `uv pip compile`.\n\nUsed to reflect custom build scripts and commands that wrap `uv pip compile`.", "type": [ From bce2ea480d8efc3fb4640b759a1894d0eb760e4e Mon Sep 17 00:00:00 2001 From: konsti Date: Fri, 18 Jul 2025 12:50:04 +0200 Subject: [PATCH 263/349] Escape requires version for built_by_uv test (#14706) This keeps the hash stable across uv releases. Fixes #14695 --- Cargo.lock | 1 + crates/uv-build-backend/Cargo.toml | 1 + crates/uv-build-backend/src/lib.rs | 29 ++++++++++++++++++++--------- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 78429b08f..77dfad413 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4817,6 +4817,7 @@ dependencies = [ "indoc", "insta", "itertools 0.14.0", + "regex", "rustc-hash", "schemars", "serde", diff --git a/crates/uv-build-backend/Cargo.toml b/crates/uv-build-backend/Cargo.toml index 7714423d4..677cbc222 100644 --- a/crates/uv-build-backend/Cargo.toml +++ b/crates/uv-build-backend/Cargo.toml @@ -57,4 +57,5 @@ schemars = ["dep:schemars", "uv-pypi-types/schemars"] [dev-dependencies] indoc = { workspace = true } insta = { version = "1.40.0", features = ["filters"] } +regex = { workspace = true } tempfile = { workspace = true } diff --git a/crates/uv-build-backend/src/lib.rs b/crates/uv-build-backend/src/lib.rs index 8add8dda3..5e0efd6d5 100644 --- a/crates/uv-build-backend/src/lib.rs +++ b/crates/uv-build-backend/src/lib.rs @@ -355,6 +355,7 @@ mod tests { use indoc::indoc; use insta::assert_snapshot; use itertools::Itertools; + use regex::Regex; use sha2::Digest; use std::io::{BufReader, Read}; use std::iter; @@ -362,6 +363,8 @@ mod tests { use uv_distribution_filename::{SourceDistFilename, WheelFilename}; use uv_fs::{copy_dir_all, relative_to}; + const MOCK_UV_VERSION: &str = "1.0.0+test"; + fn format_err(err: &Error) -> String { let context = iter::successors(std::error::Error::source(&err), |&err| err.source()) .map(|err| format!(" Caused by: {err}")) @@ -388,19 +391,19 @@ mod tests { fn build(source_root: &Path, dist: &Path) -> Result { // Build a direct wheel, capture all its properties to compare it with the indirect wheel // latest and remove it since it has the same filename as the indirect wheel. - let (_name, direct_wheel_list_files) = list_wheel(source_root, "1.0.0+test")?; - let direct_wheel_filename = build_wheel(source_root, dist, None, "1.0.0+test")?; + let (_name, direct_wheel_list_files) = list_wheel(source_root, MOCK_UV_VERSION)?; + let direct_wheel_filename = build_wheel(source_root, dist, None, MOCK_UV_VERSION)?; let direct_wheel_path = dist.join(direct_wheel_filename.to_string()); let direct_wheel_contents = wheel_contents(&direct_wheel_path); let direct_wheel_hash = sha2::Sha256::digest(fs_err::read(&direct_wheel_path)?); fs_err::remove_file(&direct_wheel_path)?; // Build a source distribution. - let (_name, source_dist_list_files) = list_source_dist(source_root, "1.0.0+test")?; + let (_name, source_dist_list_files) = list_source_dist(source_root, MOCK_UV_VERSION)?; // TODO(konsti): This should run in the unpacked source dist tempdir, but we need to // normalize the path. - let (_name, wheel_list_files) = list_wheel(source_root, "1.0.0+test")?; - let source_dist_filename = build_source_dist(source_root, dist, "1.0.0+test")?; + let (_name, wheel_list_files) = list_wheel(source_root, MOCK_UV_VERSION)?; + let source_dist_filename = build_source_dist(source_root, dist, MOCK_UV_VERSION)?; let source_dist_path = dist.join(source_dist_filename.to_string()); let source_dist_contents = sdist_contents(&source_dist_path); @@ -414,7 +417,7 @@ mod tests { source_dist_filename.name.as_dist_info_name(), source_dist_filename.version )); - let wheel_filename = build_wheel(&sdist_top_level_directory, dist, None, "1.0.0+test")?; + let wheel_filename = build_wheel(&sdist_top_level_directory, dist, None, MOCK_UV_VERSION)?; let wheel_contents = wheel_contents(&dist.join(wheel_filename.to_string())); // Check that direct and indirect wheels are identical. @@ -515,14 +518,14 @@ mod tests { ] { copy_dir_all(built_by_uv.join(dir), src.path().join(dir)).unwrap(); } - for dir in [ + for filename in [ "pyproject.toml", "README.md", "uv.lock", "LICENSE-APACHE", "LICENSE-MIT", ] { - fs_err::copy(built_by_uv.join(dir), src.path().join(dir)).unwrap(); + fs_err::copy(built_by_uv.join(filename), src.path().join(filename)).unwrap(); } // Clear executable bit on Unix to build the same archive between Unix and Windows. @@ -539,6 +542,14 @@ mod tests { fs_err::set_permissions(&path, perms).unwrap(); } + // Redact the uv_build version to keep the hash stable across releases + let pyproject_toml = fs_err::read_to_string(src.path().join("pyproject.toml")).unwrap(); + let current_requires = + Regex::new(r#"requires = \["uv_build>=[0-9.]+,<[0-9.]+"\]"#).unwrap(); + let mocked_requires = r#"requires = ["uv_build>=1,<2"]"#; + let pyproject_toml = current_requires.replace(pyproject_toml.as_str(), mocked_requires); + fs_err::write(src.path().join("pyproject.toml"), pyproject_toml.as_bytes()).unwrap(); + // Add some files to be excluded let module_root = src.path().join("src").join("built_by_uv"); fs_err::create_dir_all(module_root.join("__pycache__")).unwrap(); @@ -557,7 +568,7 @@ mod tests { // Check that the source dist is reproducible across platforms. assert_snapshot!( format!("{:x}", sha2::Sha256::digest(fs_err::read(&source_dist_path).unwrap())), - @"9a7f7181c5e69ac14e411a2500fed153a1e6ea41cd5da6f24f226c4cddacf6b7" + @"871d1f859140721b67cbeaca074e7a2740c88c38028d0509eba87d1285f1da9e" ); // Check both the files we report and the actual files assert_snapshot!(format_file_list(build.source_dist_list_files, src.path()), @r" From 327c2bcd8a9567f46de4f2d86a5a744968346cf7 Mon Sep 17 00:00:00 2001 From: konsti Date: Fri, 18 Jul 2025 14:03:55 +0200 Subject: [PATCH 264/349] Use SHA256 from GitHub API for Python downloads (#14708) We recently ran over the file limit and had to drop hash file from the releases page in favor of bulk SHA256SUMS files (https://github.com/astral-sh/python-build-standalone/pull/691). Conveniently, GitHub has recently started to add a SHA256 digest to the API. GitHub did not backfill the hashes for the old releases, so use the API hashes for newer assets, and eventually only download SHA256SUMS for older releases. --- crates/uv-python/fetch-download-metadata.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/crates/uv-python/fetch-download-metadata.py b/crates/uv-python/fetch-download-metadata.py index 08adaecea..3dd0817f3 100755 --- a/crates/uv-python/fetch-download-metadata.py +++ b/crates/uv-python/fetch-download-metadata.py @@ -255,8 +255,13 @@ class CPythonFinder(Finder): # Sort the assets to ensure deterministic results row["assets"].sort(key=lambda asset: asset["browser_download_url"]) for asset in row["assets"]: + # On older versions, GitHub didn't backfill the digest. + if digest := asset["digest"]: + sha256 = digest.removeprefix("sha256:") + else: + sha256 = None url = asset["browser_download_url"] - download = self._parse_download_url(url) + download = self._parse_download_url(url, sha256) if download is None: continue if ( @@ -305,6 +310,9 @@ class CPythonFinder(Finder): """Fetch the checksums for the given downloads.""" checksum_urls = set() for download in downloads: + # Skip the newer releases where we got the hash from the GitHub API + if download.sha256: + continue release_base_url = download.url.rsplit("/", maxsplit=1)[0] checksum_url = release_base_url + "/SHA256SUMS" checksum_urls.add(checksum_url) @@ -343,9 +351,13 @@ class CPythonFinder(Finder): checksums[filename] = checksum for download in downloads: + if download.sha256: + continue download.sha256 = checksums.get(download.filename) - def _parse_download_url(self, url: str) -> PythonDownload | None: + def _parse_download_url( + self, url: str, sha256: str | None + ) -> PythonDownload | None: """Parse an indygreg download URL into a PythonDownload object.""" # Ex) # https://github.com/astral-sh/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-aarch64-unknown-linux-gnu-lto-full.tar.zst @@ -391,6 +403,7 @@ class CPythonFinder(Finder): url=url, build_options=build_options, variant=variant, + sha256=sha256, ) def _normalize_triple(self, triple: str) -> PlatformTriple | None: From 8f2f43c5614e0e0723afdd039a819d05ed880fc3 Mon Sep 17 00:00:00 2001 From: konsti Date: Fri, 18 Jul 2025 14:08:49 +0200 Subject: [PATCH 265/349] Add a reusable path-or-URL parser (#14712) Reviewing #14687, I noticed that we had implemented a `Url::from_url_or_path`-like function, but it wasn't reusable. This change `Verbatim::from_url_or_path` so we can use it in other places too. The PEP 508 parser is an odd place for this, but that's where `VerbatimUrl` and `Scheme` are already living. --- crates/uv-distribution-types/src/index_url.rs | 29 +----------- crates/uv-pep508/src/verbatim_url.rs | 46 +++++++++++++++++++ 2 files changed, 48 insertions(+), 27 deletions(-) diff --git a/crates/uv-distribution-types/src/index_url.rs b/crates/uv-distribution-types/src/index_url.rs index bd3e9abc2..cbc1a4eb1 100644 --- a/crates/uv-distribution-types/src/index_url.rs +++ b/crates/uv-distribution-types/src/index_url.rs @@ -39,33 +39,8 @@ impl IndexUrl { /// If no root directory is provided, relative paths are resolved against the current working /// directory. pub fn parse(path: &str, root_dir: Option<&Path>) -> Result { - let url = match split_scheme(path) { - Some((scheme, ..)) => { - match Scheme::parse(scheme) { - Some(_) => { - // Ex) `https://pypi.org/simple` - VerbatimUrl::parse_url(path)? - } - None => { - // Ex) `C:\Users\user\index` - if let Some(root_dir) = root_dir { - VerbatimUrl::from_path(path, root_dir)? - } else { - VerbatimUrl::from_absolute_path(std::path::absolute(path)?)? - } - } - } - } - None => { - // Ex) `/Users/user/index` - if let Some(root_dir) = root_dir { - VerbatimUrl::from_path(path, root_dir)? - } else { - VerbatimUrl::from_absolute_path(std::path::absolute(path)?)? - } - } - }; - Ok(Self::from(url.with_given(path))) + let url = VerbatimUrl::from_url_or_path(path, root_dir)?; + Ok(Self::from(url)) } /// Return the root [`Url`] of the index, if applicable. diff --git a/crates/uv-pep508/src/verbatim_url.rs b/crates/uv-pep508/src/verbatim_url.rs index 37d07b40b..2911de938 100644 --- a/crates/uv-pep508/src/verbatim_url.rs +++ b/crates/uv-pep508/src/verbatim_url.rs @@ -58,6 +58,48 @@ impl VerbatimUrl { }) } + /// Convert a [`VerbatimUrl`] from a path or a URL. + /// + /// If no root directory is provided, relative paths are resolved against the current working + /// directory. + pub fn from_url_or_path( + input: &str, + root_dir: Option<&Path>, + ) -> Result { + let url = match split_scheme(input) { + Some((scheme, ..)) => { + match Scheme::parse(scheme) { + Some(_) => { + // Ex) `https://pypi.org/simple` + Self::parse_url(input)? + } + None => { + // Ex) `C:\Users\user\index` + if let Some(root_dir) = root_dir { + Self::from_path(input, root_dir)? + } else { + let absolute_path = std::path::absolute(input).map_err(|err| { + VerbatimUrlError::Absolute(input.to_string(), err) + })?; + Self::from_absolute_path(absolute_path)? + } + } + } + } + None => { + // Ex) `/Users/user/index` + if let Some(root_dir) = root_dir { + Self::from_path(input, root_dir)? + } else { + let absolute_path = std::path::absolute(input) + .map_err(|err| VerbatimUrlError::Absolute(input.to_string(), err))?; + Self::from_absolute_path(absolute_path)? + } + } + }; + Ok(url.with_given(input)) + } + /// Parse a URL from an absolute or relative path. #[cfg(feature = "non-pep508-extensions")] // PEP 508 arguably only allows absolute file URLs. pub fn from_path( @@ -362,6 +404,10 @@ pub enum VerbatimUrlError { #[error("path could not be normalized: {0}")] Normalization(PathBuf, #[source] std::io::Error), + /// Received a path that could not be converted to an absolute path. + #[error("path could not be converted to an absolute path: {0}")] + Absolute(String, #[source] std::io::Error), + /// Received a path that could not be normalized. #[cfg(not(feature = "non-pep508-extensions"))] #[error("Not a URL (missing scheme): {0}")] From d1f4f8a358016a60d730ba1ca234ccfdf194ee8e Mon Sep 17 00:00:00 2001 From: konsti Date: Fri, 18 Jul 2025 14:47:56 +0200 Subject: [PATCH 266/349] More resilient registry removal (#14717) With the previous order of operations, there could be warnings from race conditions between two process A and B removing and installing Python versions. * A removes the files for CPython3.9.18 * B sees the key CPython3.9.18 * B sees that CPython3.9.18 has no files * A removes the key for CPython3.9.18 * B try to removes the key for CPython3.9.18, gets and error that it's already gone, issues a warning We make the more resilient in two ways: * We remove the registry key first, avoiding dangling registry keys in the removal process * We ignore not found errors in registry removal operations: If we try to remove something that's already gone, that's fine. Fixes #14714 (hopefully) --- crates/uv-python/src/windows_registry.rs | 6 ++++++ crates/uv/src/commands/python/uninstall.rs | 24 ++++++++++++---------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/crates/uv-python/src/windows_registry.rs b/crates/uv-python/src/windows_registry.rs index 7c6f6f307..f722db60c 100644 --- a/crates/uv-python/src/windows_registry.rs +++ b/crates/uv-python/src/windows_registry.rs @@ -268,6 +268,9 @@ pub fn remove_orphan_registry_entries(installations: &[ManagedPythonInstallation // Separate assignment since `keys()` creates a borrow. let subkeys = match key.keys() { Ok(subkeys) => subkeys, + Err(err) if err.code() == ERROR_NOT_FOUND => { + return; + } Err(err) => { // TODO(konsti): We don't have an installation key here. warn_user_once!("Failed to list subkeys of HKCU:\\{astral_key}: {err}"); @@ -281,6 +284,9 @@ pub fn remove_orphan_registry_entries(installations: &[ManagedPythonInstallation let python_entry = format!("{astral_key}\\{subkey}"); debug!("Removing orphan registry key HKCU:\\{}", python_entry); if let Err(err) = CURRENT_USER.remove_tree(&python_entry) { + if err.code() == ERROR_NOT_FOUND { + continue; + } // TODO(konsti): We don't have an installation key here. warn_user_once!("Failed to remove orphan registry key HKCU:\\{python_entry}: {err}"); } diff --git a/crates/uv/src/commands/python/uninstall.rs b/crates/uv/src/commands/python/uninstall.rs index dd306fc4d..c2e2e6877 100644 --- a/crates/uv/src/commands/python/uninstall.rs +++ b/crates/uv/src/commands/python/uninstall.rs @@ -142,6 +142,19 @@ async fn do_uninstall( return Ok(ExitStatus::Failure); } + // Remove registry entries first, so we don't have dangling entries between the file removal + // and the registry removal. + let mut errors = vec![]; + #[cfg(windows)] + { + uv_python::windows_registry::remove_registry_entry( + &matching_installations, + all, + &mut errors, + ); + uv_python::windows_registry::remove_orphan_registry_entries(&installed_installations); + } + // Find and remove all relevant Python executables let mut uninstalled_executables: FxHashMap> = FxHashMap::default(); @@ -201,7 +214,6 @@ async fn do_uninstall( } let mut uninstalled = IndexSet::::default(); - let mut errors = vec![]; while let Some((key, result)) = tasks.next().await { if let Err(err) = result { errors.push((key.clone(), anyhow::Error::new(err))); @@ -210,16 +222,6 @@ async fn do_uninstall( } } - #[cfg(windows)] - { - uv_python::windows_registry::remove_registry_entry( - &matching_installations, - all, - &mut errors, - ); - uv_python::windows_registry::remove_orphan_registry_entries(&installed_installations); - } - // Read all existing managed installations and find the highest installed patch // for each installed minor version. Ensure the minor version link directory // is still valid. From 70875128be837b8ea96fb0cf6c9c2f9dea859195 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Fri, 18 Jul 2025 07:49:25 -0500 Subject: [PATCH 267/349] Disable the Windows Registry updates during `python install` tests (#14718) --- crates/uv/tests/it/common/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/uv/tests/it/common/mod.rs b/crates/uv/tests/it/common/mod.rs index ab4c38247..4c686cb77 100644 --- a/crates/uv/tests/it/common/mod.rs +++ b/crates/uv/tests/it/common/mod.rs @@ -795,6 +795,9 @@ impl TestContext { .env(EnvVars::UV_PYTHON_DOWNLOADS, "never") .env(EnvVars::UV_TEST_PYTHON_PATH, self.python_path()) .env(EnvVars::UV_EXCLUDE_NEWER, EXCLUDE_NEWER) + // When installations are allowed, we don't want to write to global state, like the + // Windows registry + .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. .env(EnvVars::UV_TEST_NO_CLI_PROGRESS, "1") From a186fda2d27d74c631c50ffccb9413d31f95cc89 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Fri, 18 Jul 2025 08:07:36 -0500 Subject: [PATCH 268/349] Elide traceback when `python -m uv` in interrupted with Ctrl-C on Windows (#14715) Closes https://github.com/astral-sh/uv/issues/14704 --- python/uv/__main__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/python/uv/__main__.py b/python/uv/__main__.py index d8731c7ec..15c081867 100644 --- a/python/uv/__main__.py +++ b/python/uv/__main__.py @@ -37,7 +37,12 @@ def _run() -> None: if sys.platform == "win32": import subprocess - completed_process = subprocess.run([uv, *sys.argv[1:]], env=env) + # Avoid emitting a traceback on interrupt + try: + completed_process = subprocess.run([uv, *sys.argv[1:]], env=env) + except KeyboardInterrupt: + sys.exit(2) + sys.exit(completed_process.returncode) else: os.execvpe(uv, [uv, *sys.argv[1:]], env=env) From 574aa1ef110ef08293512eb200bd6881bb738179 Mon Sep 17 00:00:00 2001 From: konsti Date: Fri, 18 Jul 2025 15:26:47 +0200 Subject: [PATCH 269/349] Better error reporting for removing Python versions from the Windows registry (#14722) See https://github.com/astral-sh/uv/actions/runs/16370666070/job/46258004849 We didn't actual use a format string, showing the template instead. We don't show the causes in the error report, so we format it into one error. --- crates/uv-python/src/windows_registry.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/uv-python/src/windows_registry.rs b/crates/uv-python/src/windows_registry.rs index f722db60c..0020f95e9 100644 --- a/crates/uv-python/src/windows_registry.rs +++ b/crates/uv-python/src/windows_registry.rs @@ -3,6 +3,7 @@ use crate::managed::ManagedPythonInstallation; use crate::platform::Arch; use crate::{COMPANY_DISPLAY_NAME, COMPANY_KEY, PythonInstallationKey, PythonVersion}; +use anyhow::anyhow; use std::cmp::Ordering; use std::collections::HashSet; use std::path::PathBuf; @@ -238,8 +239,7 @@ pub fn remove_registry_entry<'a>( } else { errors.push(( installation.key().clone(), - anyhow::Error::new(err) - .context("Failed to clear registry entries under HKCU:\\{python_entry}"), + anyhow!("Failed to clear registry entries under HKCU:\\{python_entry}: {err}"), )); } } From d0efe1ed9c4bec806e8449a471bd97c9be10ba14 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Fri, 18 Jul 2025 16:32:29 -0400 Subject: [PATCH 270/349] Apply Cache-Control overrides to response, not request headers (#14736) ## Summary This was just an oversight on my part in the initial implementation. Closes https://github.com/astral-sh/uv/issues/14719. ## Test Plan With: ```toml [project] name = "foo" version = "0.1.0" description = "Add your description here" readme = "README.md" requires-python = ">=3.13.2" dependencies = [ ] [[tool.uv.index]] url = "https://download.pytorch.org/whl/cpu" cache-control = { api = "max-age=600" } ``` Ran `cargo run lock -vvv` and verified that the PyTorch index response was cached (whereas it typically returns `cache-control: no-cache,no-store,must-revalidate`). --- crates/uv-client/src/cached_client.rs | 83 ++++++++++++++----- crates/uv-distribution-types/src/index_url.rs | 20 +++++ .../src/distribution_database.rs | 68 +++++++++++---- crates/uv-distribution/src/source/mod.rs | 80 +++++++++++++++--- 4 files changed, 201 insertions(+), 50 deletions(-) diff --git a/crates/uv-client/src/cached_client.rs b/crates/uv-client/src/cached_client.rs index f888ea5f1..4219decd5 100644 --- a/crates/uv-client/src/cached_client.rs +++ b/crates/uv-client/src/cached_client.rs @@ -304,7 +304,7 @@ impl CachedClient { .await? } else { debug!("No cache entry for: {}", req.url()); - let (response, cache_policy) = self.fresh_request(req).await?; + let (response, cache_policy) = self.fresh_request(req, cache_control).await?; CachedResponse::ModifiedOrNew { response, cache_policy, @@ -318,8 +318,13 @@ impl CachedClient { "Broken fresh cache entry (for payload) at {}, removing: {err}", cache_entry.path().display() ); - self.resend_and_heal_cache(fresh_req, cache_entry, response_callback) - .await + self.resend_and_heal_cache( + fresh_req, + cache_entry, + cache_control, + response_callback, + ) + .await } }, CachedResponse::NotModified { cached, new_policy } => { @@ -339,8 +344,13 @@ impl CachedClient { (for payload) at {}, removing: {err}", cache_entry.path().display() ); - self.resend_and_heal_cache(fresh_req, cache_entry, response_callback) - .await + self.resend_and_heal_cache( + fresh_req, + cache_entry, + cache_control, + response_callback, + ) + .await } } } @@ -355,8 +365,13 @@ impl CachedClient { // ETag didn't match). We need to make a fresh request. if response.status() == http::StatusCode::NOT_MODIFIED { warn!("Server returned unusable 304 for: {}", fresh_req.url()); - self.resend_and_heal_cache(fresh_req, cache_entry, response_callback) - .await + self.resend_and_heal_cache( + fresh_req, + cache_entry, + cache_control, + response_callback, + ) + .await } else { self.run_response_callback( cache_entry, @@ -379,9 +394,10 @@ impl CachedClient { &self, req: Request, cache_entry: &CacheEntry, + cache_control: CacheControl<'_>, response_callback: Callback, ) -> Result> { - let (response, cache_policy) = self.fresh_request(req).await?; + let (response, cache_policy) = self.fresh_request(req, cache_control).await?; let payload = self .run_response_callback(cache_entry, cache_policy, response, async |resp| { @@ -401,10 +417,11 @@ impl CachedClient { &self, req: Request, cache_entry: &CacheEntry, + cache_control: CacheControl<'_>, response_callback: Callback, ) -> Result> { let _ = fs_err::tokio::remove_file(&cache_entry.path()).await; - let (response, cache_policy) = self.fresh_request(req).await?; + let (response, cache_policy) = self.fresh_request(req, cache_control).await?; self.run_response_callback(cache_entry, cache_policy, response, response_callback) .await } @@ -476,20 +493,13 @@ impl CachedClient { ) -> Result { // Apply the cache control header, if necessary. match cache_control { - CacheControl::None | CacheControl::AllowStale => {} + CacheControl::None | CacheControl::AllowStale | CacheControl::Override(..) => {} CacheControl::MustRevalidate => { req.headers_mut().insert( http::header::CACHE_CONTROL, http::HeaderValue::from_static("no-cache"), ); } - CacheControl::Override(value) => { - req.headers_mut().insert( - http::header::CACHE_CONTROL, - http::HeaderValue::from_str(value) - .map_err(|_| ErrorKind::InvalidCacheControl(value.to_string()))?, - ); - } } Ok(match cached.cache_policy.before_request(&mut req) { BeforeRequest::Fresh => { @@ -499,8 +509,13 @@ impl CachedClient { BeforeRequest::Stale(new_cache_policy_builder) => match cache_control { CacheControl::None | CacheControl::MustRevalidate | CacheControl::Override(_) => { debug!("Found stale response for: {}", req.url()); - self.send_cached_handle_stale(req, cached, new_cache_policy_builder) - .await? + self.send_cached_handle_stale( + req, + cache_control, + cached, + new_cache_policy_builder, + ) + .await? } CacheControl::AllowStale => { debug!("Found stale (but allowed) response for: {}", req.url()); @@ -513,7 +528,7 @@ impl CachedClient { "Cached request doesn't match current request for: {}", req.url() ); - let (response, cache_policy) = self.fresh_request(req).await?; + let (response, cache_policy) = self.fresh_request(req, cache_control).await?; CachedResponse::ModifiedOrNew { response, cache_policy, @@ -525,12 +540,13 @@ impl CachedClient { async fn send_cached_handle_stale( &self, req: Request, + cache_control: CacheControl<'_>, cached: DataWithCachePolicy, new_cache_policy_builder: CachePolicyBuilder, ) -> Result { let url = DisplaySafeUrl::from(req.url().clone()); debug!("Sending revalidation request for: {url}"); - let response = self + let mut response = self .0 .execute(req) .instrument(info_span!("revalidation_request", url = url.as_str())) @@ -538,6 +554,16 @@ impl CachedClient { .map_err(|err| ErrorKind::from_reqwest_middleware(url.clone(), err))? .error_for_status() .map_err(|err| ErrorKind::from_reqwest(url.clone(), err))?; + + // If the user set a custom `Cache-Control` header, override it. + if let CacheControl::Override(header) = cache_control { + response.headers_mut().insert( + http::header::CACHE_CONTROL, + http::HeaderValue::from_str(header) + .expect("Cache-Control header must be valid UTF-8"), + ); + } + match cached .cache_policy .after_response(new_cache_policy_builder, &response) @@ -566,16 +592,26 @@ impl CachedClient { async fn fresh_request( &self, req: Request, + cache_control: CacheControl<'_>, ) -> Result<(Response, Option>), Error> { let url = DisplaySafeUrl::from(req.url().clone()); trace!("Sending fresh {} request for {}", req.method(), url); let cache_policy_builder = CachePolicyBuilder::new(&req); - let response = self + let mut response = self .0 .execute(req) .await .map_err(|err| ErrorKind::from_reqwest_middleware(url.clone(), err))?; + // If the user set a custom `Cache-Control` header, override it. + if let CacheControl::Override(header) = cache_control { + response.headers_mut().insert( + http::header::CACHE_CONTROL, + http::HeaderValue::from_str(header) + .expect("Cache-Control header must be valid UTF-8"), + ); + } + let retry_count = response .extensions() .get::() @@ -690,6 +726,7 @@ impl CachedClient { &self, req: Request, cache_entry: &CacheEntry, + cache_control: CacheControl<'_>, response_callback: Callback, ) -> Result> { let mut past_retries = 0; @@ -698,7 +735,7 @@ impl CachedClient { loop { let fresh_req = req.try_clone().expect("HTTP request must be cloneable"); let result = self - .skip_cache(fresh_req, cache_entry, &response_callback) + .skip_cache(fresh_req, cache_entry, cache_control, &response_callback) .await; // Check if the middleware already performed retries diff --git a/crates/uv-distribution-types/src/index_url.rs b/crates/uv-distribution-types/src/index_url.rs index cbc1a4eb1..6baca1c1f 100644 --- a/crates/uv-distribution-types/src/index_url.rs +++ b/crates/uv-distribution-types/src/index_url.rs @@ -441,6 +441,26 @@ impl<'a> IndexLocations { } } } + + /// Return the Simple API cache control header for an [`IndexUrl`], if configured. + pub fn simple_api_cache_control_for(&self, url: &IndexUrl) -> Option<&str> { + for index in &self.indexes { + if index.url() == url { + return index.cache_control.as_ref()?.api.as_deref(); + } + } + None + } + + /// Return the artifact cache control header for an [`IndexUrl`], if configured. + pub fn artifact_cache_control_for(&self, url: &IndexUrl) -> Option<&str> { + for index in &self.indexes { + if index.url() == url { + return index.cache_control.as_ref()?.files.as_deref(); + } + } + None + } } impl From<&IndexLocations> for uv_auth::Indexes { diff --git a/crates/uv-distribution/src/distribution_database.rs b/crates/uv-distribution/src/distribution_database.rs index d18269730..30f3a243c 100644 --- a/crates/uv-distribution/src/distribution_database.rs +++ b/crates/uv-distribution/src/distribution_database.rs @@ -20,7 +20,7 @@ use uv_client::{ }; use uv_distribution_filename::WheelFilename; use uv_distribution_types::{ - BuildableSource, BuiltDist, Dist, HashPolicy, Hashed, InstalledDist, Name, SourceDist, + BuildableSource, BuiltDist, Dist, HashPolicy, Hashed, IndexUrl, InstalledDist, Name, SourceDist, }; use uv_extract::hash::Hasher; use uv_fs::write_atomic; @@ -201,6 +201,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> { match self .stream_wheel( url.clone(), + dist.index(), &wheel.filename, wheel.file.size, &wheel_entry, @@ -236,6 +237,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> { let archive = self .download_wheel( url, + dist.index(), &wheel.filename, wheel.file.size, &wheel_entry, @@ -272,6 +274,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> { match self .stream_wheel( wheel.url.raw().clone(), + None, &wheel.filename, None, &wheel_entry, @@ -301,6 +304,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> { let archive = self .download_wheel( wheel.url.raw().clone(), + None, &wheel.filename, None, &wheel_entry, @@ -534,6 +538,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> { async fn stream_wheel( &self, url: DisplaySafeUrl, + index: Option<&IndexUrl>, filename: &WheelFilename, size: Option, wheel_entry: &CacheEntry, @@ -616,13 +621,24 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> { // Fetch the archive from the cache, or download it if necessary. let req = self.request(url.clone())?; + // Determine the cache control policy for the URL. let cache_control = match self.client.unmanaged.connectivity() { - Connectivity::Online => CacheControl::from( - self.build_context - .cache() - .freshness(&http_entry, Some(&filename.name), None) - .map_err(Error::CacheRead)?, - ), + Connectivity::Online => { + if let Some(header) = index.and_then(|index| { + self.build_context + .locations() + .artifact_cache_control_for(index) + }) { + CacheControl::Override(header) + } else { + CacheControl::from( + self.build_context + .cache() + .freshness(&http_entry, Some(&filename.name), None) + .map_err(Error::CacheRead)?, + ) + } + } Connectivity::Offline => CacheControl::AllowStale, }; @@ -654,7 +670,12 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> { .managed(async |client| { client .cached_client() - .skip_cache_with_retry(self.request(url)?, &http_entry, download) + .skip_cache_with_retry( + self.request(url)?, + &http_entry, + cache_control, + download, + ) .await .map_err(|err| match err { CachedClientError::Callback { err, .. } => err, @@ -671,6 +692,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> { async fn download_wheel( &self, url: DisplaySafeUrl, + index: Option<&IndexUrl>, filename: &WheelFilename, size: Option, wheel_entry: &CacheEntry, @@ -783,13 +805,24 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> { // Fetch the archive from the cache, or download it if necessary. let req = self.request(url.clone())?; + // Determine the cache control policy for the URL. let cache_control = match self.client.unmanaged.connectivity() { - Connectivity::Online => CacheControl::from( - self.build_context - .cache() - .freshness(&http_entry, Some(&filename.name), None) - .map_err(Error::CacheRead)?, - ), + Connectivity::Online => { + if let Some(header) = index.and_then(|index| { + self.build_context + .locations() + .artifact_cache_control_for(index) + }) { + CacheControl::Override(header) + } else { + CacheControl::from( + self.build_context + .cache() + .freshness(&http_entry, Some(&filename.name), None) + .map_err(Error::CacheRead)?, + ) + } + } Connectivity::Offline => CacheControl::AllowStale, }; @@ -821,7 +854,12 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> { .managed(async |client| { client .cached_client() - .skip_cache_with_retry(self.request(url)?, &http_entry, download) + .skip_cache_with_retry( + self.request(url)?, + &http_entry, + cache_control, + download, + ) .await .map_err(|err| match err { CachedClientError::Callback { err, .. } => err, diff --git a/crates/uv-distribution/src/source/mod.rs b/crates/uv-distribution/src/source/mod.rs index 080a1e52d..66b6122e0 100644 --- a/crates/uv-distribution/src/source/mod.rs +++ b/crates/uv-distribution/src/source/mod.rs @@ -32,7 +32,7 @@ use uv_client::{ use uv_configuration::{BuildKind, BuildOutput, ConfigSettings, SourceStrategy}; use uv_distribution_filename::{SourceDistExtension, WheelFilename}; use uv_distribution_types::{ - BuildableSource, DirectorySourceUrl, GitSourceUrl, HashPolicy, Hashed, PathSourceUrl, + BuildableSource, DirectorySourceUrl, GitSourceUrl, HashPolicy, Hashed, IndexUrl, PathSourceUrl, SourceDist, SourceUrl, }; use uv_extract::hash::Hasher; @@ -148,6 +148,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { self.url( source, &url, + Some(&dist.index), &cache_shard, None, dist.ext, @@ -168,6 +169,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { self.url( source, &dist.url, + None, &cache_shard, dist.subdirectory.as_deref(), dist.ext, @@ -213,6 +215,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { self.url( source, resource.url, + None, &cache_shard, resource.subdirectory, resource.ext, @@ -288,9 +291,18 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .await; } - self.url_metadata(source, &url, &cache_shard, None, dist.ext, hashes, client) - .boxed_local() - .await? + self.url_metadata( + source, + &url, + Some(&dist.index), + &cache_shard, + None, + dist.ext, + hashes, + client, + ) + .boxed_local() + .await? } BuildableSource::Dist(SourceDist::DirectUrl(dist)) => { // For direct URLs, cache directly under the hash of the URL itself. @@ -302,6 +314,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { self.url_metadata( source, &dist.url, + None, &cache_shard, dist.subdirectory.as_deref(), dist.ext, @@ -340,6 +353,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { self.url_metadata( source, resource.url, + None, &cache_shard, resource.subdirectory, resource.ext, @@ -395,6 +409,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { &self, source: &BuildableSource<'data>, url: &'data DisplaySafeUrl, + index: Option<&'data IndexUrl>, cache_shard: &CacheShard, subdirectory: Option<&'data Path>, ext: SourceDistExtension, @@ -406,7 +421,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { // Fetch the revision for the source distribution. let revision = self - .url_revision(source, ext, url, cache_shard, hashes, client) + .url_revision(source, ext, url, index, cache_shard, hashes, client) .await?; // Before running the build, check that the hashes match. @@ -448,6 +463,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { source, ext, url, + index, &source_dist_entry, revision, hashes, @@ -511,6 +527,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { &self, source: &BuildableSource<'data>, url: &'data Url, + index: Option<&'data IndexUrl>, cache_shard: &CacheShard, subdirectory: Option<&'data Path>, ext: SourceDistExtension, @@ -521,7 +538,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { // Fetch the revision for the source distribution. let revision = self - .url_revision(source, ext, url, cache_shard, hashes, client) + .url_revision(source, ext, url, index, cache_shard, hashes, client) .await?; // Before running the build, check that the hashes match. @@ -578,6 +595,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { source, ext, url, + index, &source_dist_entry, revision, hashes, @@ -689,18 +707,31 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { source: &BuildableSource<'_>, ext: SourceDistExtension, url: &Url, + index: Option<&IndexUrl>, cache_shard: &CacheShard, hashes: HashPolicy<'_>, client: &ManagedClient<'_>, ) -> Result { let cache_entry = cache_shard.entry(HTTP_REVISION); + + // Determine the cache control policy for the request. let cache_control = match client.unmanaged.connectivity() { - Connectivity::Online => CacheControl::from( - self.build_context - .cache() - .freshness(&cache_entry, source.name(), source.source_tree()) - .map_err(Error::CacheRead)?, - ), + Connectivity::Online => { + if let Some(header) = index.and_then(|index| { + self.build_context + .locations() + .artifact_cache_control_for(index) + }) { + CacheControl::Override(header) + } else { + CacheControl::from( + self.build_context + .cache() + .freshness(&cache_entry, source.name(), source.source_tree()) + .map_err(Error::CacheRead)?, + ) + } + } Connectivity::Offline => CacheControl::AllowStale, }; @@ -750,6 +781,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .skip_cache_with_retry( Self::request(DisplaySafeUrl::from(url.clone()), client)?, &cache_entry, + cache_control, download, ) .await @@ -2056,6 +2088,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { source: &BuildableSource<'_>, ext: SourceDistExtension, url: &Url, + index: Option<&IndexUrl>, entry: &CacheEntry, revision: Revision, hashes: HashPolicy<'_>, @@ -2063,6 +2096,28 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { ) -> Result { warn!("Re-downloading missing source distribution: {source}"); let cache_entry = entry.shard().entry(HTTP_REVISION); + + // Determine the cache control policy for the request. + let cache_control = match client.unmanaged.connectivity() { + Connectivity::Online => { + if let Some(header) = index.and_then(|index| { + self.build_context + .locations() + .artifact_cache_control_for(index) + }) { + CacheControl::Override(header) + } else { + CacheControl::from( + self.build_context + .cache() + .freshness(&cache_entry, source.name(), source.source_tree()) + .map_err(Error::CacheRead)?, + ) + } + } + Connectivity::Offline => CacheControl::AllowStale, + }; + let download = |response| { async { // Take the union of the requested and existing hash algorithms. @@ -2096,6 +2151,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .skip_cache_with_retry( Self::request(DisplaySafeUrl::from(url.clone()), client)?, &cache_entry, + cache_control, download, ) .await From 9c9db9b5476388bcfb32eb5f76e09462ea1c3d86 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sun, 20 Jul 2025 09:44:25 -0400 Subject: [PATCH 271/349] Clarify which portions of requires-python behavior are consistent with pip (#14752) See: #14711 --- docs/pip/compatibility.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/pip/compatibility.md b/docs/pip/compatibility.md index 5719c2fcc..2ce702006 100644 --- a/docs/pip/compatibility.md +++ b/docs/pip/compatibility.md @@ -447,7 +447,7 @@ By default, uv does not write any index URLs to the output file, while `pip-comp in the output file, pass the `--emit-index-url` flag to `uv pip compile`. Unlike `pip-compile`, uv will include all index URLs when `--emit-index-url` is passed, including the default index URL. -## `requires-python` enforcement +## `requires-python` upper bounds When evaluating `requires-python` ranges for dependencies, uv only considers lower bounds and ignores upper bounds entirely. For example, `>=3.8, <4` is treated as `>=3.8`. Respecting upper @@ -455,6 +455,8 @@ bounds on `requires-python` often leads to formally correct but practically inco as, e.g., resolvers will backtrack to the first published version that omits the upper bound (see: [`Requires-Python` upper limits](https://discuss.python.org/t/requires-python-upper-limits/12663)). +## `requires-python` specifiers + When evaluating Python versions against `requires-python` specifiers, uv truncates the candidate version to the major, minor, and patch components, ignoring (e.g.) pre-release and post-release identifiers. From d0a14c72a303deaf6f8ea5115df2129948b20cf9 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Sun, 20 Jul 2025 11:12:01 -0500 Subject: [PATCH 272/349] Fix tests requiring patch-level Python (#14733) Closes #14723 https://chatgpt.com/codex/tasks/task_e_687a532188d08331b4352ba0a78f8fdb --- crates/uv/tests/it/sync.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index 16c4d673a..1639ecaae 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -10494,7 +10494,7 @@ fn sync_required_environment_hint() -> Result<()> { [project] name = "example" version = "0.1.0" - requires-python = ">=3.13.2" + requires-python = ">=3.13" dependencies = ["no-sdist-no-wheels-with-matching-platform-a"] [[tool.uv.index]] @@ -10544,7 +10544,7 @@ fn sync_url_with_query_parameters() -> Result<()> { [project] name = "example" version = "0.1.0" - requires-python = ">=3.13.2" + requires-python = ">=3.13" dependencies = ["source-distribution @ https://files.pythonhosted.org/packages/1f/e5/5b016c945d745f8b108e759d428341488a6aee8f51f07c6c4e33498bb91f/source_distribution-0.0.3.tar.gz?foo=bar"] "# )?; From 2d8dda34b4c5e1cc7ec479dee830627dd317ddf3 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sun, 20 Jul 2025 13:53:36 -0400 Subject: [PATCH 273/349] Fix comment on `extra_names` (#14756) --- crates/uv-configuration/src/extras.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/uv-configuration/src/extras.rs b/crates/uv-configuration/src/extras.rs index e39fc72ef..5bb74240f 100644 --- a/crates/uv-configuration/src/extras.rs +++ b/crates/uv-configuration/src/extras.rs @@ -155,7 +155,8 @@ impl ExtrasSpecificationInner { self.include.names().chain(&self.exclude) } - /// Returns `true` if the specification includes the given extra. + /// Returns an iterator over all extras that are included in the specification, + /// assuming `all_names` is an iterator over all extras. pub fn extra_names<'a, Names>( &'a self, all_names: Names, From a3371867acc888771316c0c5a7e49fc2f46a1df7 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sun, 20 Jul 2025 14:02:22 -0400 Subject: [PATCH 274/349] Support `extras` and `dependency_groups` markers in PEP 508 grammar (#14753) ## Summary We always evaluate these to `false` right now, but we can at least parse them. See: https://peps.python.org/pep-0751/#dependency-groups. --- crates/uv-pep508/src/marker/algebra.rs | 60 ++++- crates/uv-pep508/src/marker/lowering.rs | 37 ++- crates/uv-pep508/src/marker/parse.rs | 102 +++++++- crates/uv-pep508/src/marker/simplify.rs | 59 +++++ crates/uv-pep508/src/marker/tree.rs | 267 +++++++++++++++++++- crates/uv-resolver/src/marker.rs | 10 + crates/uv-resolver/src/resolution/output.rs | 10 + 7 files changed, 530 insertions(+), 15 deletions(-) diff --git a/crates/uv-pep508/src/marker/algebra.rs b/crates/uv-pep508/src/marker/algebra.rs index 2a3f82f27..d1a369491 100644 --- a/crates/uv-pep508/src/marker/algebra.rs +++ b/crates/uv-pep508/src/marker/algebra.rs @@ -59,8 +59,10 @@ use uv_pep440::{Operator, Version, VersionSpecifier, release_specifier_to_range} use crate::marker::MarkerValueExtra; use crate::marker::lowering::{ - CanonicalMarkerValueExtra, CanonicalMarkerValueString, CanonicalMarkerValueVersion, + CanonicalMarkerValueDependencyGroup, CanonicalMarkerValueExtra, CanonicalMarkerValueString, + CanonicalMarkerValueVersion, }; +use crate::marker::tree::{ContainerOperator, MarkerValueDependencyGroup}; use crate::{ ExtraOperator, MarkerExpression, MarkerOperator, MarkerValueString, MarkerValueVersion, }; @@ -328,11 +330,53 @@ impl InternerGuard<'_> { Variable::Extra(CanonicalMarkerValueExtra::Extra(extra)), Edges::from_bool(false), ), - // Invalid extras are always `false`. + // Invalid `extra` names are always `false`. MarkerExpression::Extra { name: MarkerValueExtra::Arbitrary(_), .. } => return NodeId::FALSE, + // A variable representing the existence or absence of a particular extra, in the + // context of a PEP 751 lockfile. + MarkerExpression::Extras { + name: MarkerValueExtra::Extra(extra), + operator: ContainerOperator::In, + } => ( + Variable::Extras(CanonicalMarkerValueExtra::Extra(extra)), + Edges::from_bool(true), + ), + MarkerExpression::Extras { + name: MarkerValueExtra::Extra(extra), + operator: ContainerOperator::NotIn, + } => ( + Variable::Extras(CanonicalMarkerValueExtra::Extra(extra)), + Edges::from_bool(false), + ), + // Invalid `extras` names are always `false`. + MarkerExpression::Extras { + name: MarkerValueExtra::Arbitrary(_), + .. + } => return NodeId::FALSE, + // A variable representing the existence or absence of a particular extra, in the + // context of a PEP 751 lockfile. + MarkerExpression::DependencyGroups { + name: MarkerValueDependencyGroup::Group(group), + operator: ContainerOperator::In, + } => ( + Variable::DependencyGroups(CanonicalMarkerValueDependencyGroup::Group(group)), + Edges::from_bool(true), + ), + MarkerExpression::DependencyGroups { + name: MarkerValueDependencyGroup::Group(group), + operator: ContainerOperator::NotIn, + } => ( + Variable::DependencyGroups(CanonicalMarkerValueDependencyGroup::Group(group)), + Edges::from_bool(false), + ), + // Invalid `dependency_group` names are always `false`. + MarkerExpression::DependencyGroups { + name: MarkerValueDependencyGroup::Arbitrary(_), + .. + } => return NodeId::FALSE, }; self.create_node(var, children) @@ -1046,6 +1090,18 @@ pub(crate) enum Variable { /// We keep extras at the leaves of the tree, so when simplifying extras we can /// trivially remove the leaves without having to reconstruct the entire tree. Extra(CanonicalMarkerValueExtra), + /// A variable representing the existence or absence of a given extra, in the context of a + /// PEP 751 lockfile marker. + /// + /// We keep extras at the leaves of the tree, so when simplifying extras we can + /// trivially remove the leaves without having to reconstruct the entire tree. + Extras(CanonicalMarkerValueExtra), + /// A variable representing the existence or absence of a given dependency group, in the context of a + /// PEP 751 lockfile marker. + /// + /// We keep groups at the leaves of the tree, so when simplifying groups we can + /// trivially remove the leaves without having to reconstruct the entire tree. + DependencyGroups(CanonicalMarkerValueDependencyGroup), } impl Variable { diff --git a/crates/uv-pep508/src/marker/lowering.rs b/crates/uv-pep508/src/marker/lowering.rs index 16139a65d..dadfeac53 100644 --- a/crates/uv-pep508/src/marker/lowering.rs +++ b/crates/uv-pep508/src/marker/lowering.rs @@ -1,7 +1,8 @@ use std::fmt::{Display, Formatter}; -use uv_normalize::ExtraName; +use uv_normalize::{ExtraName, GroupName}; +use crate::marker::tree::MarkerValueDependencyGroup; use crate::{MarkerValueExtra, MarkerValueString, MarkerValueVersion}; /// Those environment markers with a PEP 440 version as value such as `python_version` @@ -128,7 +129,7 @@ impl Display for CanonicalMarkerValueString { } } -/// The [`ExtraName`] value used in `extra` markers. +/// The [`ExtraName`] value used in `extra` and `extras` markers. #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] pub enum CanonicalMarkerValueExtra { /// A valid [`ExtraName`]. @@ -159,3 +160,35 @@ impl Display for CanonicalMarkerValueExtra { } } } + +/// The [`GroupName`] value used in `dependency_group` markers. +#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub enum CanonicalMarkerValueDependencyGroup { + /// A valid [`GroupName`]. + Group(GroupName), +} + +impl CanonicalMarkerValueDependencyGroup { + /// Returns the [`GroupName`] value. + pub fn group(&self) -> &GroupName { + match self { + Self::Group(group) => group, + } + } +} + +impl From for MarkerValueDependencyGroup { + fn from(value: CanonicalMarkerValueDependencyGroup) -> Self { + match value { + CanonicalMarkerValueDependencyGroup::Group(group) => Self::Group(group), + } + } +} + +impl Display for CanonicalMarkerValueDependencyGroup { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Group(group) => group.fmt(f), + } + } +} diff --git a/crates/uv-pep508/src/marker/parse.rs b/crates/uv-pep508/src/marker/parse.rs index 13620662b..9c361c19d 100644 --- a/crates/uv-pep508/src/marker/parse.rs +++ b/crates/uv-pep508/src/marker/parse.rs @@ -1,10 +1,11 @@ use arcstr::ArcStr; use std::str::FromStr; -use uv_normalize::ExtraName; +use uv_normalize::{ExtraName, GroupName}; use uv_pep440::{Version, VersionPattern, VersionSpecifier}; use crate::cursor::Cursor; use crate::marker::MarkerValueExtra; +use crate::marker::tree::{ContainerOperator, MarkerValueDependencyGroup}; use crate::{ ExtraOperator, MarkerExpression, MarkerOperator, MarkerTree, MarkerValue, MarkerValueString, MarkerValueVersion, MarkerWarningKind, Pep508Error, Pep508ErrorSource, Pep508Url, Reporter, @@ -208,6 +209,8 @@ pub(crate) fn parse_marker_key_op_value( MarkerValue::MarkerEnvString(key) => { let value = match r_value { MarkerValue::Extra + | MarkerValue::Extras + | MarkerValue::DependencyGroups | MarkerValue::MarkerEnvVersion(_) | MarkerValue::MarkerEnvString(_) => { reporter.report( @@ -242,7 +245,9 @@ pub(crate) fn parse_marker_key_op_value( let value = match r_value { MarkerValue::MarkerEnvVersion(_) | MarkerValue::MarkerEnvString(_) - | MarkerValue::Extra => { + | MarkerValue::Extra + | MarkerValue::Extras + | MarkerValue::DependencyGroups => { reporter.report( MarkerWarningKind::ExtraInvalidComparison, "Comparing extra with something other than a quoted string is wrong, @@ -257,7 +262,7 @@ pub(crate) fn parse_marker_key_op_value( parse_extra_expr(operator, &value, reporter) } - // This is either MarkerEnvVersion, MarkerEnvString or Extra inverted + // This is either MarkerEnvVersion, MarkerEnvString, Extra (inverted), or Extras MarkerValue::QuotedString(l_string) => { match r_value { // The only sound choice for this is ` ` @@ -273,6 +278,12 @@ pub(crate) fn parse_marker_key_op_value( }), // `'...' == extra` MarkerValue::Extra => parse_extra_expr(operator, &l_string, reporter), + // `'...' in extras` + MarkerValue::Extras => parse_extras_expr(operator, &l_string, reporter), + // `'...' in dependency_groups` + MarkerValue::DependencyGroups => { + parse_dependency_groups_expr(operator, &l_string, reporter) + } // `'...' == '...'`, doesn't make much sense MarkerValue::QuotedString(_) => { // Not even pypa/packaging 22.0 supports this @@ -289,6 +300,26 @@ pub(crate) fn parse_marker_key_op_value( } } } + MarkerValue::Extras => { + reporter.report( + MarkerWarningKind::Pep440Error, + format!( + "The `extras` marker must be used as '...' in extras' or '... not in extras', + found `{l_value} {operator} {r_value}`, will be ignored" + ), + ); + return Ok(None); + } + MarkerValue::DependencyGroups => { + reporter.report( + MarkerWarningKind::Pep440Error, + format!( + "The `dependency_groups` marker must be used as '...' in dependency_groups' or '... not in dependency_groups', + found `{l_value} {operator} {r_value}`, will be ignored" + ), + ); + return Ok(None); + } }; Ok(expr) @@ -491,8 +522,69 @@ fn parse_extra_expr( reporter.report( MarkerWarningKind::ExtraInvalidComparison, - "Comparing extra with something other than a quoted string is wrong, - will be ignored" + "Comparing `extra` with any operator other than `==` or `!=` is wrong and will be ignored" + .to_string(), + ); + + None +} + +/// Creates an instance of [`MarkerExpression::Extras`] with the given values, falling back to +/// [`MarkerExpression::Arbitrary`] on failure. +fn parse_extras_expr( + operator: MarkerOperator, + value: &str, + reporter: &mut impl Reporter, +) -> Option { + let name = match ExtraName::from_str(value) { + Ok(name) => MarkerValueExtra::Extra(name), + Err(err) => { + reporter.report( + MarkerWarningKind::ExtrasInvalidComparison, + format!("Expected extra name (found `{value}`): {err}"), + ); + MarkerValueExtra::Arbitrary(value.to_string()) + } + }; + + if let Some(operator) = ContainerOperator::from_marker_operator(operator) { + return Some(MarkerExpression::Extras { operator, name }); + } + + reporter.report( + MarkerWarningKind::ExtrasInvalidComparison, + "Comparing `extras` with any operator other than `in` or `not in` is wrong and will be ignored" + .to_string(), + ); + + None +} + +/// Creates an instance of [`MarkerExpression::DependencyGroups`] with the given values, falling +/// back to [`MarkerExpression::Arbitrary`] on failure. +fn parse_dependency_groups_expr( + operator: MarkerOperator, + value: &str, + reporter: &mut impl Reporter, +) -> Option { + let name = match GroupName::from_str(value) { + Ok(name) => MarkerValueDependencyGroup::Group(name), + Err(err) => { + reporter.report( + MarkerWarningKind::ExtrasInvalidComparison, + format!("Expected extra name (found `{value}`): {err}"), + ); + MarkerValueDependencyGroup::Arbitrary(value.to_string()) + } + }; + + if let Some(operator) = ContainerOperator::from_marker_operator(operator) { + return Some(MarkerExpression::DependencyGroups { operator, name }); + } + + reporter.report( + MarkerWarningKind::ExtrasInvalidComparison, + "Comparing `extras` with any operator other than `in` or `not in` is wrong and will be ignored" .to_string(), ); diff --git a/crates/uv-pep508/src/marker/simplify.rs b/crates/uv-pep508/src/marker/simplify.rs index 3dc03693a..6897615c4 100644 --- a/crates/uv-pep508/src/marker/simplify.rs +++ b/crates/uv-pep508/src/marker/simplify.rs @@ -9,6 +9,7 @@ use version_ranges::Ranges; use uv_pep440::{Version, VersionSpecifier}; +use crate::marker::tree::ContainerOperator; use crate::{ExtraOperator, MarkerExpression, MarkerOperator, MarkerTree, MarkerTreeKind}; /// Returns a simplified DNF expression for a given marker tree. @@ -174,6 +175,42 @@ fn collect_dnf( operator, }; + path.push(expr); + collect_dnf(tree, dnf, path); + path.pop(); + } + } + MarkerTreeKind::Extras(marker) => { + for (value, tree) in marker.children() { + let operator = if value { + ContainerOperator::In + } else { + ContainerOperator::NotIn + }; + + let expr = MarkerExpression::Extras { + name: marker.name().clone().into(), + operator, + }; + + path.push(expr); + collect_dnf(tree, dnf, path); + path.pop(); + } + } + MarkerTreeKind::DependencyGroups(marker) => { + for (value, tree) in marker.children() { + let operator = if value { + ContainerOperator::In + } else { + ContainerOperator::NotIn + }; + + let expr = MarkerExpression::DependencyGroups { + name: marker.name().clone().into(), + operator, + }; + path.push(expr); collect_dnf(tree, dnf, path); path.pop(); @@ -440,5 +477,27 @@ fn is_negation(left: &MarkerExpression, right: &MarkerExpression) -> bool { name == name2 && operator.negate() == *operator2 } + MarkerExpression::Extras { name, operator } => { + let MarkerExpression::Extras { + name: name2, + operator: operator2, + } = right + else { + return false; + }; + + name == name2 && *operator == operator2.negate() + } + MarkerExpression::DependencyGroups { name, operator } => { + let MarkerExpression::DependencyGroups { + name: name2, + operator: operator2, + } = right + else { + return false; + }; + + name == name2 && *operator == operator2.negate() + } } } diff --git a/crates/uv-pep508/src/marker/tree.rs b/crates/uv-pep508/src/marker/tree.rs index 5739d7c98..594b81723 100644 --- a/crates/uv-pep508/src/marker/tree.rs +++ b/crates/uv-pep508/src/marker/tree.rs @@ -9,14 +9,15 @@ use itertools::Itertools; use serde::{Deserialize, Deserializer, Serialize, Serializer, de}; use version_ranges::Ranges; -use uv_normalize::ExtraName; +use uv_normalize::{ExtraName, GroupName}; use uv_pep440::{Version, VersionParseError, VersionSpecifier}; use super::algebra::{Edges, INTERNER, NodeId, Variable}; use super::simplify; use crate::cursor::Cursor; use crate::marker::lowering::{ - CanonicalMarkerValueExtra, CanonicalMarkerValueString, CanonicalMarkerValueVersion, + CanonicalMarkerValueDependencyGroup, CanonicalMarkerValueExtra, CanonicalMarkerValueString, + CanonicalMarkerValueVersion, }; use crate::marker::parse; use crate::{ @@ -32,6 +33,12 @@ pub enum MarkerWarningKind { /// Doing an operation other than `==` and `!=` on a quoted string with `extra`, such as /// `extra > "perf"` or `extra == os_name` ExtraInvalidComparison, + /// Doing an operation other than `in` and `not in` on a quoted string with `extra`, such as + /// `extras > "perf"` or `extras == os_name` + ExtrasInvalidComparison, + /// Doing an operation other than `in` and `not in` on a quoted string with `dependency_groups`, + /// such as `dependency_groups > "perf"` or `dependency_groups == os_name` + DependencyGroupsInvalidComparison, /// Comparing a string valued marker and a string lexicographically, such as `"3.9" > "3.10"` LexicographicComparison, /// Comparing two markers, such as `os_name != sys_implementation` @@ -128,8 +135,12 @@ pub enum MarkerValue { MarkerEnvVersion(MarkerValueVersion), /// Those environment markers with an arbitrary string as value such as `sys_platform` MarkerEnvString(MarkerValueString), - /// `extra`. This one is special because it's a list and not env but user given + /// `extra`. This one is special because it's a list, and user-provided Extra, + /// `extras`. This one is special because it's a list, and user-provided + Extras, + /// `dependency_groups`. This one is special because it's a list, and user-provided + DependencyGroups, /// Not a constant, but a user given quoted string with a value inside such as '3.8' or "windows" QuotedString(ArcStr), } @@ -170,6 +181,8 @@ impl FromStr for MarkerValue { "sys_platform" => Self::MarkerEnvString(MarkerValueString::SysPlatform), "sys.platform" => Self::MarkerEnvString(MarkerValueString::SysPlatformDeprecated), "extra" => Self::Extra, + "extras" => Self::Extras, + "dependency_groups" => Self::DependencyGroups, _ => return Err(format!("Invalid key: {s}")), }; Ok(value) @@ -182,6 +195,8 @@ impl Display for MarkerValue { Self::MarkerEnvVersion(marker_value_version) => marker_value_version.fmt(f), Self::MarkerEnvString(marker_value_string) => marker_value_string.fmt(f), Self::Extra => f.write_str("extra"), + Self::Extras => f.write_str("extras"), + Self::DependencyGroups => f.write_str("dependency_groups"), Self::QuotedString(value) => write!(f, "'{value}'"), } } @@ -433,7 +448,7 @@ impl Deref for StringVersion { } } -/// The [`ExtraName`] value used in `extra` markers. +/// The [`ExtraName`] value used in `extra` and `extras` markers. #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] pub enum MarkerValueExtra { /// A valid [`ExtraName`]. @@ -469,6 +484,24 @@ impl Display for MarkerValueExtra { } } +/// The [`GroupName`] value used in `dependency_group` markers. +#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub enum MarkerValueDependencyGroup { + /// A valid [`GroupName`]. + Group(GroupName), + /// An invalid name, preserved as an arbitrary string. + Arbitrary(String), +} + +impl Display for MarkerValueDependencyGroup { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Group(group) => group.fmt(f), + Self::Arbitrary(string) => string.fmt(f), + } + } +} + /// Represents one clause such as `python_version > "3.8"`. #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] #[allow(missing_docs)] @@ -504,8 +537,18 @@ pub enum MarkerExpression { }, /// `extra '...'` or `'...' extra`. Extra { - operator: ExtraOperator, name: MarkerValueExtra, + operator: ExtraOperator, + }, + /// `'...' in extras` + Extras { + name: MarkerValueExtra, + operator: ContainerOperator, + }, + /// `'...' in dependency_groups` + DependencyGroups { + name: MarkerValueDependencyGroup, + operator: ContainerOperator, }, } @@ -520,6 +563,10 @@ pub(crate) enum MarkerExpressionKind { String(MarkerValueString), /// An extra expression, e.g. `extra == '...'`. Extra, + /// An extras expression, e.g. `'...' in extras`. + Extras, + /// A dependency groups expression, e.g. `'...' in dependency_groups`. + DependencyGroups, } /// The operator for an extra expression, either '==' or '!='. @@ -561,6 +608,45 @@ impl Display for ExtraOperator { } } +/// The operator for a container expression, either 'in' or 'not in'. +#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub enum ContainerOperator { + /// `in` + In, + /// `not in` + NotIn, +} + +impl ContainerOperator { + /// Creates a [`ContainerOperator`] from an equivalent [`MarkerOperator`]. + /// + /// Returns `None` if the operator is not supported for containers. + pub(crate) fn from_marker_operator(operator: MarkerOperator) -> Option { + match operator { + MarkerOperator::In => Some(ContainerOperator::In), + MarkerOperator::NotIn => Some(ContainerOperator::NotIn), + _ => None, + } + } + + /// Negates this operator. + pub(crate) fn negate(&self) -> ContainerOperator { + match *self { + ContainerOperator::In => ContainerOperator::NotIn, + ContainerOperator::NotIn => ContainerOperator::In, + } + } +} + +impl Display for ContainerOperator { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Self::In => "in", + Self::NotIn => "not in", + }) + } +} + impl MarkerExpression { /// Parse a [`MarkerExpression`] from a string with the given reporter. pub fn parse_reporter( @@ -600,6 +686,8 @@ impl MarkerExpression { MarkerExpression::VersionIn { key, .. } => MarkerExpressionKind::VersionIn(*key), MarkerExpression::String { key, .. } => MarkerExpressionKind::String(*key), MarkerExpression::Extra { .. } => MarkerExpressionKind::Extra, + MarkerExpression::Extras { .. } => MarkerExpressionKind::Extras, + MarkerExpression::DependencyGroups { .. } => MarkerExpressionKind::DependencyGroups, } } } @@ -641,6 +729,12 @@ impl Display for MarkerExpression { MarkerExpression::Extra { operator, name } => { write!(f, "extra {operator} '{name}'") } + MarkerExpression::Extras { operator, name } => { + write!(f, "'{name}' {operator} extras") + } + MarkerExpression::DependencyGroups { operator, name } => { + write!(f, "'{name}' {operator} dependency_groups") + } } } } @@ -862,6 +956,26 @@ impl MarkerTree { low: low.negate(self.0), }) } + Variable::Extras(name) => { + let Edges::Boolean { low, high } = node.children else { + unreachable!() + }; + MarkerTreeKind::Extras(ExtrasMarkerTree { + name, + high: high.negate(self.0), + low: low.negate(self.0), + }) + } + Variable::DependencyGroups(name) => { + let Edges::Boolean { low, high } = node.children else { + unreachable!() + }; + MarkerTreeKind::DependencyGroups(DependencyGroupsMarkerTree { + name, + high: high.negate(self.0), + low: low.negate(self.0), + }) + } } } @@ -962,6 +1076,10 @@ impl MarkerTree { .edge(extras.contains(marker.name().extra())) .evaluate_reporter_impl(env, extras, reporter); } + // TODO(charlie): Add support for evaluating container extras in PEP 751 lockfiles. + MarkerTreeKind::Extras(..) | MarkerTreeKind::DependencyGroups(..) => { + return false; + } } false @@ -989,6 +1107,12 @@ impl MarkerTree { MarkerTreeKind::Extra(marker) => marker .edge(extras.contains(marker.name().extra())) .evaluate_extras(extras), + MarkerTreeKind::Extras(marker) => marker + .children() + .any(|(_, tree)| tree.evaluate_extras(extras)), + MarkerTreeKind::DependencyGroups(marker) => marker + .children() + .any(|(_, tree)| tree.evaluate_extras(extras)), } } @@ -1226,6 +1350,16 @@ impl MarkerTree { imp(tree, f); } } + MarkerTreeKind::Extras(kind) => { + for (_, tree) in kind.children() { + imp(tree, f); + } + } + MarkerTreeKind::DependencyGroups(kind) => { + for (_, tree) in kind.children() { + imp(tree, f); + } + } } } imp(self, &mut f); @@ -1348,6 +1482,36 @@ impl MarkerTree { write!(f, "extra != {} -> ", kind.name())?; kind.edge(false).fmt_graph(f, level + 1)?; } + MarkerTreeKind::Extras(kind) => { + writeln!(f)?; + for _ in 0..level { + write!(f, " ")?; + } + write!(f, "{} in extras -> ", kind.name())?; + kind.edge(true).fmt_graph(f, level + 1)?; + + writeln!(f)?; + for _ in 0..level { + write!(f, " ")?; + } + write!(f, "{} not in extras -> ", kind.name())?; + kind.edge(false).fmt_graph(f, level + 1)?; + } + MarkerTreeKind::DependencyGroups(kind) => { + writeln!(f)?; + for _ in 0..level { + write!(f, " ")?; + } + write!(f, "{} in dependency_groups -> ", kind.name())?; + kind.edge(true).fmt_graph(f, level + 1)?; + + writeln!(f)?; + for _ in 0..level { + write!(f, " ")?; + } + write!(f, "{} not in dependency_groups -> ", kind.name())?; + kind.edge(false).fmt_graph(f, level + 1)?; + } } Ok(()) @@ -1417,8 +1581,12 @@ pub enum MarkerTreeKind<'a> { In(InMarkerTree<'a>), /// A string expression with the `contains` operator. Contains(ContainsMarkerTree<'a>), - /// A string expression. + /// A string expression (e.g., `extra == 'dev'`). Extra(ExtraMarkerTree<'a>), + /// A string expression (e.g., `'dev' in extras`). + Extras(ExtrasMarkerTree<'a>), + /// A string expression (e.g., `'dev' in dependency_groups`). + DependencyGroups(DependencyGroupsMarkerTree<'a>), } /// A version marker node, such as `python_version < '3.7'`. @@ -1636,6 +1804,93 @@ impl Ord for ExtraMarkerTree<'_> { } } +/// A node representing the existence or absence of a given extra, such as `'bar' in extras`. +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct ExtrasMarkerTree<'a> { + name: &'a CanonicalMarkerValueExtra, + high: NodeId, + low: NodeId, +} + +impl ExtrasMarkerTree<'_> { + /// Returns the name of the extra in this expression. + pub fn name(&self) -> &CanonicalMarkerValueExtra { + self.name + } + + /// The edges of this node, corresponding to the boolean evaluation of the expression. + pub fn children(&self) -> impl Iterator { + [(true, MarkerTree(self.high)), (false, MarkerTree(self.low))].into_iter() + } + + /// Returns the subtree associated with the given edge value. + pub fn edge(&self, value: bool) -> MarkerTree { + if value { + MarkerTree(self.high) + } else { + MarkerTree(self.low) + } + } +} + +impl PartialOrd for ExtrasMarkerTree<'_> { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for ExtrasMarkerTree<'_> { + fn cmp(&self, other: &Self) -> Ordering { + self.name() + .cmp(other.name()) + .then_with(|| self.children().cmp(other.children())) + } +} + +/// A node representing the existence or absence of a given dependency group, such as +/// `'bar' in dependency_groups`. +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct DependencyGroupsMarkerTree<'a> { + name: &'a CanonicalMarkerValueDependencyGroup, + high: NodeId, + low: NodeId, +} + +impl DependencyGroupsMarkerTree<'_> { + /// Returns the name of the group in this expression. + pub fn name(&self) -> &CanonicalMarkerValueDependencyGroup { + self.name + } + + /// The edges of this node, corresponding to the boolean evaluation of the expression. + pub fn children(&self) -> impl Iterator { + [(true, MarkerTree(self.high)), (false, MarkerTree(self.low))].into_iter() + } + + /// Returns the subtree associated with the given edge value. + pub fn edge(&self, value: bool) -> MarkerTree { + if value { + MarkerTree(self.high) + } else { + MarkerTree(self.low) + } + } +} + +impl PartialOrd for DependencyGroupsMarkerTree<'_> { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for DependencyGroupsMarkerTree<'_> { + fn cmp(&self, other: &Self) -> Ordering { + self.name() + .cmp(other.name()) + .then_with(|| self.children().cmp(other.children())) + } +} + /// A marker tree that contains at least one expression. /// /// See [`MarkerTree::contents`] for details. diff --git a/crates/uv-resolver/src/marker.rs b/crates/uv-resolver/src/marker.rs index b63d51401..5a2203f9b 100644 --- a/crates/uv-resolver/src/marker.rs +++ b/crates/uv-resolver/src/marker.rs @@ -54,6 +54,16 @@ pub(crate) fn requires_python(tree: MarkerTree) -> Option { collect_python_markers(tree, markers, range); } } + MarkerTreeKind::Extras(marker) => { + for (_, tree) in marker.children() { + collect_python_markers(tree, markers, range); + } + } + MarkerTreeKind::DependencyGroups(marker) => { + for (_, tree) in marker.children() { + collect_python_markers(tree, markers, range); + } + } } } diff --git a/crates/uv-resolver/src/resolution/output.rs b/crates/uv-resolver/src/resolution/output.rs index dd2b3388f..2afbf2c6b 100644 --- a/crates/uv-resolver/src/resolution/output.rs +++ b/crates/uv-resolver/src/resolution/output.rs @@ -698,6 +698,16 @@ impl ResolverOutput { add_marker_params_from_tree(tree, set); } } + MarkerTreeKind::Extras(marker) => { + for (_, tree) in marker.children() { + add_marker_params_from_tree(tree, set); + } + } + MarkerTreeKind::DependencyGroups(marker) => { + for (_, tree) in marker.children() { + add_marker_params_from_tree(tree, set); + } + } } } From bd4c7ff860a844a907fbe1ce9723c6ae04a4ede9 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sun, 20 Jul 2025 14:13:27 -0400 Subject: [PATCH 275/349] Move dependency group normalization into specification (#14757) ## Summary A refactor that I'm extracting from #14755. There should be no functional changes, but the core idea is to postpone filling in the default `path` for a dependency group until we make the specification. This allows us to use the groups for the `pylock.toml` in the future, if such a `pylock.toml` is provided. --- crates/uv-normalize/src/group_name.rs | 13 +------ crates/uv-requirements/src/specification.rs | 36 +++++++++++++++--- crates/uv/src/commands/pip/compile.rs | 13 ++++--- crates/uv/src/commands/pip/install.rs | 10 ++--- crates/uv/src/commands/pip/operations.rs | 20 ++++------ crates/uv/src/commands/pip/sync.rs | 4 +- crates/uv/src/commands/project/add.rs | 2 +- crates/uv/src/commands/tool/install.rs | 3 +- crates/uv/src/commands/tool/run.rs | 3 +- crates/uv/src/lib.rs | 41 +++++---------------- 10 files changed, 65 insertions(+), 80 deletions(-) diff --git a/crates/uv-normalize/src/group_name.rs b/crates/uv-normalize/src/group_name.rs index 6b9ab14bd..e0a2b7c1f 100644 --- a/crates/uv-normalize/src/group_name.rs +++ b/crates/uv-normalize/src/group_name.rs @@ -1,5 +1,5 @@ use std::fmt::{Display, Formatter}; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::str::FromStr; use std::sync::LazyLock; @@ -98,17 +98,6 @@ pub struct PipGroupName { pub name: GroupName, } -impl PipGroupName { - /// Gets the path to use, applying the default if it's missing - pub fn path(&self) -> &Path { - if let Some(path) = &self.path { - path - } else { - Path::new("pyproject.toml") - } - } -} - impl FromStr for PipGroupName { type Err = InvalidPipGroupError; diff --git a/crates/uv-requirements/src/specification.rs b/crates/uv-requirements/src/specification.rs index 4c5741392..deead2c82 100644 --- a/crates/uv-requirements/src/specification.rs +++ b/crates/uv-requirements/src/specification.rs @@ -43,7 +43,7 @@ use uv_distribution_types::{ UnresolvedRequirementSpecification, }; use uv_fs::{CWD, Simplified}; -use uv_normalize::{ExtraName, GroupName, PackageName}; +use uv_normalize::{ExtraName, PackageName, PipGroupName}; use uv_requirements_txt::{RequirementsTxt, RequirementsTxtRequirement}; use uv_warnings::warn_user; use uv_workspace::pyproject::PyProjectToml; @@ -215,7 +215,7 @@ impl RequirementsSpecification { requirements: &[RequirementsSource], constraints: &[RequirementsSource], overrides: &[RequirementsSource], - groups: BTreeMap>, + groups: Option<&GroupsSpecification>, client_builder: &BaseClientBuilder<'_>, ) -> Result { let mut spec = Self::default(); @@ -272,7 +272,7 @@ impl RequirementsSpecification { "Cannot specify constraints with a `pylock.toml` file" )); } - if !groups.is_empty() { + if groups.is_some_and(|groups| !groups.groups.is_empty()) { return Err(anyhow::anyhow!( "Cannot specify groups with a `pylock.toml` file" )); @@ -287,9 +287,24 @@ impl RequirementsSpecification { } // pip `--group` flags specify their own sources, which we need to process here - if !groups.is_empty() { + if let Some(groups) = groups { + // First, we collect all groups by their path. + let mut groups_by_path = BTreeMap::new(); + for group in &groups.groups { + // If there's no path provided, expect a pyproject.toml in the project-dir + // (Which is typically the current working directory, matching pip's behaviour) + let pyproject_path = group + .path + .clone() + .unwrap_or_else(|| groups.root.join("pyproject.toml")); + groups_by_path + .entry(pyproject_path) + .or_insert_with(Vec::new) + .push(group.name.clone()); + } + let mut group_specs = BTreeMap::new(); - for (path, groups) in groups { + for (path, groups) in groups_by_path { let group_spec = DependencyGroups::from_args( false, false, @@ -426,7 +441,7 @@ impl RequirementsSpecification { requirements: &[RequirementsSource], client_builder: &BaseClientBuilder<'_>, ) -> Result { - Self::from_sources(requirements, &[], &[], BTreeMap::default(), client_builder).await + Self::from_sources(requirements, &[], &[], None, client_builder).await } /// Initialize a [`RequirementsSpecification`] from a list of [`Requirement`]. @@ -485,3 +500,12 @@ impl RequirementsSpecification { self.requirements.is_empty() && self.source_trees.is_empty() && self.overrides.is_empty() } } + +#[derive(Debug, Default, Clone)] +pub struct GroupsSpecification { + /// The path to the project root, relative to which the default `pyproject.toml` file is + /// located. + pub root: PathBuf, + /// The enabled groups. + pub groups: Vec, +} diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs index a5116327b..b9dda45c8 100644 --- a/crates/uv/src/commands/pip/compile.rs +++ b/crates/uv/src/commands/pip/compile.rs @@ -1,7 +1,7 @@ -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeSet; use std::env; use std::ffi::OsStr; -use std::path::{Path, PathBuf}; +use std::path::Path; use std::str::FromStr; use anyhow::{Result, anyhow}; @@ -26,7 +26,7 @@ use uv_distribution_types::{ use uv_fs::{CWD, Simplified}; use uv_git::ResolvedRepositoryReference; use uv_install_wheel::LinkMode; -use uv_normalize::{GroupName, PackageName}; +use uv_normalize::PackageName; use uv_pypi_types::{Conflicts, SupportedEnvironments}; use uv_python::{ EnvironmentPreference, PythonEnvironment, PythonInstallation, PythonPreference, PythonRequest, @@ -34,7 +34,8 @@ use uv_python::{ }; use uv_requirements::upgrade::{LockedRequirements, read_pylock_toml_requirements}; use uv_requirements::{ - RequirementsSource, RequirementsSpecification, is_pylock_toml, upgrade::read_requirements_txt, + GroupsSpecification, RequirementsSource, RequirementsSpecification, is_pylock_toml, + upgrade::read_requirements_txt, }; use uv_resolver::{ AnnotationStyle, DependencyMode, DisplayResolutionGraph, ExcludeNewer, FlatIndex, ForkStrategy, @@ -64,7 +65,7 @@ pub(crate) async fn pip_compile( build_constraints_from_workspace: Vec, environments: SupportedEnvironments, extras: ExtrasSpecification, - groups: BTreeMap>, + groups: GroupsSpecification, output_file: Option<&Path>, format: Option, resolution_mode: ResolutionMode, @@ -207,7 +208,7 @@ pub(crate) async fn pip_compile( requirements, constraints, overrides, - groups, + Some(&groups), &client_builder, ) .await?; diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs index 79e18bd98..b9edad20e 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -1,6 +1,5 @@ -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeSet; use std::fmt::Write; -use std::path::PathBuf; use anyhow::Context; use itertools::Itertools; @@ -23,14 +22,13 @@ use uv_distribution_types::{ use uv_fs::Simplified; use uv_install_wheel::LinkMode; use uv_installer::{SatisfiesResult, SitePackages}; -use uv_normalize::GroupName; use uv_pep508::PackageName; use uv_pypi_types::Conflicts; use uv_python::{ EnvironmentPreference, Prefix, PythonEnvironment, PythonInstallation, PythonPreference, PythonRequest, PythonVersion, Target, }; -use uv_requirements::{RequirementsSource, RequirementsSpecification}; +use uv_requirements::{GroupsSpecification, RequirementsSource, RequirementsSpecification}; use uv_resolver::{ DependencyMode, ExcludeNewer, FlatIndex, OptionsBuilder, PrereleaseMode, PylockToml, PythonRequirement, ResolutionMode, ResolverEnvironment, @@ -59,7 +57,7 @@ pub(crate) async fn pip_install( overrides_from_workspace: Vec, build_constraints_from_workspace: Vec, extras: &ExtrasSpecification, - groups: BTreeMap>, + groups: &GroupsSpecification, resolution_mode: ResolutionMode, prerelease_mode: PrereleaseMode, dependency_mode: DependencyMode, @@ -128,7 +126,7 @@ pub(crate) async fn pip_install( constraints, overrides, extras, - groups, + Some(groups), &client_builder, ) .await?; diff --git a/crates/uv/src/commands/pip/operations.rs b/crates/uv/src/commands/pip/operations.rs index 117321c14..809f8bfdc 100644 --- a/crates/uv/src/commands/pip/operations.rs +++ b/crates/uv/src/commands/pip/operations.rs @@ -27,14 +27,14 @@ use uv_distribution_types::{ use uv_fs::Simplified; use uv_install_wheel::LinkMode; use uv_installer::{Plan, Planner, Preparer, SitePackages}; -use uv_normalize::{GroupName, PackageName}; +use uv_normalize::PackageName; use uv_pep508::{MarkerEnvironment, RequirementOrigin}; use uv_platform_tags::Tags; use uv_pypi_types::{Conflicts, ResolverMarkerEnvironment}; use uv_python::{PythonEnvironment, PythonInstallation}; use uv_requirements::{ - LookaheadResolver, NamedRequirementsResolver, RequirementsSource, RequirementsSpecification, - SourceTreeResolver, + GroupsSpecification, LookaheadResolver, NamedRequirementsResolver, RequirementsSource, + RequirementsSpecification, SourceTreeResolver, }; use uv_resolver::{ DependencyMode, Exclusions, FlatIndex, InMemoryIndex, Manifest, Options, Preference, @@ -55,7 +55,7 @@ pub(crate) async fn read_requirements( constraints: &[RequirementsSource], overrides: &[RequirementsSource], extras: &ExtrasSpecification, - groups: BTreeMap>, + groups: Option<&GroupsSpecification>, client_builder: &BaseClientBuilder<'_>, ) -> Result { // If the user requests `extras` but does not provide a valid source (e.g., a `pyproject.toml`), @@ -91,15 +91,11 @@ pub(crate) async fn read_constraints( constraints: &[RequirementsSource], client_builder: &BaseClientBuilder<'_>, ) -> Result, Error> { - Ok(RequirementsSpecification::from_sources( - &[], - constraints, - &[], - BTreeMap::default(), - client_builder, + Ok( + RequirementsSpecification::from_sources(&[], constraints, &[], None, client_builder) + .await? + .constraints, ) - .await? - .constraints) } /// Resolve a set of requirements, similar to running `pip compile`. diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index 61999825e..47d180a74 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -1,4 +1,4 @@ -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeSet; use std::fmt::Write; use anyhow::{Context, Result}; @@ -92,7 +92,7 @@ pub(crate) async fn pip_sync( // Initialize a few defaults. let overrides = &[]; let extras = ExtrasSpecification::default(); - let groups = BTreeMap::default(); + let groups = None; let upgrade = Upgrade::default(); let resolution_mode = ResolutionMode::default(); let prerelease_mode = PrereleaseMode::default(); diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index 12535f859..4bf5905d2 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -345,7 +345,7 @@ pub(crate) async fn add( &requirements, &constraints, &[], - BTreeMap::default(), + None, &client_builder, ) .await?; diff --git a/crates/uv/src/commands/tool/install.rs b/crates/uv/src/commands/tool/install.rs index 27f18abe4..12de5fd1f 100644 --- a/crates/uv/src/commands/tool/install.rs +++ b/crates/uv/src/commands/tool/install.rs @@ -1,4 +1,3 @@ -use std::collections::BTreeMap; use std::fmt::Write; use std::str::FromStr; @@ -261,7 +260,7 @@ pub(crate) async fn install( with, constraints, overrides, - BTreeMap::default(), + None, &client_builder, ) .await?; diff --git a/crates/uv/src/commands/tool/run.rs b/crates/uv/src/commands/tool/run.rs index a1faa1153..7c91b9fe9 100644 --- a/crates/uv/src/commands/tool/run.rs +++ b/crates/uv/src/commands/tool/run.rs @@ -1,4 +1,3 @@ -use std::collections::BTreeMap; use std::fmt::Display; use std::fmt::Write; use std::path::Path; @@ -871,7 +870,7 @@ async fn get_or_create_environment( with, constraints, overrides, - BTreeMap::default(), + None, &client_builder, ) .await?; diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 0f6c9465f..6ca03a470 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -1,5 +1,4 @@ use std::borrow::Cow; -use std::collections::BTreeMap; use std::ffi::OsString; use std::fmt::Write; use std::io::stdout; @@ -36,7 +35,7 @@ use uv_pep440::release_specifiers_to_ranges; use uv_pep508::VersionOrUrl; use uv_pypi_types::{ParsedDirectoryUrl, ParsedUrl}; use uv_python::PythonRequest; -use uv_requirements::RequirementsSource; +use uv_requirements::{GroupsSpecification, RequirementsSource}; use uv_requirements_txt::RequirementsTxtRequirement; use uv_scripts::{Pep723Error, Pep723Item, Pep723ItemRef, Pep723Metadata, Pep723Script}; use uv_settings::{Combine, EnvironmentOptions, FilesystemOptions, Options}; @@ -472,20 +471,10 @@ async fn run(mut cli: Cli) -> Result { .into_iter() .map(RequirementsSource::from_constraints_txt) .collect::, _>>()?; - - let mut groups = BTreeMap::new(); - for group in args.settings.groups { - // If there's no path provided, expect a pyproject.toml in the project-dir - // (Which is typically the current working directory, matching pip's behaviour) - let pyproject_path = group - .path - .clone() - .unwrap_or_else(|| project_dir.join("pyproject.toml")); - groups - .entry(pyproject_path) - .or_insert_with(Vec::new) - .push(group.name.clone()); - } + let groups = GroupsSpecification { + root: project_dir.to_path_buf(), + groups: args.settings.groups, + }; commands::pip_compile( &requirements, @@ -657,20 +646,10 @@ async fn run(mut cli: Cli) -> Result { .into_iter() .map(RequirementsSource::from_overrides_txt) .collect::, _>>()?; - - let mut groups = BTreeMap::new(); - for group in args.settings.groups { - // If there's no path provided, expect a pyproject.toml in the project-dir - // (Which is typically the current working directory, matching pip's behaviour) - let pyproject_path = group - .path - .clone() - .unwrap_or_else(|| project_dir.join("pyproject.toml")); - groups - .entry(pyproject_path) - .or_insert_with(Vec::new) - .push(group.name.clone()); - } + let groups = GroupsSpecification { + root: project_dir.to_path_buf(), + groups: args.settings.groups, + }; // Special-case: any source trees specified on the command-line are automatically // reinstalled. This matches user expectations: `uv pip install .` should always @@ -730,7 +709,7 @@ async fn run(mut cli: Cli) -> Result { args.overrides_from_workspace, args.build_constraints_from_workspace, &args.settings.extras, - groups, + &groups, args.settings.resolution, args.settings.prerelease, args.settings.dependency_mode, From d85a300b5f456a26dc1ac780e7ed8da9d39b7566 Mon Sep 17 00:00:00 2001 From: Matt Norton Date: Sun, 20 Jul 2025 22:27:33 +0100 Subject: [PATCH 276/349] Fix typo in `concepts/projects/config.md` (#14759) --- docs/concepts/projects/config.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/concepts/projects/config.md b/docs/concepts/projects/config.md index 34b62c01a..b2eafd36a 100644 --- a/docs/concepts/projects/config.md +++ b/docs/concepts/projects/config.md @@ -367,9 +367,9 @@ in the deployed environment without a dependency on the originating source code. ## Conflicting dependencies -uv requires resolves all project dependencies together, including optional dependencies ("extras") -and dependency groups. If dependencies declared in one section are not compatible with those in -another section, uv will fail to resolve the requirements of the project with an error. +uv resolves all project dependencies together, including optional dependencies ("extras") and +dependency groups. If dependencies declared in one section are not compatible with those in another +section, uv will fail to resolve the requirements of the project with an error. uv supports explicit declaration of conflicting dependency groups. For example, to declare that the `optional-dependency` groups `extra1` and `extra2` are incompatible: From fcf0bdd3a6675df6be5b5e4c8420f3cdea4d0ce6 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sun, 20 Jul 2025 17:38:24 -0400 Subject: [PATCH 277/349] Add missing `the` in concept link (#14763) --- docs/getting-started/features.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started/features.md b/docs/getting-started/features.md index ed34dd8b2..c78f5f560 100644 --- a/docs/getting-started/features.md +++ b/docs/getting-started/features.md @@ -104,6 +104,6 @@ self-update: ## Next steps -Read the [guides](../guides/index.md) for an introduction to each feature, check out +Read the [guides](../guides/index.md) for an introduction to each feature, check out the [concept](../concepts/index.md) pages for in-depth details about uv's features, or learn how to [get help](./help.md) if you run into any problems. From 9923f42c2eb03debc9bf7461b61ea64ae5b081a1 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sun, 20 Jul 2025 17:38:50 -0400 Subject: [PATCH 278/349] Fix kebab casing of README variants in build backend (#14762) ## Summary In this context, `rename_all` only applies to the variants, not their fields. Closes #14761. --- crates/uv-build-backend/src/metadata.rs | 63 ++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/crates/uv-build-backend/src/metadata.rs b/crates/uv-build-backend/src/metadata.rs index 296c76a2b..5997f72b6 100644 --- a/crates/uv-build-backend/src/metadata.rs +++ b/crates/uv-build-backend/src/metadata.rs @@ -703,7 +703,7 @@ struct Project { /// The optional `project.readme` key in a pyproject.toml as specified in /// . #[derive(Deserialize, Debug, Clone)] -#[serde(untagged, rename_all = "kebab-case")] +#[serde(untagged, rename_all_fields = "kebab-case")] pub(crate) enum Readme { /// Relative path to the README. String(PathBuf), @@ -713,7 +713,7 @@ pub(crate) enum Readme { content_type: String, charset: Option, }, - /// The full description of the project as inline value. + /// The full description of the project as an inline value. Text { text: String, content_type: String, @@ -965,6 +965,65 @@ mod tests { "###); } + #[test] + fn readme() { + let temp_dir = TempDir::new().unwrap(); + + fs_err::write( + temp_dir.path().join("Readme.md"), + indoc! {r" + # Foo + + This is the foo library. + "}, + ) + .unwrap(); + + fs_err::write( + temp_dir.path().join("License.txt"), + indoc! {r#" + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE + OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + "#}, + ) + .unwrap(); + + let contents = indoc! {r#" + # See https://github.com/pypa/sampleproject/blob/main/pyproject.toml for another example + + [project] + name = "hello-world" + version = "0.1.0" + description = "A Python package" + readme = { file = "Readme.md", content-type = "text/markdown" } + requires_python = ">=3.12" + + [build-system] + requires = ["uv_build>=0.4.15,<0.5"] + build-backend = "uv_build" + "# + }; + + let pyproject_toml = PyProjectToml::parse(contents).unwrap(); + let metadata = pyproject_toml.to_metadata(temp_dir.path()).unwrap(); + + assert_snapshot!(metadata.core_metadata_format(), @r" + Metadata-Version: 2.3 + Name: hello-world + Version: 0.1.0 + Summary: A Python package + Description-Content-Type: text/markdown + + # Foo + + This is the foo library. + "); + } + #[test] fn self_extras() { let temp_dir = TempDir::new().unwrap(); From 5e2047b253718bb58d7d373f38e6176d7f154afa Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sun, 20 Jul 2025 18:17:07 -0400 Subject: [PATCH 279/349] Implement `PartialEq` for `OptionSet` (#14765) Closes https://github.com/astral-sh/uv/issues/14737. --- crates/uv-options-metadata/src/lib.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/uv-options-metadata/src/lib.rs b/crates/uv-options-metadata/src/lib.rs index 6e966cfc4..4c0a5c322 100644 --- a/crates/uv-options-metadata/src/lib.rs +++ b/crates/uv-options-metadata/src/lib.rs @@ -69,12 +69,20 @@ impl Display for OptionEntry { /// /// It extracts the options by calling the [`OptionsMetadata::record`] of a type implementing /// [`OptionsMetadata`]. -#[derive(Copy, Clone, Eq, PartialEq)] +#[derive(Copy, Clone)] pub struct OptionSet { record: fn(&mut dyn Visit), doc: fn() -> Option<&'static str>, } +impl PartialEq for OptionSet { + fn eq(&self, other: &Self) -> bool { + std::ptr::fn_addr_eq(self.record, other.record) && std::ptr::fn_addr_eq(self.doc, other.doc) + } +} + +impl Eq for OptionSet {} + impl OptionSet { pub fn of() -> Self where From dbe6a214862d4bd86ff5eecd91921ee77848e610 Mon Sep 17 00:00:00 2001 From: konsti Date: Mon, 21 Jul 2025 00:28:34 +0200 Subject: [PATCH 280/349] Retry request on invalid data error (#14703) I also improved the trace logging. Fixes #14699 --- crates/uv-client/src/base_client.rs | 30 ++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/crates/uv-client/src/base_client.rs b/crates/uv-client/src/base_client.rs index 9ddc30e75..d901f57e7 100644 --- a/crates/uv-client/src/base_client.rs +++ b/crates/uv-client/src/base_client.rs @@ -920,18 +920,34 @@ pub fn is_extended_transient_error(err: &dyn Error) -> bool { } // IO Errors may be nested through custom IO errors. + let mut has_io_error = false; for io_err in find_sources::(&err) { - if io_err.kind() == io::ErrorKind::ConnectionReset - || io_err.kind() == io::ErrorKind::UnexpectedEof - || io_err.kind() == io::ErrorKind::BrokenPipe - { - trace!("Retrying error: `ConnectionReset` or `UnexpectedEof`"); + has_io_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/9246 + io::ErrorKind::UnexpectedEof, + ]; + if retryable_io_err_kinds.contains(&io_err.kind()) { + trace!("Retrying error: `{}`", io_err.kind()); return true; } - trace!("Cannot retry IO error: not one of `ConnectionReset` or `UnexpectedEof`"); + trace!( + "Cannot retry IO error `{}`, not a retryable IO error kind", + io_err.kind() + ); } - trace!("Cannot retry error: not an IO error"); + if !has_io_error { + trace!("Cannot retry error: not an extended IO error"); + } false } From a42a2846e662c68785ccc29e583e1bf9227bec85 Mon Sep 17 00:00:00 2001 From: Aria Desires Date: Sun, 20 Jul 2025 18:54:50 -0400 Subject: [PATCH 281/349] Make warnings about masked `[tool.uv]` fields more precise (#14325) This is the second half of #14308 --- crates/uv-settings/src/lib.rs | 254 +++++++++++++++++++++++++++- crates/uv/tests/it/show_settings.rs | 233 ++++++++++++++++++++++++- 2 files changed, 482 insertions(+), 5 deletions(-) diff --git a/crates/uv-settings/src/lib.rs b/crates/uv-settings/src/lib.rs index 84aef8f28..4dd4c392f 100644 --- a/crates/uv-settings/src/lib.rs +++ b/crates/uv-settings/src/lib.rs @@ -120,10 +120,9 @@ impl FilesystemOptions { .ok() .and_then(|content| toml::from_str::(&content).ok()) { - if pyproject.tool.is_some_and(|tool| tool.uv.is_some()) { - warn_user!( - "Found both a `uv.toml` file and a `[tool.uv]` section in an adjacent `pyproject.toml`. The `[tool.uv]` section will be ignored in favor of the `uv.toml` file." - ); + if let Some(options) = pyproject.tool.as_ref().and_then(|tool| tool.uv.as_ref()) + { + warn_uv_toml_masked_fields(options); } } @@ -269,6 +268,253 @@ fn validate_uv_toml(path: &Path, options: &Options) -> Result<(), Error> { Ok(()) } +/// Validate that an [`Options`] contains no fields that `uv.toml` would mask +/// +/// This is essentially the inverse of [`validated_uv_toml`][]. +fn warn_uv_toml_masked_fields(options: &Options) { + let Options { + globals: + GlobalOptions { + required_version, + native_tls, + offline, + no_cache, + cache_dir, + preview, + python_preference, + python_downloads, + concurrent_downloads, + concurrent_builds, + concurrent_installs, + allow_insecure_host, + }, + top_level: + ResolverInstallerOptions { + index, + index_url, + extra_index_url, + no_index, + find_links, + index_strategy, + keyring_provider, + resolution, + prerelease, + fork_strategy, + dependency_metadata, + config_settings, + no_build_isolation, + no_build_isolation_package, + exclude_newer, + link_mode, + compile_bytecode, + no_sources, + upgrade, + upgrade_package, + reinstall, + reinstall_package, + no_build, + no_build_package, + no_binary, + no_binary_package, + }, + install_mirrors: + PythonInstallMirrors { + python_install_mirror, + pypy_install_mirror, + python_downloads_json_url, + }, + publish: + PublishOptions { + publish_url, + trusted_publishing, + check_url, + }, + add: AddOptions { add_bounds }, + pip, + cache_keys, + override_dependencies, + constraint_dependencies, + build_constraint_dependencies, + environments, + required_environments, + conflicts: _, + workspace: _, + sources: _, + dev_dependencies: _, + default_groups: _, + dependency_groups: _, + managed: _, + package: _, + build_backend: _, + } = options; + + let mut masked_fields = vec![]; + + if required_version.is_some() { + masked_fields.push("required-version"); + } + if native_tls.is_some() { + masked_fields.push("native-tls"); + } + if offline.is_some() { + masked_fields.push("offline"); + } + if no_cache.is_some() { + masked_fields.push("no-cache"); + } + if cache_dir.is_some() { + masked_fields.push("cache-dir"); + } + if preview.is_some() { + masked_fields.push("preview"); + } + if python_preference.is_some() { + masked_fields.push("python-preference"); + } + if python_downloads.is_some() { + masked_fields.push("python-downloads"); + } + if concurrent_downloads.is_some() { + masked_fields.push("concurrent-downloads"); + } + if concurrent_builds.is_some() { + masked_fields.push("concurrent-builds"); + } + if concurrent_installs.is_some() { + masked_fields.push("concurrent-installs"); + } + if allow_insecure_host.is_some() { + masked_fields.push("allow-insecure-host"); + } + if index.is_some() { + masked_fields.push("index"); + } + if index_url.is_some() { + masked_fields.push("index-url"); + } + if extra_index_url.is_some() { + masked_fields.push("extra-index-url"); + } + if no_index.is_some() { + masked_fields.push("no-index"); + } + if find_links.is_some() { + masked_fields.push("find-links"); + } + if index_strategy.is_some() { + masked_fields.push("index-strategy"); + } + if keyring_provider.is_some() { + masked_fields.push("keyring-provider"); + } + if resolution.is_some() { + masked_fields.push("resolution"); + } + if prerelease.is_some() { + masked_fields.push("prerelease"); + } + if fork_strategy.is_some() { + masked_fields.push("fork-strategy"); + } + if dependency_metadata.is_some() { + masked_fields.push("dependency-metadata"); + } + if config_settings.is_some() { + masked_fields.push("config-settings"); + } + if no_build_isolation.is_some() { + masked_fields.push("no-build-isolation"); + } + if no_build_isolation_package.is_some() { + masked_fields.push("no-build-isolation-package"); + } + if exclude_newer.is_some() { + masked_fields.push("exclude-newer"); + } + if link_mode.is_some() { + masked_fields.push("link-mode"); + } + if compile_bytecode.is_some() { + masked_fields.push("compile-bytecode"); + } + if no_sources.is_some() { + masked_fields.push("no-sources"); + } + if upgrade.is_some() { + masked_fields.push("upgrade"); + } + if upgrade_package.is_some() { + masked_fields.push("upgrade-package"); + } + if reinstall.is_some() { + masked_fields.push("reinstall"); + } + if reinstall_package.is_some() { + masked_fields.push("reinstall-package"); + } + if no_build.is_some() { + masked_fields.push("no-build"); + } + if no_build_package.is_some() { + masked_fields.push("no-build-package"); + } + if no_binary.is_some() { + masked_fields.push("no-binary"); + } + if no_binary_package.is_some() { + masked_fields.push("no-binary-package"); + } + if python_install_mirror.is_some() { + masked_fields.push("python-install-mirror"); + } + if pypy_install_mirror.is_some() { + masked_fields.push("pypy-install-mirror"); + } + if python_downloads_json_url.is_some() { + masked_fields.push("python-downloads-json-url"); + } + if publish_url.is_some() { + masked_fields.push("publish-url"); + } + if trusted_publishing.is_some() { + masked_fields.push("trusted-publishing"); + } + if check_url.is_some() { + masked_fields.push("check-url"); + } + if add_bounds.is_some() { + masked_fields.push("add-bounds"); + } + if pip.is_some() { + masked_fields.push("pip"); + } + if cache_keys.is_some() { + masked_fields.push("cache_keys"); + } + if override_dependencies.is_some() { + masked_fields.push("override-dependencies"); + } + if constraint_dependencies.is_some() { + masked_fields.push("constraint-dependencies"); + } + if build_constraint_dependencies.is_some() { + masked_fields.push("build-constraint-dependencies"); + } + if environments.is_some() { + masked_fields.push("environments"); + } + if required_environments.is_some() { + masked_fields.push("required-environments"); + } + if !masked_fields.is_empty() { + let field_listing = masked_fields.join("\n- "); + warn_user!( + "Found both a `uv.toml` file and a `[tool.uv]` section in an adjacent `pyproject.toml`. The following fields from `[tool.uv]` will be ignored in favor of the `uv.toml` file:\n- {}", + field_listing, + ); + } +} + #[derive(thiserror::Error, Debug)] pub enum Error { #[error(transparent)] diff --git a/crates/uv/tests/it/show_settings.rs b/crates/uv/tests/it/show_settings.rs index 500e78965..c88f8b739 100644 --- a/crates/uv/tests/it/show_settings.rs +++ b/crates/uv/tests/it/show_settings.rs @@ -3511,6 +3511,8 @@ fn resolve_poetry_toml() -> anyhow::Result<()> { } /// Read from both a `uv.toml` and `pyproject.toml` file in the current directory. +/// +/// Some fields in `[tool.uv]` are masked by `uv.toml` being defined, and should be warned about. #[test] #[cfg_attr( windows, @@ -3535,6 +3537,10 @@ fn resolve_both() -> anyhow::Result<()> { name = "example" version = "0.0.0" + [tool.uv] + offline = true + dev-dependencies = ["pytest"] + [tool.uv.pip] resolution = "highest" extra-index-url = ["https://test.pypi.org/simple"] @@ -3724,7 +3730,232 @@ fn resolve_both() -> anyhow::Result<()> { } ----- stderr ----- - warning: Found both a `uv.toml` file and a `[tool.uv]` section in an adjacent `pyproject.toml`. The `[tool.uv]` section will be ignored in favor of the `uv.toml` file. + warning: Found both a `uv.toml` file and a `[tool.uv]` section in an adjacent `pyproject.toml`. The following fields from `[tool.uv]` will be ignored in favor of the `uv.toml` file: + - offline + - pip + "# + ); + + Ok(()) +} + +/// Read from both a `uv.toml` and `pyproject.toml` file in the current directory. +/// +/// But the fields `[tool.uv]` defines aren't allowed in `uv.toml` so there's no warning. +#[test] +#[cfg_attr( + windows, + ignore = "Configuration tests are not yet supported on Windows" +)] +fn resolve_both_special_fields() -> anyhow::Result<()> { + let context = TestContext::new("3.12"); + + // Write a `uv.toml` file to the directory. + let config = context.temp_dir.child("uv.toml"); + config.write_str(indoc::indoc! {r#" + [pip] + resolution = "lowest-direct" + generate-hashes = true + index-url = "https://pypi.org/simple" + "#})?; + + // Write a `pyproject.toml` file to the directory + let config = context.temp_dir.child("pyproject.toml"); + config.write_str(indoc::indoc! {r#" + [project] + name = "example" + version = "0.0.0" + + [dependency-groups] + mygroup = ["iniconfig"] + + [tool.uv] + dev-dependencies = ["pytest"] + + [tool.uv.dependency-groups] + mygroup = {requires-python = ">=3.12"} + "#})?; + + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("anyio>3.0.0")?; + + // Resolution should succeed, but warn that the `pip` section in `pyproject.toml` is ignored. + uv_snapshot!(context.filters(), add_shared_args(context.pip_compile(), context.temp_dir.path()) + .arg("--show-settings") + .arg("requirements.in"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + GlobalSettings { + required_version: None, + quiet: 0, + verbose: 0, + color: Auto, + network_settings: NetworkSettings { + connectivity: Online, + native_tls: false, + allow_insecure_host: [], + }, + concurrency: Concurrency { + downloads: 50, + builds: 16, + installs: 8, + }, + show_settings: true, + preview: Disabled, + python_preference: Managed, + python_downloads: Automatic, + no_progress: false, + installer_metadata: true, + } + CacheSettings { + no_cache: false, + cache_dir: Some( + "[CACHE_DIR]/", + ), + } + PipCompileSettings { + format: None, + src_file: [ + "requirements.in", + ], + constraints: [], + overrides: [], + build_constraints: [], + constraints_from_workspace: [], + overrides_from_workspace: [], + build_constraints_from_workspace: [], + environments: SupportedEnvironments( + [], + ), + refresh: None( + Timestamp( + SystemTime { + tv_sec: [TIME], + tv_nsec: [TIME], + }, + ), + ), + settings: PipSettings { + index_locations: IndexLocations { + indexes: [ + Index { + name: None, + url: Pypi( + VerbatimUrl { + url: DisplaySafeUrl { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "pypi.org", + ), + ), + port: None, + path: "/simple", + query: None, + fragment: None, + }, + given: Some( + "https://pypi.org/simple", + ), + }, + ), + explicit: false, + default: true, + origin: None, + format: Simple, + publish_url: None, + authenticate: Auto, + ignore_error_codes: None, + }, + ], + flat_index: [], + no_index: false, + }, + python: None, + install_mirrors: PythonInstallMirrors { + python_install_mirror: None, + pypy_install_mirror: None, + python_downloads_json_url: None, + }, + system: false, + extras: ExtrasSpecification( + ExtrasSpecificationInner { + include: Some( + [], + ), + exclude: [], + only_extras: false, + history: ExtrasSpecificationHistory { + extra: [], + only_extra: [], + no_extra: [], + all_extras: false, + no_default_extras: false, + defaults: List( + [], + ), + }, + }, + ), + groups: [], + break_system_packages: false, + target: None, + prefix: None, + index_strategy: FirstIndex, + keyring_provider: Disabled, + torch_backend: None, + no_build_isolation: false, + no_build_isolation_package: [], + build_options: BuildOptions { + no_binary: None, + no_build: None, + }, + allow_empty_requirements: false, + strict: false, + dependency_mode: Transitive, + resolution: LowestDirect, + prerelease: IfNecessaryOrExplicit, + fork_strategy: RequiresPython, + dependency_metadata: DependencyMetadata( + {}, + ), + output_file: None, + no_strip_extras: false, + no_strip_markers: false, + no_annotate: false, + no_header: false, + custom_compile_command: None, + generate_hashes: true, + config_setting: ConfigSettings( + {}, + ), + python_version: None, + python_platform: None, + universal: false, + exclude_newer: None, + no_emit_package: [], + emit_index_url: false, + emit_find_links: false, + emit_build_options: false, + emit_marker_expression: false, + emit_index_annotation: false, + annotation_style: Split, + link_mode: Clone, + compile_bytecode: false, + sources: Enabled, + hash_checking: Some( + Verify, + ), + upgrade: None, + reinstall: None, + }, + } + + ----- stderr ----- "# ); From 0487034e91bbbbbe88def7ebdaf58abf454f8cb8 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sun, 20 Jul 2025 20:28:31 -0400 Subject: [PATCH 282/349] Fix bad merge in `warn_uv_toml_masked_fields` (#14767) ## Summary The branch got stale and merged without flagging that this no longer compiles. --- crates/uv-settings/src/lib.rs | 4 ++++ crates/uv/tests/it/show_settings.rs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/crates/uv-settings/src/lib.rs b/crates/uv-settings/src/lib.rs index 4dd4c392f..64f160aa3 100644 --- a/crates/uv-settings/src/lib.rs +++ b/crates/uv-settings/src/lib.rs @@ -302,6 +302,7 @@ fn warn_uv_toml_masked_fields(options: &Options) { fork_strategy, dependency_metadata, config_settings, + config_settings_package, no_build_isolation, no_build_isolation_package, exclude_newer, @@ -422,6 +423,9 @@ fn warn_uv_toml_masked_fields(options: &Options) { if config_settings.is_some() { masked_fields.push("config-settings"); } + if config_settings_package.is_some() { + masked_fields.push("config-settings-package"); + } if no_build_isolation.is_some() { masked_fields.push("no-build-isolation"); } diff --git a/crates/uv/tests/it/show_settings.rs b/crates/uv/tests/it/show_settings.rs index c88f8b739..293b437d6 100644 --- a/crates/uv/tests/it/show_settings.rs +++ b/crates/uv/tests/it/show_settings.rs @@ -3870,6 +3870,7 @@ fn resolve_both_special_fields() -> anyhow::Result<()> { publish_url: None, authenticate: Auto, ignore_error_codes: None, + cache_control: None, }, ], flat_index: [], @@ -3933,6 +3934,9 @@ fn resolve_both_special_fields() -> anyhow::Result<()> { config_setting: ConfigSettings( {}, ), + config_settings_package: PackageConfigSettings( + {}, + ), python_version: None, python_platform: None, universal: false, From a4c7bcf3ca5698e87388030e4d32749ed89bde35 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 20 Jul 2025 21:24:09 -0400 Subject: [PATCH 283/349] Update aws-actions/configure-aws-credentials digest to a159d7b (#14768) --- .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 4fb67346e..06d578d28 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1594,7 +1594,7 @@ jobs: run: chmod +x ./uv - name: "Configure AWS credentials" - uses: aws-actions/configure-aws-credentials@f503a1870408dcf2c35d5c2b8a68e69211042c7d + uses: aws-actions/configure-aws-credentials@a159d7bb5354cf786f855f2f5d1d8d768d9a08d1 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} From 0951ebc55ceec102341a6e0b038baaa2ecd5229e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 20 Jul 2025 21:24:22 -0400 Subject: [PATCH 284/349] Update google-github-actions/auth digest to 140bb51 (#14769) --- .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 06d578d28..2cd11706a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1613,7 +1613,7 @@ jobs: - name: "Authenticate with GCP" id: "auth" - uses: "google-github-actions/auth@0920706a19e9d22c3d0da43d1db5939c6ad837a8" + uses: "google-github-actions/auth@140bb5113ffb6b65a7e9b937a81fa96cf5064462" with: credentials_json: "${{ secrets.GCP_SERVICE_ACCOUNT_KEY }}" From 2c54a4acfba3282b649d7bc7cb7b72ac8b19a297 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 20 Jul 2025 21:24:28 -0400 Subject: [PATCH 285/349] Update google-github-actions/setup-gcloud digest to 6a7c903 (#14770) --- .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 2cd11706a..ff8212d91 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1618,7 +1618,7 @@ jobs: credentials_json: "${{ secrets.GCP_SERVICE_ACCOUNT_KEY }}" - name: "Set up GCP SDK" - uses: "google-github-actions/setup-gcloud@a8b58010a5b2a061afd605f50e88629c9ec7536b" + uses: "google-github-actions/setup-gcloud@6a7c903a70c8625ed6700fa299f5ddb4ca6022e9" - name: "Get GCP Artifact Registry token" id: get_token From 51336acd2a070528515dab9cd1c1823be6396a76 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 20 Jul 2025 21:24:34 -0400 Subject: [PATCH 286/349] Update pre-commit hook astral-sh/ruff-pre-commit to v0.12.4 (#14771) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5476c9dc8..3a8e4a39a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -42,7 +42,7 @@ repos: types_or: [yaml, json5] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.3 + rev: v0.12.4 hooks: - id: ruff-format - id: ruff From 3a949e0e5312a6e616b94dc39289d8a745916fdc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 20 Jul 2025 21:24:46 -0400 Subject: [PATCH 287/349] Update Rust crate rustix to v1.0.8 (#14772) --- Cargo.lock | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 77dfad413..e1bc5dbef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1150,7 +1150,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1989,7 +1989,7 @@ checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" dependencies = [ "hermit-abi 0.4.0", "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2049,7 +2049,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2902,7 +2902,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3033,7 +3033,7 @@ checksum = "78c81d000a2c524133cc00d2f92f019d399e57906c3b7119271a2495354fe895" dependencies = [ "cfg-if", "libc", - "rustix 1.0.7", + "rustix 1.0.8", "windows 0.61.1", ] @@ -3334,20 +3334,20 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "rustix" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.9.2", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3929,8 +3929,8 @@ dependencies = [ "fastrand", "getrandom 0.3.1", "once_cell", - "rustix 1.0.7", - "windows-sys 0.52.0", + "rustix 1.0.8", + "windows-sys 0.59.0", ] [[package]] @@ -5289,7 +5289,7 @@ dependencies = [ "junction", "path-slash", "percent-encoding", - "rustix 1.0.7", + "rustix 1.0.8", "same-file", "schemars", "serde", @@ -6284,7 +6284,7 @@ checksum = "d3fabb953106c3c8eea8306e4393700d7657561cb43122571b172bbfb7c7ba1d" dependencies = [ "env_home", "regex", - "rustix 1.0.7", + "rustix 1.0.8", "winsafe", ] From b6d12c1b84f666cacf18d1501b701f9ee63c410b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 20 Jul 2025 21:24:56 -0400 Subject: [PATCH 288/349] Update Rust crate serde_json to v1.0.141 (#14773) --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e1bc5dbef..7b73f29d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3584,9 +3584,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.141" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" dependencies = [ "itoa", "memchr", From abcd03bc0ede8bc19566b90d0c059709978e6748 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 20 Jul 2025 21:25:01 -0400 Subject: [PATCH 289/349] Update astral-sh/setup-uv action to v6.4.1 (#14774) --- .github/workflows/ci.yml | 10 +++++----- .github/workflows/publish-pypi.yml | 4 ++-- .github/workflows/sync-python-releases.yml | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ff8212d91..a8be5efe0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,7 +82,7 @@ jobs: run: rustup component add rustfmt - name: "Install uv" - uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1 + uses: astral-sh/setup-uv@7edac99f961f18b581bbd960d59d049f04c0002f # v6.4.1 - name: "rustfmt" run: cargo fmt --all --check @@ -213,7 +213,7 @@ jobs: - name: "Install Rust toolchain" run: rustup show - - uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1 + - uses: astral-sh/setup-uv@7edac99f961f18b581bbd960d59d049f04c0002f # v6.4.1 - name: "Install required Python versions" run: uv python install @@ -249,7 +249,7 @@ jobs: - name: "Install Rust toolchain" run: rustup show - - uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1 + - uses: astral-sh/setup-uv@7edac99f961f18b581bbd960d59d049f04c0002f # v6.4.1 - name: "Install required Python versions" run: uv python install @@ -286,7 +286,7 @@ jobs: run: | Copy-Item -Path "${{ github.workspace }}" -Destination "${{ env.UV_WORKSPACE }}" -Recurse - - uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1 + - uses: astral-sh/setup-uv@7edac99f961f18b581bbd960d59d049f04c0002f # v6.4.1 - name: "Install required Python versions" run: uv python install @@ -439,7 +439,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 - - uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1 + - uses: astral-sh/setup-uv@7edac99f961f18b581bbd960d59d049f04c0002f # v6.4.1 - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 - name: "Add SSH key" if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }} diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index e4435ff17..f6e4b1b4a 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -22,7 +22,7 @@ jobs: id-token: write steps: - name: "Install uv" - uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1 + uses: astral-sh/setup-uv@7edac99f961f18b581bbd960d59d049f04c0002f # v6.4.1 - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: pattern: wheels_uv-* @@ -43,7 +43,7 @@ jobs: id-token: write steps: - name: "Install uv" - uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1 + uses: astral-sh/setup-uv@7edac99f961f18b581bbd960d59d049f04c0002f # v6.4.1 - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: pattern: wheels_uv_build-* diff --git a/.github/workflows/sync-python-releases.yml b/.github/workflows/sync-python-releases.yml index 166458507..bbc9e7b07 100644 --- a/.github/workflows/sync-python-releases.yml +++ b/.github/workflows/sync-python-releases.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1 + - uses: astral-sh/setup-uv@7edac99f961f18b581bbd960d59d049f04c0002f # v6.4.1 with: version: "latest" enable-cache: true From e0feed8f9e1a9735c2e74e8553692362077fa566 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 20 Jul 2025 21:54:38 -0400 Subject: [PATCH 290/349] Update taiki-e/install-action action to v2.56.19 (#14777) --- .github/workflows/ci.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a8be5efe0..0e4afd098 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -188,7 +188,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: "Install cargo shear" - uses: taiki-e/install-action@7b20dfd705618832f20d29066e34aa2f2f6194c2 # v2.52.8 + uses: taiki-e/install-action@c99cc51b309eee71a866715cfa08c922f11cf898 # v2.56.19 with: tool: cargo-shear - run: cargo shear @@ -218,7 +218,7 @@ jobs: run: uv python install - name: "Install cargo nextest" - uses: taiki-e/install-action@7b20dfd705618832f20d29066e34aa2f2f6194c2 # v2.52.8 + uses: taiki-e/install-action@c99cc51b309eee71a866715cfa08c922f11cf898 # v2.56.19 with: tool: cargo-nextest @@ -254,7 +254,7 @@ jobs: run: uv python install - name: "Install cargo nextest" - uses: taiki-e/install-action@7b20dfd705618832f20d29066e34aa2f2f6194c2 # v2.52.8 + uses: taiki-e/install-action@c99cc51b309eee71a866715cfa08c922f11cf898 # v2.56.19 with: tool: cargo-nextest @@ -299,7 +299,7 @@ jobs: run: rustup show - name: "Install cargo nextest" - uses: taiki-e/install-action@7b20dfd705618832f20d29066e34aa2f2f6194c2 # v2.52.8 + uses: taiki-e/install-action@c99cc51b309eee71a866715cfa08c922f11cf898 # v2.56.19 with: tool: cargo-nextest @@ -352,7 +352,7 @@ jobs: rustup component add rust-src --target ${{ matrix.target-arch }}-pc-windows-msvc - name: "Install cargo-bloat" - uses: taiki-e/install-action@7b20dfd705618832f20d29066e34aa2f2f6194c2 # v2.52.8 + uses: taiki-e/install-action@c99cc51b309eee71a866715cfa08c922f11cf898 # v2.56.19 with: tool: cargo-bloat @@ -2516,7 +2516,7 @@ jobs: run: rustup show - name: "Install codspeed" - uses: taiki-e/install-action@7b20dfd705618832f20d29066e34aa2f2f6194c2 # v2.52.8 + uses: taiki-e/install-action@c99cc51b309eee71a866715cfa08c922f11cf898 # v2.56.19 with: tool: cargo-codspeed @@ -2553,7 +2553,7 @@ jobs: run: rustup show - name: "Install codspeed" - uses: taiki-e/install-action@7b20dfd705618832f20d29066e34aa2f2f6194c2 # v2.52.8 + uses: taiki-e/install-action@c99cc51b309eee71a866715cfa08c922f11cf898 # v2.56.19 with: tool: cargo-codspeed From a049ba78fcf34b0c7314f111498f6125d2fd185c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 20 Jul 2025 21:54:44 -0400 Subject: [PATCH 291/349] Update uraimo/run-on-arch-action action to v3 (#14778) --- .github/workflows/build-binaries.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml index ccd3ef3ee..b8d245c1b 100644 --- a/.github/workflows/build-binaries.yml +++ b/.github/workflows/build-binaries.yml @@ -718,7 +718,7 @@ jobs: manylinux: auto docker-options: ${{ matrix.platform.maturin_docker_options }} args: --release --locked --out dist --features self-update - - uses: uraimo/run-on-arch-action@ac33288c3728ca72563c97b8b88dda5a65a84448 # v2 + - uses: uraimo/run-on-arch-action@d94c13912ea685de38fccc1109385b83fd79427d # v3.0.1 name: "Test wheel" with: arch: ${{ matrix.platform.arch }} @@ -767,7 +767,7 @@ jobs: manylinux: auto docker-options: ${{ matrix.platform.maturin_docker_options }} args: --profile minimal-size --locked --out crates/uv-build/dist -m crates/uv-build/Cargo.toml - - uses: uraimo/run-on-arch-action@ac33288c3728ca72563c97b8b88dda5a65a84448 # v2 + - uses: uraimo/run-on-arch-action@d94c13912ea685de38fccc1109385b83fd79427d # v3.0.1 name: "Test wheel uv-build" with: arch: ${{ matrix.platform.arch }} From 7c2819d1f63b7a2c5b1aa3b9acee032b5bf1d725 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Mon, 21 Jul 2025 04:48:38 -0500 Subject: [PATCH 292/349] Match `--bounds` formatting for `uv_build` bounds in `uv init` (#14731) Closes #14724 https://chatgpt.com/codex/tasks/task_e_687a53ba646c8331baa4140c5b2bec70 --------- Co-authored-by: konstin --- crates/uv-build-backend/src/lib.rs | 28 ++++++++++----------- crates/uv-build-backend/src/metadata.rs | 12 ++++----- crates/uv/src/commands/project/init.rs | 9 ++++++- scripts/packages/built-by-uv/pyproject.toml | 2 +- 4 files changed, 29 insertions(+), 22 deletions(-) diff --git a/crates/uv-build-backend/src/lib.rs b/crates/uv-build-backend/src/lib.rs index 5e0efd6d5..5800d04d2 100644 --- a/crates/uv-build-backend/src/lib.rs +++ b/crates/uv-build-backend/src/lib.rs @@ -680,7 +680,7 @@ mod tests { license = { file = "license.txt" } [build-system] - requires = ["uv_build>=0.5.15,<0.6"] + requires = ["uv_build>=0.5.15,<0.6.0"] build-backend = "uv_build" "# }, @@ -748,7 +748,7 @@ mod tests { version = "1.0.0" [build-system] - requires = ["uv_build>=0.5.15,<0.6"] + requires = ["uv_build>=0.5.15,<0.6.0"] build-backend = "uv_build" "# }, @@ -812,7 +812,7 @@ mod tests { version = "1.0.0" [build-system] - requires = ["uv_build>=0.5.15,<0.6"] + requires = ["uv_build>=0.5.15,<0.6.0"] build-backend = "uv_build" [tool.uv.build-backend] @@ -854,7 +854,7 @@ mod tests { version = "1.0.0" [build-system] - requires = ["uv_build>=0.5.15,<0.6"] + requires = ["uv_build>=0.5.15,<0.6.0"] build-backend = "uv_build" [tool.uv.build-backend] @@ -879,7 +879,7 @@ mod tests { version = "1.0.0" [build-system] - requires = ["uv_build>=0.5.15,<0.6"] + requires = ["uv_build>=0.5.15,<0.6.0"] build-backend = "uv_build" [tool.uv.build-backend] @@ -928,7 +928,7 @@ mod tests { version = "1.0.0" [build-system] - requires = ["uv_build>=0.5.15,<0.6"] + requires = ["uv_build>=0.5.15,<0.6.0"] build-backend = "uv_build" [tool.uv.build-backend] @@ -959,7 +959,7 @@ mod tests { version = "1.0.0" [build-system] - requires = ["uv_build>=0.5.15,<0.6"] + requires = ["uv_build>=0.5.15,<0.6.0"] build-backend = "uv_build" "# }; @@ -1010,7 +1010,7 @@ mod tests { version = "1.0.0" [build-system] - requires = ["uv_build>=0.5.15,<0.6"] + requires = ["uv_build>=0.5.15,<0.6.0"] build-backend = "uv_build" [tool.uv.build-backend] @@ -1036,7 +1036,7 @@ mod tests { module-name = "simple_namespace.part" [build-system] - requires = ["uv_build>=0.5.15,<0.6"] + requires = ["uv_build>=0.5.15,<0.6.0"] build-backend = "uv_build" "# }; @@ -1104,7 +1104,7 @@ mod tests { namespace = true [build-system] - requires = ["uv_build>=0.5.15,<0.6"] + requires = ["uv_build>=0.5.15,<0.6.0"] build-backend = "uv_build" "# }; @@ -1127,7 +1127,7 @@ mod tests { namespace = true [build-system] - requires = ["uv_build>=0.5.15,<0.6"] + requires = ["uv_build>=0.5.15,<0.6.0"] build-backend = "uv_build" "# }; @@ -1188,7 +1188,7 @@ mod tests { namespace = true [build-system] - requires = ["uv_build>=0.5.15,<0.6"] + requires = ["uv_build>=0.5.15,<0.6.0"] build-backend = "uv_build" "# }; @@ -1211,7 +1211,7 @@ mod tests { module-name = "cloud-stubs.db.schema" [build-system] - requires = ["uv_build>=0.5.15,<0.6"] + requires = ["uv_build>=0.5.15,<0.6.0"] build-backend = "uv_build" "# }; @@ -1261,7 +1261,7 @@ mod tests { module-name = ["foo", "simple_namespace.part_a", "simple_namespace.part_b"] [build-system] - requires = ["uv_build>=0.5.15,<0.6"] + requires = ["uv_build>=0.5.15,<0.6.0"] build-backend = "uv_build" "# }; diff --git a/crates/uv-build-backend/src/metadata.rs b/crates/uv-build-backend/src/metadata.rs index 5997f72b6..d224fd788 100644 --- a/crates/uv-build-backend/src/metadata.rs +++ b/crates/uv-build-backend/src/metadata.rs @@ -171,7 +171,7 @@ impl PyProjectToml { /// /// ```toml /// [build-system] - /// requires = ["uv_build>=0.4.15,<0.5"] + /// requires = ["uv_build>=0.4.15,<0.5.0"] /// build-backend = "uv_build" /// ``` pub fn check_build_system(&self, uv_version: &str) -> Vec { @@ -826,7 +826,7 @@ mod tests { {payload} [build-system] - requires = ["uv_build>=0.4.15,<0.5"] + requires = ["uv_build>=0.4.15,<0.5.0"] build-backend = "uv_build" "# } @@ -909,7 +909,7 @@ mod tests { foo-bar = "foo:bar" [build-system] - requires = ["uv_build>=0.4.15,<0.5"] + requires = ["uv_build>=0.4.15,<0.5.0"] build-backend = "uv_build" "# }; @@ -1095,7 +1095,7 @@ mod tests { foo-bar = "foo:bar" [build-system] - requires = ["uv_build>=0.4.15,<0.5"] + requires = ["uv_build>=0.4.15,<0.5.0"] build-backend = "uv_build" "# }; @@ -1194,7 +1194,7 @@ mod tests { version = "0.1.0" [build-system] - requires = ["uv_build>=0.4.15,<0.5", "wheel"] + requires = ["uv_build>=0.4.15,<0.5.0", "wheel"] build-backend = "uv_build" "#}; let pyproject_toml = PyProjectToml::parse(contents).unwrap(); @@ -1230,7 +1230,7 @@ mod tests { version = "0.1.0" [build-system] - requires = ["uv_build>=0.4.15,<0.5"] + requires = ["uv_build>=0.4.15,<0.5.0"] build-backend = "setuptools" "#}; let pyproject_toml = PyProjectToml::parse(contents).unwrap(); diff --git a/crates/uv/src/commands/project/init.rs b/crates/uv/src/commands/project/init.rs index 4fd79b1c2..9ba2a434d 100644 --- a/crates/uv/src/commands/project/init.rs +++ b/crates/uv/src/commands/project/init.rs @@ -1,6 +1,7 @@ use anyhow::{Context, Result, anyhow}; use owo_colors::OwoColorize; use std::fmt::Write; +use std::iter; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use std::str::FromStr; @@ -944,7 +945,13 @@ fn pyproject_build_system(package: &PackageName, build_backend: ProjectBuildBack min_version.release()[0] == 0, "migrate to major version bumps" ); - let max_version = Version::new([0, min_version.release()[1] + 1]); + let max_version = Version::new( + [0, min_version.release()[1] + 1] + .into_iter() + // Add trailing zeroes to match the version length, to use the same style + // as `--bounds`. + .chain(iter::repeat_n(0, min_version.release().len() - 2)), + ); indoc::formatdoc! {r#" [build-system] requires = ["uv_build>={min_version},<{max_version}"] diff --git a/scripts/packages/built-by-uv/pyproject.toml b/scripts/packages/built-by-uv/pyproject.toml index b1914e071..b95f9862f 100644 --- a/scripts/packages/built-by-uv/pyproject.toml +++ b/scripts/packages/built-by-uv/pyproject.toml @@ -24,5 +24,5 @@ data = "assets" headers = "header" [build-system] -requires = ["uv_build>=0.8,<0.9"] +requires = ["uv_build>=0.8.0,<0.9.0"] build-backend = "uv_build" From 98d6ab6632a4ac06211eb2dbd18cd588c653b069 Mon Sep 17 00:00:00 2001 From: Jo <10510431+j178@users.noreply.github.com> Date: Mon, 21 Jul 2025 18:22:45 +0800 Subject: [PATCH 293/349] Improve `CPythonFinder._parse_download_url` a bit (#14780) ## Summary Rename `_parse_download_url` to `_parse_download_asset` and move the `asset['digest']` logic into it. ## Test Plan ```console uv run ./crates/uv-python/fetch-download-metadata.py ``` --- crates/uv-python/fetch-download-metadata.py | 24 +++++++++------------ 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/crates/uv-python/fetch-download-metadata.py b/crates/uv-python/fetch-download-metadata.py index 3dd0817f3..f43349e50 100755 --- a/crates/uv-python/fetch-download-metadata.py +++ b/crates/uv-python/fetch-download-metadata.py @@ -53,8 +53,7 @@ import re from dataclasses import asdict, dataclass, field from enum import StrEnum from pathlib import Path -from typing import Generator, Iterable, NamedTuple, Self -from urllib.parse import unquote +from typing import Any, Generator, Iterable, NamedTuple, Self import httpx @@ -255,13 +254,7 @@ class CPythonFinder(Finder): # Sort the assets to ensure deterministic results row["assets"].sort(key=lambda asset: asset["browser_download_url"]) for asset in row["assets"]: - # On older versions, GitHub didn't backfill the digest. - if digest := asset["digest"]: - sha256 = digest.removeprefix("sha256:") - else: - sha256 = None - url = asset["browser_download_url"] - download = self._parse_download_url(url, sha256) + download = self._parse_download_asset(asset) if download is None: continue if ( @@ -355,16 +348,19 @@ class CPythonFinder(Finder): continue download.sha256 = checksums.get(download.filename) - def _parse_download_url( - self, url: str, sha256: str | None - ) -> PythonDownload | None: - """Parse an indygreg download URL into a PythonDownload object.""" + def _parse_download_asset(self, asset: dict[str, Any]) -> PythonDownload | None: + """Parse a python-build-standalone download asset into a PythonDownload object.""" + url = asset["browser_download_url"] # Ex) # https://github.com/astral-sh/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-aarch64-unknown-linux-gnu-lto-full.tar.zst if url.endswith(".sha256"): return None - filename = unquote(url.rsplit("/", maxsplit=1)[-1]) release = int(url.rsplit("/")[-2]) + filename = asset["name"] + sha256 = None + # On older versions, GitHub didn't backfill the digest. + if digest := asset["digest"]: + sha256 = digest.removeprefix("sha256:") match = self._filename_re.match(filename) or self._legacy_filename_re.match( filename From 8ed86a6dcdc794f6054533d94e0983e527cb2d31 Mon Sep 17 00:00:00 2001 From: konsti Date: Mon, 21 Jul 2025 14:27:59 +0200 Subject: [PATCH 294/349] Remove Python 3.9.18 from `.python-versions` (#14784) Python 3.9.18 is not used in the tests anymore. --- .python-versions | 1 - 1 file changed, 1 deletion(-) diff --git a/.python-versions b/.python-versions index 957687cb4..f17a9a96b 100644 --- a/.python-versions +++ b/.python-versions @@ -6,7 +6,6 @@ 3.8.20 # The following are required for packse scenarios 3.9.20 -3.9.18 3.9.12 # The following is needed for `==3.13` request tests 3.13.0 From 9983273289ba229c20c3008b831a06c685497b29 Mon Sep 17 00:00:00 2001 From: Jo <10510431+j178@users.noreply.github.com> Date: Mon, 21 Jul 2025 20:35:45 +0800 Subject: [PATCH 295/349] Use sha256 checksum from GitHub API for GraalPy releases (#14779) ## Summary Follow #14078, use GitHub generated sha256 for GraalPy releases too. ## Test Plan ```console uv run ./crates/uv-python/fetch-download-metadata.py ``` --- 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 f43349e50..ec2b4835e 100755 --- a/crates/uv-python/fetch-download-metadata.py +++ b/crates/uv-python/fetch-download-metadata.py @@ -607,6 +607,9 @@ class GraalPyFinder(Finder): platform = self._normalize_os(m.group(1)) arch = self._normalize_arch(m.group(2)) libc = "gnu" if platform == "linux" else "none" + sha256 = None + if digest := asset["digest"]: + sha256 = digest.removeprefix("sha256:") download = PythonDownload( release=0, version=python_version, @@ -619,6 +622,7 @@ class GraalPyFinder(Finder): implementation=self.implementation, filename=asset["name"], url=url, + sha256=sha256, ) # Only keep the latest GraalPy version of each arch/platform if (python_version, arch, platform) not in results: @@ -633,6 +637,7 @@ class GraalPyFinder(Finder): return self.PLATFORM_MAPPING.get(os, os) async def _fetch_checksums(self, downloads: list[PythonDownload], n: int) -> None: + downloads = list(filter(lambda d: not d.sha256, downloads)) for idx, batch in enumerate(batched(downloads, n)): logging.info("Fetching GraalPy checksums: %d/%d", idx * n, len(downloads)) checksum_requests = [] From ab48dfd0cb56e10b50bdcf36aff5a09359388e97 Mon Sep 17 00:00:00 2001 From: konsti Date: Mon, 21 Jul 2025 14:38:33 +0200 Subject: [PATCH 296/349] Collect contains markers in enum (#14782) We'll add more contains markers for the wheel variants, so I want to unify them before rebasing the variants branch on them. --- crates/uv-pep508/src/marker/parse.rs | 34 ++++++++++------------------ crates/uv-pep508/src/marker/tree.rs | 31 ++++++++++++++++++------- 2 files changed, 35 insertions(+), 30 deletions(-) diff --git a/crates/uv-pep508/src/marker/parse.rs b/crates/uv-pep508/src/marker/parse.rs index 9c361c19d..de8bfac72 100644 --- a/crates/uv-pep508/src/marker/parse.rs +++ b/crates/uv-pep508/src/marker/parse.rs @@ -5,7 +5,7 @@ use uv_pep440::{Version, VersionPattern, VersionSpecifier}; use crate::cursor::Cursor; use crate::marker::MarkerValueExtra; -use crate::marker::tree::{ContainerOperator, MarkerValueDependencyGroup}; +use crate::marker::tree::{ContainerOperator, MarkerValueContains, MarkerValueDependencyGroup}; use crate::{ ExtraOperator, MarkerExpression, MarkerOperator, MarkerTree, MarkerValue, MarkerValueString, MarkerValueVersion, MarkerWarningKind, Pep508Error, Pep508ErrorSource, Pep508Url, Reporter, @@ -209,10 +209,9 @@ pub(crate) fn parse_marker_key_op_value( MarkerValue::MarkerEnvString(key) => { let value = match r_value { MarkerValue::Extra - | MarkerValue::Extras - | MarkerValue::DependencyGroups | MarkerValue::MarkerEnvVersion(_) - | MarkerValue::MarkerEnvString(_) => { + | MarkerValue::MarkerEnvString(_) + | MarkerValue::MarkerEnvContains(_) => { reporter.report( MarkerWarningKind::MarkerMarkerComparison, "Comparing two markers with each other doesn't make any sense, @@ -245,9 +244,8 @@ pub(crate) fn parse_marker_key_op_value( let value = match r_value { MarkerValue::MarkerEnvVersion(_) | MarkerValue::MarkerEnvString(_) - | MarkerValue::Extra - | MarkerValue::Extras - | MarkerValue::DependencyGroups => { + | MarkerValue::MarkerEnvContains(_) + | MarkerValue::Extra => { reporter.report( MarkerWarningKind::ExtraInvalidComparison, "Comparing extra with something other than a quoted string is wrong, @@ -279,9 +277,11 @@ pub(crate) fn parse_marker_key_op_value( // `'...' == extra` MarkerValue::Extra => parse_extra_expr(operator, &l_string, reporter), // `'...' in extras` - MarkerValue::Extras => parse_extras_expr(operator, &l_string, reporter), + MarkerValue::MarkerEnvContains(MarkerValueContains::Extras) => { + parse_extras_expr(operator, &l_string, reporter) + } // `'...' in dependency_groups` - MarkerValue::DependencyGroups => { + MarkerValue::MarkerEnvContains(MarkerValueContains::DependencyGroups) => { parse_dependency_groups_expr(operator, &l_string, reporter) } // `'...' == '...'`, doesn't make much sense @@ -300,22 +300,12 @@ pub(crate) fn parse_marker_key_op_value( } } } - MarkerValue::Extras => { + MarkerValue::MarkerEnvContains(key) => { reporter.report( MarkerWarningKind::Pep440Error, format!( - "The `extras` marker must be used as '...' in extras' or '... not in extras', - found `{l_value} {operator} {r_value}`, will be ignored" - ), - ); - return Ok(None); - } - MarkerValue::DependencyGroups => { - reporter.report( - MarkerWarningKind::Pep440Error, - format!( - "The `dependency_groups` marker must be used as '...' in dependency_groups' or '... not in dependency_groups', - found `{l_value} {operator} {r_value}`, will be ignored" + "The `{key}` marker must be used as '...' in {key}' or '... not in {key}', + found `{key} {operator} {r_value}`, will be ignored" ), ); return Ok(None); diff --git a/crates/uv-pep508/src/marker/tree.rs b/crates/uv-pep508/src/marker/tree.rs index 594b81723..95e7327ed 100644 --- a/crates/uv-pep508/src/marker/tree.rs +++ b/crates/uv-pep508/src/marker/tree.rs @@ -126,6 +126,24 @@ impl Display for MarkerValueString { } } +/// Those markers with exclusively `in` and `not in` operators (PEP 751) +#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub enum MarkerValueContains { + /// `extras`. This one is special because it's a list, and user-provided + Extras, + /// `dependency_groups`. This one is special because it's a list, and user-provided + DependencyGroups, +} + +impl Display for MarkerValueContains { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Extras => f.write_str("extras"), + Self::DependencyGroups => f.write_str("dependency_groups"), + } + } +} + /// One of the predefined environment values /// /// @@ -137,10 +155,8 @@ pub enum MarkerValue { MarkerEnvString(MarkerValueString), /// `extra`. This one is special because it's a list, and user-provided Extra, - /// `extras`. This one is special because it's a list, and user-provided - Extras, - /// `dependency_groups`. This one is special because it's a list, and user-provided - DependencyGroups, + /// Those markers with exclusively `in` and `not in` operators (PEP 751) + MarkerEnvContains(MarkerValueContains), /// Not a constant, but a user given quoted string with a value inside such as '3.8' or "windows" QuotedString(ArcStr), } @@ -181,8 +197,8 @@ impl FromStr for MarkerValue { "sys_platform" => Self::MarkerEnvString(MarkerValueString::SysPlatform), "sys.platform" => Self::MarkerEnvString(MarkerValueString::SysPlatformDeprecated), "extra" => Self::Extra, - "extras" => Self::Extras, - "dependency_groups" => Self::DependencyGroups, + "extras" => Self::MarkerEnvContains(MarkerValueContains::Extras), + "dependency_groups" => Self::MarkerEnvContains(MarkerValueContains::DependencyGroups), _ => return Err(format!("Invalid key: {s}")), }; Ok(value) @@ -195,8 +211,7 @@ impl Display for MarkerValue { Self::MarkerEnvVersion(marker_value_version) => marker_value_version.fmt(f), Self::MarkerEnvString(marker_value_string) => marker_value_string.fmt(f), Self::Extra => f.write_str("extra"), - Self::Extras => f.write_str("extras"), - Self::DependencyGroups => f.write_str("dependency_groups"), + Self::MarkerEnvContains(marker_value_contains) => marker_value_contains.fmt(f), Self::QuotedString(value) => write!(f, "'{value}'"), } } From b81cce9152047a8183d50319c74a7f66baa7b9a0 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 21 Jul 2025 08:48:47 -0400 Subject: [PATCH 297/349] Support `extras` and `dependency_groups` markers on `uv pip install` and `uv pip sync` (#14755) ## Summary We don't yet support writing these, but we can at least read them (which, e.g., allows you to install PDM-exported `pylock.toml` files with uv, since PDM _always_ writes a default group). Closes #14740. --- crates/uv-cli/src/lib.rs | 61 +++-- .../uv-configuration/src/dependency_groups.rs | 12 + crates/uv-pep508/src/marker/tree.rs | 91 +++++++- crates/uv-requirements/src/sources.rs | 4 +- crates/uv-requirements/src/specification.rs | 64 ++++-- .../src/lock/export/pylock_toml.rs | 11 +- crates/uv/src/commands/pip/install.rs | 33 ++- crates/uv/src/commands/pip/operations.rs | 2 +- crates/uv/src/commands/pip/sync.rs | 45 +++- crates/uv/src/lib.rs | 6 + crates/uv/src/settings.rs | 7 + crates/uv/tests/it/pip_install.rs | 217 +++++++++++++++++- docs/reference/cli.md | 21 +- 13 files changed, 492 insertions(+), 82 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index d6560014f..e1084f035 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -1202,6 +1202,14 @@ pub struct PipCompileArgs { #[arg(long, overrides_with("all_extras"), hide = true)] pub no_all_extras: bool, + /// Install the specified dependency group from a `pyproject.toml`. + /// + /// If no path is provided, the `pyproject.toml` in the working directory is used. + /// + /// May be provided multiple times. + #[arg(long, group = "sources")] + pub group: Vec, + #[command(flatten)] pub resolver: ResolverArgs, @@ -1216,14 +1224,6 @@ pub struct PipCompileArgs { #[arg(long, overrides_with("no_deps"), hide = true)] pub deps: bool, - /// Install the specified dependency group from a `pyproject.toml`. - /// - /// If no path is provided, the `pyproject.toml` in the working directory is used. - /// - /// May be provided multiple times. - #[arg(long, group = "sources")] - pub group: Vec, - /// Write the compiled requirements to the given `requirements.txt` or `pylock.toml` file. /// /// If the file already exists, the existing versions will be preferred when resolving @@ -1518,6 +1518,30 @@ pub struct PipSyncArgs { #[arg(long, short, alias = "build-constraint", env = EnvVars::UV_BUILD_CONSTRAINT, value_delimiter = ' ', value_parser = parse_maybe_file_path)] pub build_constraints: Vec>, + /// Include optional dependencies from the specified extra name; may be provided more than once. + /// + /// Only applies to `pylock.toml`, `pyproject.toml`, `setup.py`, and `setup.cfg` sources. + #[arg(long, conflicts_with = "all_extras", value_parser = extra_name_with_clap_error)] + pub extra: Option>, + + /// Include all optional dependencies. + /// + /// Only applies to `pylock.toml`, `pyproject.toml`, `setup.py`, and `setup.cfg` sources. + #[arg(long, conflicts_with = "extra", overrides_with = "no_all_extras")] + pub all_extras: bool, + + #[arg(long, overrides_with("all_extras"), hide = true)] + pub no_all_extras: bool, + + /// Install the specified dependency group from a `pylock.toml` or `pyproject.toml`. + /// + /// If no path is provided, the `pylock.toml` or `pyproject.toml` in the working directory is + /// used. + /// + /// May be provided multiple times. + #[arg(long, group = "sources")] + pub group: Vec, + #[command(flatten)] pub installer: InstallerArgs, @@ -1798,19 +1822,28 @@ pub struct PipInstallArgs { /// Include optional dependencies from the specified extra name; may be provided more than once. /// - /// Only applies to `pyproject.toml`, `setup.py`, and `setup.cfg` sources. + /// Only applies to `pylock.toml`, `pyproject.toml`, `setup.py`, and `setup.cfg` sources. #[arg(long, conflicts_with = "all_extras", value_parser = extra_name_with_clap_error)] pub extra: Option>, /// Include all optional dependencies. /// - /// Only applies to `pyproject.toml`, `setup.py`, and `setup.cfg` sources. + /// Only applies to `pylock.toml`, `pyproject.toml`, `setup.py`, and `setup.cfg` sources. #[arg(long, conflicts_with = "extra", overrides_with = "no_all_extras")] pub all_extras: bool, #[arg(long, overrides_with("all_extras"), hide = true)] pub no_all_extras: bool, + /// Install the specified dependency group from a `pylock.toml` or `pyproject.toml`. + /// + /// If no path is provided, the `pylock.toml` or `pyproject.toml` in the working directory is + /// used. + /// + /// May be provided multiple times. + #[arg(long, group = "sources")] + pub group: Vec, + #[command(flatten)] pub installer: ResolverInstallerArgs, @@ -1825,14 +1858,6 @@ pub struct PipInstallArgs { #[arg(long, overrides_with("no_deps"), hide = true)] pub deps: bool, - /// Install the specified dependency group from a `pyproject.toml`. - /// - /// If no path is provided, the `pyproject.toml` in the working directory is used. - /// - /// May be provided multiple times. - #[arg(long, group = "sources")] - pub group: Vec, - /// Require a matching hash for each requirement. /// /// By default, uv will verify any available hashes in the requirements file, but will not diff --git a/crates/uv-configuration/src/dependency_groups.rs b/crates/uv-configuration/src/dependency_groups.rs index a3b90ea5f..70dd9db08 100644 --- a/crates/uv-configuration/src/dependency_groups.rs +++ b/crates/uv-configuration/src/dependency_groups.rs @@ -186,6 +186,18 @@ impl DependencyGroupsInner { self.include.names().chain(&self.exclude) } + /// Returns an iterator over all groups that are included in the specification, + /// assuming `all_names` is an iterator over all groups. + pub fn group_names<'a, Names>( + &'a self, + all_names: Names, + ) -> impl Iterator + 'a + where + Names: Iterator + 'a, + { + all_names.filter(move |name| self.contains(name)) + } + /// Iterate over all groups the user explicitly asked for on the CLI pub fn explicit_names(&self) -> impl Iterator { let DependencyGroupsHistory { diff --git a/crates/uv-pep508/src/marker/tree.rs b/crates/uv-pep508/src/marker/tree.rs index 95e7327ed..756c90ade 100644 --- a/crates/uv-pep508/src/marker/tree.rs +++ b/crates/uv-pep508/src/marker/tree.rs @@ -754,6 +754,51 @@ impl Display for MarkerExpression { } } +/// The extra and dependency group names to use when evaluating a marker tree. +#[derive(Debug, Copy, Clone)] +enum ExtrasEnvironment<'a> { + /// E.g., `extra == '...'` + Extras(&'a [ExtraName]), + /// E.g., `'...' in extras` or `'...' in dependency_groups` + Pep751(&'a [ExtraName], &'a [GroupName]), +} + +impl<'a> ExtrasEnvironment<'a> { + /// Creates a new [`ExtrasEnvironment`] for the given `extra` names. + fn from_extras(extras: &'a [ExtraName]) -> Self { + Self::Extras(extras) + } + + /// Creates a new [`ExtrasEnvironment`] for the given PEP 751 `extras` and `dependency_groups`. + fn from_pep751(extras: &'a [ExtraName], dependency_groups: &'a [GroupName]) -> Self { + Self::Pep751(extras, dependency_groups) + } + + /// Returns the `extra` names in this environment. + fn extra(&self) -> &[ExtraName] { + match self { + ExtrasEnvironment::Extras(extra) => extra, + ExtrasEnvironment::Pep751(..) => &[], + } + } + + /// Returns the `extras` names in this environment, as in a PEP 751 lockfile. + fn extras(&self) -> &[ExtraName] { + match self { + ExtrasEnvironment::Extras(..) => &[], + ExtrasEnvironment::Pep751(extras, ..) => extras, + } + } + + /// Returns the `dependency_group` group names in this environment, as in a PEP 751 lockfile. + fn dependency_groups(&self) -> &[GroupName] { + match self { + ExtrasEnvironment::Extras(..) => &[], + ExtrasEnvironment::Pep751(.., groups) => groups, + } + } +} + /// Represents one or more nested marker expressions with and/or/parentheses. /// /// Marker trees are canonical, meaning any two functionally equivalent markers @@ -1001,7 +1046,27 @@ impl MarkerTree { /// Does this marker apply in the given environment? pub fn evaluate(self, env: &MarkerEnvironment, extras: &[ExtraName]) -> bool { - self.evaluate_reporter_impl(env, extras, &mut TracingReporter) + self.evaluate_reporter_impl( + env, + ExtrasEnvironment::from_extras(extras), + &mut TracingReporter, + ) + } + + /// Evaluate a marker in the context of a PEP 751 lockfile, which exposes several additional + /// markers (`extras` and `dependency_groups`) that are not available in any other context, + /// per the spec. + pub fn evaluate_pep751( + self, + env: &MarkerEnvironment, + extras: &[ExtraName], + groups: &[GroupName], + ) -> bool { + self.evaluate_reporter_impl( + env, + ExtrasEnvironment::from_pep751(extras, groups), + &mut TracingReporter, + ) } /// Evaluates this marker tree against an optional environment and a @@ -1018,7 +1083,11 @@ impl MarkerTree { ) -> bool { match env { None => self.evaluate_extras(extras), - Some(env) => self.evaluate_reporter_impl(env, extras, &mut TracingReporter), + Some(env) => self.evaluate_reporter_impl( + env, + ExtrasEnvironment::from_extras(extras), + &mut TracingReporter, + ), } } @@ -1030,13 +1099,13 @@ impl MarkerTree { extras: &[ExtraName], reporter: &mut impl Reporter, ) -> bool { - self.evaluate_reporter_impl(env, extras, reporter) + self.evaluate_reporter_impl(env, ExtrasEnvironment::from_extras(extras), reporter) } fn evaluate_reporter_impl( self, env: &MarkerEnvironment, - extras: &[ExtraName], + extras: ExtrasEnvironment, reporter: &mut impl Reporter, ) -> bool { match self.kind() { @@ -1088,12 +1157,18 @@ impl MarkerTree { } MarkerTreeKind::Extra(marker) => { return marker - .edge(extras.contains(marker.name().extra())) + .edge(extras.extra().contains(marker.name().extra())) .evaluate_reporter_impl(env, extras, reporter); } - // TODO(charlie): Add support for evaluating container extras in PEP 751 lockfiles. - MarkerTreeKind::Extras(..) | MarkerTreeKind::DependencyGroups(..) => { - return false; + MarkerTreeKind::Extras(marker) => { + return marker + .edge(extras.extras().contains(marker.name().extra())) + .evaluate_reporter_impl(env, extras, reporter); + } + MarkerTreeKind::DependencyGroups(marker) => { + return marker + .edge(extras.dependency_groups().contains(marker.name().group())) + .evaluate_reporter_impl(env, extras, reporter); } } diff --git a/crates/uv-requirements/src/sources.rs b/crates/uv-requirements/src/sources.rs index 090a72e5c..024ac5ebf 100644 --- a/crates/uv-requirements/src/sources.rs +++ b/crates/uv-requirements/src/sources.rs @@ -273,13 +273,13 @@ impl RequirementsSource { pub fn allows_extras(&self) -> bool { matches!( self, - Self::PyprojectToml(_) | Self::SetupPy(_) | Self::SetupCfg(_) + Self::PylockToml(_) | Self::PyprojectToml(_) | Self::SetupPy(_) | Self::SetupCfg(_) ) } /// Returns `true` if the source allows groups to be specified. pub fn allows_groups(&self) -> bool { - matches!(self, Self::PyprojectToml(_)) + matches!(self, Self::PylockToml(_) | Self::PyprojectToml(_)) } } diff --git a/crates/uv-requirements/src/specification.rs b/crates/uv-requirements/src/specification.rs index deead2c82..88a5eba21 100644 --- a/crates/uv-requirements/src/specification.rs +++ b/crates/uv-requirements/src/specification.rs @@ -250,10 +250,13 @@ impl RequirementsSpecification { // If we have a `pylock.toml`, don't allow additional requirements, constraints, or // overrides. - if requirements - .iter() - .any(|source| matches!(source, RequirementsSource::PylockToml(..))) - { + if let Some(pylock_toml) = requirements.iter().find_map(|source| { + if let RequirementsSource::PylockToml(path) = source { + Some(path) + } else { + None + } + }) { if requirements .iter() .any(|source| !matches!(source, RequirementsSource::PylockToml(..))) @@ -272,22 +275,38 @@ impl RequirementsSpecification { "Cannot specify constraints with a `pylock.toml` file" )); } - if groups.is_some_and(|groups| !groups.groups.is_empty()) { - return Err(anyhow::anyhow!( - "Cannot specify groups with a `pylock.toml` file" - )); + + // If we have a `pylock.toml`, disallow specifying paths for groups; instead, require + // that all groups refer to the `pylock.toml` file. + if let Some(groups) = groups { + let mut names = Vec::new(); + for group in &groups.groups { + if group.path.is_some() { + return Err(anyhow::anyhow!( + "Cannot specify paths for groups with a `pylock.toml` file; all groups must refer to the `pylock.toml` file" + )); + } + names.push(group.name.clone()); + } + + if !names.is_empty() { + spec.groups.insert( + pylock_toml.clone(), + DependencyGroups::from_args( + false, + false, + false, + Vec::new(), + Vec::new(), + false, + names, + false, + ), + ); + } } - } - - // Resolve sources into specifications so we know their `source_tree`. - let mut requirement_sources = Vec::new(); - for source in requirements { - let source = Self::from_source(source, client_builder).await?; - requirement_sources.push(source); - } - - // pip `--group` flags specify their own sources, which we need to process here - if let Some(groups) = groups { + } else if let Some(groups) = groups { + // pip `--group` flags specify their own sources, which we need to process here. // First, we collect all groups by their path. let mut groups_by_path = BTreeMap::new(); for group in &groups.groups { @@ -320,6 +339,13 @@ impl RequirementsSpecification { spec.groups = group_specs; } + // Resolve sources into specifications so we know their `source_tree`. + let mut requirement_sources = Vec::new(); + for source in requirements { + let source = Self::from_source(source, client_builder).await?; + requirement_sources.push(source); + } + // Read all requirements, and keep track of all requirements _and_ constraints. // A `requirements.txt` can contain a `-c constraints.txt` directive within it, so reading // a requirements file can also add constraints. diff --git a/crates/uv-resolver/src/lock/export/pylock_toml.rs b/crates/uv-resolver/src/lock/export/pylock_toml.rs index 80cd54be2..ef3ad8615 100644 --- a/crates/uv-resolver/src/lock/export/pylock_toml.rs +++ b/crates/uv-resolver/src/lock/export/pylock_toml.rs @@ -188,11 +188,11 @@ pub struct PylockToml { #[serde(skip_serializing_if = "Option::is_none")] requires_python: Option, #[serde(skip_serializing_if = "Vec::is_empty", default)] - extras: Vec, + pub extras: Vec, #[serde(skip_serializing_if = "Vec::is_empty", default)] - dependency_groups: Vec, + pub dependency_groups: Vec, #[serde(skip_serializing_if = "Vec::is_empty", default)] - default_groups: Vec, + pub default_groups: Vec, #[serde(skip_serializing_if = "Vec::is_empty", default)] pub packages: Vec, #[serde(skip_serializing_if = "Vec::is_empty", default)] @@ -966,9 +966,12 @@ impl<'lock> PylockToml { self, install_path: &Path, markers: &MarkerEnvironment, + extras: &[ExtraName], + groups: &[GroupName], tags: &Tags, build_options: &BuildOptions, ) -> Result { + // Convert the extras and dependency groups specifications to a concrete environment. let mut graph = petgraph::graph::DiGraph::with_capacity(self.packages.len(), self.packages.len()); @@ -977,7 +980,7 @@ impl<'lock> PylockToml { for package in self.packages { // Omit packages that aren't relevant to the current environment. - if !package.marker.evaluate(markers, &[]) { + if !package.marker.evaluate_pep751(markers, extras, groups) { continue; } diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs index b9edad20e..72c532d7b 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -22,6 +22,7 @@ use uv_distribution_types::{ use uv_fs::Simplified; use uv_install_wheel::LinkMode; use uv_installer::{SatisfiesResult, SitePackages}; +use uv_normalize::{DefaultExtras, DefaultGroups}; use uv_pep508::PackageName; use uv_pypi_types::Conflicts; use uv_python::{ @@ -439,11 +440,35 @@ pub(crate) async fn pip_install( 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 = toml::from_str::(&content) - .with_context(|| format!("Not a valid pylock.toml file: {}", pylock.user_display()))?; + let lock = toml::from_str::(&content).with_context(|| { + format!("Not a valid `pylock.toml` file: {}", pylock.user_display()) + })?; - let resolution = - lock.to_resolution(install_path, marker_env.markers(), &tags, &build_options)?; + // Convert the extras and groups specifications into a concrete form. + let extras = extras.with_defaults(DefaultExtras::default()); + let extras = extras + .extra_names(lock.extras.iter()) + .cloned() + .collect::>(); + + let groups = groups + .get(&pylock) + .cloned() + .unwrap_or_default() + .with_defaults(DefaultGroups::List(lock.default_groups.clone())); + let groups = groups + .group_names(lock.dependency_groups.iter()) + .cloned() + .collect::>(); + + let resolution = lock.to_resolution( + install_path, + marker_env.markers(), + &extras, + &groups, + &tags, + &build_options, + )?; let hasher = HashStrategy::from_resolution(&resolution, HashCheckingMode::Verify)?; (resolution, hasher) diff --git a/crates/uv/src/commands/pip/operations.rs b/crates/uv/src/commands/pip/operations.rs index 809f8bfdc..b5879ecf6 100644 --- a/crates/uv/src/commands/pip/operations.rs +++ b/crates/uv/src/commands/pip/operations.rs @@ -70,7 +70,7 @@ pub(crate) async fn read_requirements( "Use `package[extra]` syntax instead." }; return Err(anyhow!( - "Requesting extras requires a `pyproject.toml`, `setup.cfg`, or `setup.py` file. {hint}" + "Requesting extras requires a `pylock.toml`, `pyproject.toml`, `setup.cfg`, or `setup.py` file. {hint}" ) .into()); } diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index 47d180a74..2f46ef502 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -18,13 +18,14 @@ use uv_distribution_types::{DependencyMetadata, Index, IndexLocations, Origin, R use uv_fs::Simplified; use uv_install_wheel::LinkMode; use uv_installer::SitePackages; +use uv_normalize::{DefaultExtras, DefaultGroups}; use uv_pep508::PackageName; use uv_pypi_types::Conflicts; use uv_python::{ EnvironmentPreference, Prefix, PythonEnvironment, PythonInstallation, PythonPreference, PythonRequest, PythonVersion, Target, }; -use uv_requirements::{RequirementsSource, RequirementsSpecification}; +use uv_requirements::{GroupsSpecification, RequirementsSource, RequirementsSpecification}; use uv_resolver::{ DependencyMode, ExcludeNewer, FlatIndex, OptionsBuilder, PrereleaseMode, PylockToml, PythonRequirement, ResolutionMode, ResolverEnvironment, @@ -48,6 +49,8 @@ pub(crate) async fn pip_sync( requirements: &[RequirementsSource], constraints: &[RequirementsSource], build_constraints: &[RequirementsSource], + extras: &ExtrasSpecification, + groups: &GroupsSpecification, reinstall: Reinstall, link_mode: LinkMode, compile: bool, @@ -91,8 +94,6 @@ pub(crate) async fn pip_sync( // Initialize a few defaults. let overrides = &[]; - let extras = ExtrasSpecification::default(); - let groups = None; let upgrade = Upgrade::default(); let resolution_mode = ResolutionMode::default(); let prerelease_mode = PrereleaseMode::default(); @@ -118,8 +119,8 @@ pub(crate) async fn pip_sync( requirements, constraints, overrides, - &extras, - groups, + extras, + Some(groups), &client_builder, ) .await?; @@ -377,11 +378,35 @@ pub(crate) async fn pip_sync( 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 = toml::from_str::(&content) - .with_context(|| format!("Not a valid pylock.toml file: {}", pylock.user_display()))?; + let lock = toml::from_str::(&content).with_context(|| { + format!("Not a valid `pylock.toml` file: {}", pylock.user_display()) + })?; - let resolution = - lock.to_resolution(install_path, marker_env.markers(), &tags, &build_options)?; + // Convert the extras and groups specifications into a concrete form. + let extras = extras.with_defaults(DefaultExtras::default()); + let extras = extras + .extra_names(lock.extras.iter()) + .cloned() + .collect::>(); + + let groups = groups + .get(&pylock) + .cloned() + .unwrap_or_default() + .with_defaults(DefaultGroups::List(lock.default_groups.clone())); + let groups = groups + .group_names(lock.dependency_groups.iter()) + .cloned() + .collect::>(); + + let resolution = lock.to_resolution( + install_path, + marker_env.markers(), + &extras, + &groups, + &tags, + &build_options, + )?; let hasher = HashStrategy::from_resolution(&resolution, HashCheckingMode::Verify)?; (resolution, hasher) @@ -406,7 +431,7 @@ pub(crate) async fn pip_sync( source_trees, project, BTreeSet::default(), - &extras, + extras, &groups, preferences, site_packages.clone(), diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 6ca03a470..9a67bb877 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -566,11 +566,17 @@ async fn run(mut cli: Cli) -> Result { .into_iter() .map(RequirementsSource::from_constraints_txt) .collect::, _>>()?; + let groups = GroupsSpecification { + root: project_dir.to_path_buf(), + groups: args.settings.groups, + }; commands::pip_sync( &requirements, &constraints, &build_constraints, + &args.settings.extras, + &groups, args.settings.reinstall, args.settings.link_mode, args.settings.compile_bytecode, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index aa105cf97..534640f94 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -2058,6 +2058,10 @@ impl PipSyncSettings { src_file, constraints, build_constraints, + extra, + all_extras, + no_all_extras, + group, installer, refresh, require_hashes, @@ -2122,6 +2126,9 @@ impl PipSyncSettings { python_version, python_platform, strict: flag(strict, no_strict, "strict"), + extra, + all_extras: flag(all_extras, no_all_extras, "all-extras"), + group: Some(group), torch_backend, ..PipOptions::from(installer) }, diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index a977ac813..e1d48b86d 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -1298,27 +1298,27 @@ fn install_extras() -> Result<()> { uv_snapshot!(context.filters(), context.pip_install() .arg("--all-extras") .arg("-e") - .arg(context.workspace_root.join("scripts/packages/poetry_editable")), @r###" + .arg(context.workspace_root.join("scripts/packages/poetry_editable")), @r" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- - error: Requesting extras requires a `pyproject.toml`, `setup.cfg`, or `setup.py` file. Use `[extra]` syntax or `-r ` instead. - "### + error: Requesting extras requires a `pylock.toml`, `pyproject.toml`, `setup.cfg`, or `setup.py` file. Use `[extra]` syntax or `-r ` instead. + " ); // Request extras for a source tree uv_snapshot!(context.filters(), context.pip_install() .arg("--all-extras") - .arg(context.workspace_root.join("scripts/packages/poetry_editable")), @r###" + .arg(context.workspace_root.join("scripts/packages/poetry_editable")), @r" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- - error: Requesting extras requires a `pyproject.toml`, `setup.cfg`, or `setup.py` file. Use `package[extra]` syntax instead. - "### + error: Requesting extras requires a `pylock.toml`, `pyproject.toml`, `setup.cfg`, or `setup.py` file. Use `package[extra]` syntax instead. + " ); let requirements_txt = context.temp_dir.child("requirements.txt"); @@ -1327,14 +1327,14 @@ fn install_extras() -> Result<()> { // Request extras for a requirements file uv_snapshot!(context.filters(), context.pip_install() .arg("--all-extras") - .arg("-r").arg("requirements.txt"), @r###" + .arg("-r").arg("requirements.txt"), @r" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- - error: Requesting extras requires a `pyproject.toml`, `setup.cfg`, or `setup.py` file. Use `package[extra]` syntax instead. - "### + error: Requesting extras requires a `pylock.toml`, `pyproject.toml`, `setup.cfg`, or `setup.py` file. Use `package[extra]` syntax instead. + " ); let pyproject_toml = context.temp_dir.child("pyproject.toml"); @@ -11392,6 +11392,205 @@ fn pep_751_multiple_sources() -> Result<()> { Ok(()) } +#[test] +fn pep_751_groups() -> Result<()> { + let context = TestContext::new("3.13"); + + let pylock_toml = context.temp_dir.child("pylock.toml"); + pylock_toml.write_str( + r#" +lock-version = "1.0" +requires-python = "==3.13.*" +environments = [ + "python_version == \"3.13\"", +] +extras = ["async", "dev"] +dependency-groups = ["default", "test"] +default-groups = ["default"] +created-by = "pdm" +[[packages]] +name = "anyio" +version = "4.9.0" +requires-python = ">=3.9" +sdist = {name = "anyio-4.9.0.tar.gz", url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hashes = {sha256 = "673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028"}} +wheels = [ + {name = "anyio-4.9.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl",hashes = {sha256 = "9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c"}}, +] +marker = "\"async\" in extras" + +[packages.tool.pdm] +dependencies = [ + "exceptiongroup>=1.0.2; python_version < \"3.11\"", + "idna>=2.8", + "sniffio>=1.1", + "typing-extensions>=4.5; python_version < \"3.13\"", +] + +[[packages]] +name = "blinker" +version = "1.9.0" +requires-python = ">=3.9" +sdist = {name = "blinker-1.9.0.tar.gz", url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hashes = {sha256 = "b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf"}} +wheels = [ + {name = "blinker-1.9.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl",hashes = {sha256 = "ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc"}}, +] +marker = "\"dev\" in extras" + +[packages.tool.pdm] +dependencies = [] + +[[packages]] +name = "idna" +version = "3.10" +requires-python = ">=3.6" +sdist = {name = "idna-3.10.tar.gz", url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hashes = {sha256 = "12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}} +wheels = [ + {name = "idna-3.10-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl",hashes = {sha256 = "946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}}, +] +marker = "\"async\" in extras" + +[packages.tool.pdm] +dependencies = [] + +[[packages]] +name = "iniconfig" +version = "2.1.0" +requires-python = ">=3.8" +sdist = {name = "iniconfig-2.1.0.tar.gz", url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hashes = {sha256 = "3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}} +wheels = [ + {name = "iniconfig-2.1.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl",hashes = {sha256 = "9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}}, +] +marker = "\"default\" in dependency_groups" + +[packages.tool.pdm] +dependencies = [] + +[[packages]] +name = "pygments" +version = "2.19.2" +requires-python = ">=3.8" +sdist = {name = "pygments-2.19.2.tar.gz", url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hashes = {sha256 = "636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}} +wheels = [ + {name = "pygments-2.19.2-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl",hashes = {sha256 = "86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}}, +] +marker = "\"test\" in dependency_groups" + +[packages.tool.pdm] +dependencies = [] + +[[packages]] +name = "sniffio" +version = "1.3.1" +requires-python = ">=3.7" +sdist = {name = "sniffio-1.3.1.tar.gz", url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hashes = {sha256 = "f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}} +wheels = [ + {name = "sniffio-1.3.1-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl",hashes = {sha256 = "2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}}, +] +marker = "\"async\" in extras" + +[packages.tool.pdm] +dependencies = [] + +[tool.pdm] +hashes = {sha256 = "51795362d337720c28bd6c3a26eb33751f2b69590261f599ffb4172ee2c441c6"} + +[[tool.pdm.targets]] +requires_python = "==3.13.*" + "#, + )?; + + // By default, only `iniconfig` should be installed, since it's in the default group. + uv_snapshot!(context.filters(), context.pip_install() + .arg("--preview") + .arg("-r") + .arg("pylock.toml"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + iniconfig==2.1.0 + " + ); + + // With `--extra async`, `anyio` should be installed. + uv_snapshot!(context.filters(), context.pip_install() + .arg("--preview") + .arg("-r") + .arg("pylock.toml") + .arg("--extra") + .arg("async"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Prepared 3 packages in [TIME] + Installed 3 packages in [TIME] + + anyio==4.9.0 + + idna==3.10 + + sniffio==1.3.1 + " + ); + + // With `--group test`, `pygments` should be installed. + uv_snapshot!(context.filters(), context.pip_install() + .arg("--preview") + .arg("-r") + .arg("pylock.toml") + .arg("--group") + .arg("test"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + pygments==2.19.2 + " + ); + + // With `--all-extras`, `blinker` should be installed. + uv_snapshot!(context.filters(), context.pip_install() + .arg("--preview") + .arg("-r") + .arg("pylock.toml") + .arg("--all-extras"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + blinker==1.9.0 + " + ); + + // `--group pylock.toml:test` should be rejeceted. + uv_snapshot!(context.filters(), context.pip_install() + .arg("--preview") + .arg("-r") + .arg("pylock.toml") + .arg("--group") + .arg("pylock.toml:test"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: invalid value 'pylock.toml:test' for '--group ': The `--group` path is required to end in 'pyproject.toml' for compatibility with pip; got: pylock.toml + + For more information, try '--help'. + " + ); + + Ok(()) +} + /// Test that uv doesn't hang if an index returns a distribution for the wrong package. #[tokio::test] async fn bogus_redirect() -> Result<()> { diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 2ca95dce0..409ef5911 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -3637,7 +3637,9 @@ uv pip sync [OPTIONS] ...

    Options

    -
    --allow-empty-requirements

    Allow sync of empty requirements, which will clear the environment of all packages

    +
    --all-extras

    Include all optional dependencies.

    +

    Only applies to pylock.toml, pyproject.toml, setup.py, and setup.cfg sources.

    +
    --allow-empty-requirements

    Allow sync of empty requirements, which will clear the environment of all packages

    --allow-insecure-host, --trusted-host allow-insecure-host

    Allow insecure connections to a host.

    Can be provided multiple times.

    Expects to receive either a hostname (e.g., localhost), a host-port pair (e.g., localhost:8080), or a URL (e.g., https://localhost).

    @@ -3675,13 +3677,18 @@ uv pip sync [OPTIONS] ...
    --dry-run

    Perform a dry run, i.e., don't actually install anything but resolve the dependencies and print the resulting plan

    --exclude-newer exclude-newer

    Limit candidate packages to those that were uploaded prior to the given date.

    Accepts both RFC 3339 timestamps (e.g., 2006-12-02T02:07:43Z) and local dates in the same format (e.g., 2006-12-02) in your system's configured time zone.

    -

    May also be set with the UV_EXCLUDE_NEWER environment variable.

    --extra-index-url extra-index-url

    (Deprecated: use --index instead) Extra URLs of package indexes to use, in addition to --index-url.

    +

    May also be set with the UV_EXCLUDE_NEWER environment variable.

    --extra extra

    Include optional dependencies from the specified extra name; may be provided more than once.

    +

    Only applies to pylock.toml, pyproject.toml, setup.py, and setup.cfg sources.

    +
    --extra-index-url extra-index-url

    (Deprecated: use --index instead) Extra URLs of package indexes to use, in addition to --index-url.

    Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.

    All indexes provided via this flag take priority over the index specified by --index-url (which defaults to PyPI). When multiple --extra-index-url flags are provided, earlier values take priority.

    May also be set with the UV_EXTRA_INDEX_URL environment variable.

    Locations to search for candidate distributions, in addition to those found in the registry indexes.

    If a path, the target must be a directory that contains packages as wheel files (.whl) or source distributions (e.g., .tar.gz or .zip) at the top level.

    If a URL, the page must contain a flat list of links to package files adhering to the formats described above.

    -

    May also be set with the UV_FIND_LINKS environment variable.

    --help, -h

    Display the concise help for this command

    +

    May also be set with the UV_FIND_LINKS environment variable.

    --group group

    Install the specified dependency group from a pylock.toml or pyproject.toml.

    +

    If no path is provided, the pylock.toml or pyproject.toml in the working directory is used.

    +

    May be provided multiple times.

    +
    --help, -h

    Display the concise help for this command

    --index index

    The URLs to use when resolving dependencies, in addition to the default index.

    Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.

    All indexes provided via this flag take priority over the index specified by --default-index (which defaults to PyPI). When multiple --index flags are provided, earlier values take priority.

    @@ -3888,7 +3895,7 @@ uv pip install [OPTIONS] |--editable Options
    --all-extras

    Include all optional dependencies.

    -

    Only applies to pyproject.toml, setup.py, and setup.cfg sources.

    +

    Only applies to pylock.toml, pyproject.toml, setup.py, and setup.cfg sources.

    --allow-insecure-host, --trusted-host allow-insecure-host

    Allow insecure connections to a host.

    Can be provided multiple times.

    Expects to receive either a hostname (e.g., localhost), a host-port pair (e.g., localhost:8080), or a URL (e.g., https://localhost).

    @@ -3930,7 +3937,7 @@ uv pip install [OPTIONS] |--editable
    --exclude-newer exclude-newer

    Limit candidate packages to those that were uploaded prior to the given date.

    Accepts both RFC 3339 timestamps (e.g., 2006-12-02T02:07:43Z) and local dates in the same format (e.g., 2006-12-02) in your system's configured time zone.

    May also be set with the UV_EXCLUDE_NEWER environment variable.

    --extra extra

    Include optional dependencies from the specified extra name; may be provided more than once.

    -

    Only applies to pyproject.toml, setup.py, and setup.cfg sources.

    +

    Only applies to pylock.toml, pyproject.toml, setup.py, and setup.cfg sources.

    --extra-index-url extra-index-url

    (Deprecated: use --index instead) Extra URLs of package indexes to use, in addition to --index-url.

    Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.

    All indexes provided via this flag take priority over the index specified by --index-url (which defaults to PyPI). When multiple --extra-index-url flags are provided, earlier values take priority.

    @@ -3944,8 +3951,8 @@ uv pip install [OPTIONS] |--editable
  • fewest: Optimize for selecting the fewest number of versions for each package. Older versions may be preferred if they are compatible with a wider range of supported Python versions or platforms
  • requires-python: Optimize for selecting latest supported version of each package, for each supported Python version
  • -
    --group group

    Install the specified dependency group from a pyproject.toml.

    -

    If no path is provided, the pyproject.toml in the working directory is used.

    +
    --group group

    Install the specified dependency group from a pylock.toml or pyproject.toml.

    +

    If no path is provided, the pylock.toml or pyproject.toml in the working directory is used.

    May be provided multiple times.

    --help, -h

    Display the concise help for this command

    --index index

    The URLs to use when resolving dependencies, in addition to the default index.

    From ba1319450a145cbe92751243e93995ff0f4a0bb8 Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Mon, 21 Jul 2025 09:18:16 -0400 Subject: [PATCH 298/349] Update `toml` to v0.9 (#14571) ## Summary This should give us some performance and error message improvements. --------- Co-authored-by: Charlie Marsh Co-authored-by: Zanie Blue --- Cargo.lock | 44 ++++++++++++------- Cargo.toml | 4 +- crates/uv-build-frontend/src/lib.rs | 14 +++--- .../src/metadata/requires_dist.rs | 11 +++-- .../src/metadata/pyproject_toml.rs | 4 +- crates/uv-workspace/src/pyproject.rs | 7 +-- crates/uv/tests/it/edit.rs | 14 +++--- crates/uv/tests/it/lock.rs | 13 +++--- crates/uv/tests/it/pip_install.rs | 9 ++-- crates/uv/tests/it/show_settings.rs | 7 ++- crates/uv/tests/it/venv.rs | 8 ++-- 11 files changed, 72 insertions(+), 63 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7b73f29d7..e8f91b076 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1252,9 +1252,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "fontconfig-parser" @@ -3596,9 +3596,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.9" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" dependencies = [ "serde", ] @@ -4218,44 +4218,58 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.23" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac" dependencies = [ + "foldhash", + "indexmap", "serde", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_parser", + "toml_writer", + "winnow", ] [[package]] name = "toml_datetime" -version = "0.6.11" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.27" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +checksum = "d1dee9dc43ac2aaf7d3b774e2fba5148212bf2bd9374f4e50152ebe9afd03d42" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", - "toml_write", + "toml_parser", + "toml_writer", "winnow", ] [[package]] -name = "toml_write" -version = "0.1.2" +name = "toml_parser" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" [[package]] name = "tower" diff --git a/Cargo.toml b/Cargo.toml index 2c32ce8d0..7c858a81d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -172,8 +172,8 @@ tl = { git = "https://github.com/astral-sh/tl.git", rev = "6e25b2ee2513d75385101 tokio = { version = "1.40.0", features = ["fs", "io-util", "macros", "process", "rt", "signal", "sync"] } tokio-stream = { version = "0.1.16" } tokio-util = { version = "0.7.12", features = ["compat", "io"] } -toml = { version = "0.8.19" } -toml_edit = { version = "0.22.21", features = ["serde"] } +toml = { version = "0.9.2", features = ["fast_hash"] } +toml_edit = { version = "0.23.2", features = ["serde"] } tracing = { version = "0.1.40" } tracing-durations-export = { version = "0.3.0", features = ["plot"] } tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json", "registry"] } diff --git a/crates/uv-build-frontend/src/lib.rs b/crates/uv-build-frontend/src/lib.rs index 67bee9619..e2a128747 100644 --- a/crates/uv-build-frontend/src/lib.rs +++ b/crates/uv-build-frontend/src/lib.rs @@ -19,8 +19,8 @@ use fs_err as fs; use indoc::formatdoc; use itertools::Itertools; use rustc_hash::FxHashMap; -use serde::de::{IntoDeserializer, SeqAccess, Visitor, value}; -use serde::{Deserialize, Deserializer, de}; +use serde::de::{self, IntoDeserializer, SeqAccess, Visitor, value}; +use serde::{Deserialize, Deserializer}; use tempfile::TempDir; use tokio::io::AsyncBufReadExt; use tokio::process::Command; @@ -511,12 +511,10 @@ impl SourceBuild { ) -> Result<(Pep517Backend, Option), Box> { match fs::read_to_string(source_tree.join("pyproject.toml")) { Ok(toml) => { - let pyproject_toml: toml_edit::ImDocument<_> = - toml_edit::ImDocument::from_str(&toml) - .map_err(Error::InvalidPyprojectTomlSyntax)?; - let pyproject_toml: PyProjectToml = - PyProjectToml::deserialize(pyproject_toml.into_deserializer()) - .map_err(Error::InvalidPyprojectTomlSchema)?; + let pyproject_toml = toml_edit::Document::from_str(&toml) + .map_err(Error::InvalidPyprojectTomlSyntax)?; + let pyproject_toml = PyProjectToml::deserialize(pyproject_toml.into_deserializer()) + .map_err(Error::InvalidPyprojectTomlSchema)?; let backend = if let Some(build_system) = pyproject_toml.build_system { // If necessary, lower the requirements. diff --git a/crates/uv-distribution/src/metadata/requires_dist.rs b/crates/uv-distribution/src/metadata/requires_dist.rs index e9f36f174..a5645c126 100644 --- a/crates/uv-distribution/src/metadata/requires_dist.rs +++ b/crates/uv-distribution/src/metadata/requires_dist.rs @@ -618,14 +618,13 @@ mod test { tqdm = { url = invalid url to tqdm-4.66.0-py3-none-any.whl" } "#}; - assert_snapshot!(format_err(input).await, @r###" - error: TOML parse error at line 8, column 16 + assert_snapshot!(format_err(input).await, @r#" + error: TOML parse error at line 8, column 28 | 8 | tqdm = { url = invalid url to tqdm-4.66.0-py3-none-any.whl" } - | ^ - invalid string - expected `"`, `'` - "###); + | ^ + missing comma between key-value pairs, expected `,` + "#); } #[tokio::test] diff --git a/crates/uv-pypi-types/src/metadata/pyproject_toml.rs b/crates/uv-pypi-types/src/metadata/pyproject_toml.rs index 113021a34..8487f058a 100644 --- a/crates/uv-pypi-types/src/metadata/pyproject_toml.rs +++ b/crates/uv-pypi-types/src/metadata/pyproject_toml.rs @@ -19,9 +19,9 @@ pub struct PyProjectToml { impl PyProjectToml { pub fn from_toml(toml: &str) -> Result { - let pyproject_toml: toml_edit::ImDocument<_> = toml_edit::ImDocument::from_str(toml) + let pyproject_toml = toml_edit::Document::from_str(toml) .map_err(MetadataError::InvalidPyprojectTomlSyntax)?; - let pyproject_toml: Self = PyProjectToml::deserialize(pyproject_toml.into_deserializer()) + let pyproject_toml = PyProjectToml::deserialize(pyproject_toml.into_deserializer()) .map_err(MetadataError::InvalidPyprojectTomlSchema)?; Ok(pyproject_toml) } diff --git a/crates/uv-workspace/src/pyproject.rs b/crates/uv-workspace/src/pyproject.rs index 4a994b801..b02dadc5d 100644 --- a/crates/uv-workspace/src/pyproject.rs +++ b/crates/uv-workspace/src/pyproject.rs @@ -17,7 +17,8 @@ use std::str::FromStr; use glob::Pattern; use owo_colors::OwoColorize; use rustc_hash::{FxBuildHasher, FxHashSet}; -use serde::{Deserialize, Deserializer, Serialize, de::IntoDeserializer, de::SeqAccess}; +use serde::de::{IntoDeserializer, SeqAccess}; +use serde::{Deserialize, Deserializer, Serialize}; use thiserror::Error; use uv_build_backend::BuildBackendSettings; use uv_distribution_types::{Index, IndexName, RequirementSource}; @@ -72,8 +73,8 @@ pub struct PyProjectToml { impl PyProjectToml { /// Parse a `PyProjectToml` from a raw TOML string. pub fn from_string(raw: String) -> Result { - let pyproject: toml_edit::ImDocument<_> = - toml_edit::ImDocument::from_str(&raw).map_err(PyprojectTomlError::TomlSyntax)?; + let pyproject = + toml_edit::Document::from_str(&raw).map_err(PyprojectTomlError::TomlSyntax)?; let pyproject = PyProjectToml::deserialize(pyproject.into_deserializer()) .map_err(PyprojectTomlError::TomlSchema)?; Ok(PyProjectToml { raw, ..pyproject }) diff --git a/crates/uv/tests/it/edit.rs b/crates/uv/tests/it/edit.rs index aa494435c..a7d11091b 100644 --- a/crates/uv/tests/it/edit.rs +++ b/crates/uv/tests/it/edit.rs @@ -3568,7 +3568,7 @@ fn add_update_git_reference_script() -> Result<()> { filters => context.filters(), }, { assert_snapshot!( - script_content, @r###" + script_content, @r##" # /// script # requires-python = ">=3.11" # dependencies = [ @@ -3581,7 +3581,7 @@ fn add_update_git_reference_script() -> Result<()> { import time time.sleep(5) - "### + "## ); }); @@ -3601,7 +3601,7 @@ fn add_update_git_reference_script() -> Result<()> { filters => context.filters(), }, { assert_snapshot!( - script_content, @r###" + script_content, @r##" # /// script # requires-python = ">=3.11" # dependencies = [ @@ -3614,7 +3614,7 @@ fn add_update_git_reference_script() -> Result<()> { import time time.sleep(5) - "### + "## ); }); @@ -10896,7 +10896,7 @@ fn add_preserves_empty_comment() -> Result<()> { filters => context.filters(), }, { assert_snapshot!( - pyproject_toml, @r###" + pyproject_toml, @r#" [project] name = "project" version = "0.1.0" @@ -10906,7 +10906,7 @@ fn add_preserves_empty_comment() -> Result<()> { # Second line. "anyio==3.7.0", ] - "### + "# ); }); @@ -13189,7 +13189,7 @@ fn add_path_with_existing_workspace() -> Result<()> { [tool.uv.workspace] members = [ "project", - "dep", + "dep", ] "# ); diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index ff9b711b7..0962ff6d2 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -18868,7 +18868,7 @@ fn lock_duplicate_sources() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.lock(), @r###" + uv_snapshot!(context.filters(), context.lock(), @r#" success: false exit_code: 2 ----- stdout ----- @@ -18878,17 +18878,16 @@ fn lock_duplicate_sources() -> Result<()> { TOML parse error at line 9, column 9 | 9 | python-multipart = { url = "https://files.pythonhosted.org/packages/c0/3e/9fbfd74e7f5b54f653f7ca99d44ceb56e718846920162165061c4c22b71a/python_multipart-0.0.8-py3-none-any.whl" } - | ^ - duplicate key `python-multipart` in table `tool.uv.sources` + | ^^^^^^^^^^^^^^^^ + duplicate key error: Failed to parse: `pyproject.toml` Caused by: TOML parse error at line 9, column 9 | 9 | python-multipart = { url = "https://files.pythonhosted.org/packages/c0/3e/9fbfd74e7f5b54f653f7ca99d44ceb56e718846920162165061c4c22b71a/python_multipart-0.0.8-py3-none-any.whl" } - | ^ - duplicate key `python-multipart` in table `tool.uv.sources` - - "###); + | ^^^^^^^^^^^^^^^^ + duplicate key + "#); let pyproject_toml = context.temp_dir.child("pyproject.toml"); pyproject_toml.write_str( diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index e1d48b86d..de62c4222 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -118,7 +118,7 @@ fn invalid_pyproject_toml_syntax() -> Result<()> { uv_snapshot!(context.pip_install() .arg("-r") - .arg("pyproject.toml"), @r###" + .arg("pyproject.toml"), @r" success: false exit_code: 2 ----- stdout ----- @@ -129,16 +129,15 @@ fn invalid_pyproject_toml_syntax() -> Result<()> { | 1 | 123 - 456 | ^ - expected `.`, `=` + key with no value, expected `=` error: Failed to parse: `pyproject.toml` Caused by: TOML parse error at line 1, column 5 | 1 | 123 - 456 | ^ - expected `.`, `=` - - "### + key with no value, expected `=` + " ); Ok(()) diff --git a/crates/uv/tests/it/show_settings.rs b/crates/uv/tests/it/show_settings.rs index 293b437d6..bbcddd2b1 100644 --- a/crates/uv/tests/it/show_settings.rs +++ b/crates/uv/tests/it/show_settings.rs @@ -4323,7 +4323,7 @@ fn resolve_config_file() -> anyhow::Result<()> { .arg("--show-settings") .arg("--config-file") .arg(config.path()) - .arg("requirements.in"), @r###" + .arg("requirements.in"), @r#" success: false exit_code: 2 ----- stdout ----- @@ -4335,9 +4335,8 @@ fn resolve_config_file() -> anyhow::Result<()> { | 9 | "" | ^ - expected `.`, `=` - - "### + key with no value, expected `=` + "# ); Ok(()) diff --git a/crates/uv/tests/it/venv.rs b/crates/uv/tests/it/venv.rs index 120d7def2..726d1731b 100644 --- a/crates/uv/tests/it/venv.rs +++ b/crates/uv/tests/it/venv.rs @@ -718,7 +718,7 @@ fn create_venv_warns_user_on_requires_python_discovery_error() -> Result<()> { let pyproject_toml = context.temp_dir.child("pyproject.toml"); pyproject_toml.write_str(indoc! { r"invalid toml" })?; - uv_snapshot!(context.filters(), context.venv(), @r###" + uv_snapshot!(context.filters(), context.venv(), @r" success: true exit_code: 0 ----- stdout ----- @@ -729,19 +729,19 @@ fn create_venv_warns_user_on_requires_python_discovery_error() -> Result<()> { | 1 | invalid toml | ^ - expected `.`, `=` + key with no value, expected `=` warning: Failed to parse `pyproject.toml` during environment creation: TOML parse error at line 1, column 9 | 1 | invalid toml | ^ - expected `.`, `=` + key with no value, expected `=` Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] Creating virtual environment at: .venv Activate with: source .venv/[BIN]/activate - "### + " ); context.venv.assert(predicates::path::is_dir()); From d768dedff674de659ae8c7fc3d03faf41a13874d Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Jul 2025 14:17:06 +0000 Subject: [PATCH 299/349] Remove `version_get_fallback_unmanaged_json` test (#14786) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `version_get_fallback_unmanaged_json` test was failing when running tests outside of a git checkout (e.g., from a release tarball) due to inconsistent behavior based on git availability. The test had conditional logic that expected different outcomes depending on whether `git_version_info_expected()` returned true or false: - In git checkouts: Expected failure with "The project is marked as unmanaged" error - Outside git checkouts: Expected success with fallback behavior showing version info However, the fallback behavior was removed in version 0.8.0, making this test obsolete. All other similar tests (`version_get_fallback_unmanaged`, `version_get_fallback_unmanaged_short`, `version_get_fallback_unmanaged_strict`) consistently expect failure when a project is marked as unmanaged, regardless of git availability. This change removes the problematic test entirely, as suggested by @zanieb. All remaining version tests (51 total) continue to pass. Fixes #14785. --- 💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: zanieb <2586601+zanieb@users.noreply.github.com> --- crates/uv/tests/it/version.rs | 80 ----------------------------------- 1 file changed, 80 deletions(-) diff --git a/crates/uv/tests/it/version.rs b/crates/uv/tests/it/version.rs index 78dd64252..e5f6e1687 100644 --- a/crates/uv/tests/it/version.rs +++ b/crates/uv/tests/it/version.rs @@ -1545,86 +1545,6 @@ fn git_version_info_expected() -> bool { git_dir.exists() } -// version_get_fallback with `--json` -#[test] -fn version_get_fallback_unmanaged_json() -> Result<()> { - let context = TestContext::new("3.12"); - - let pyproject_toml = context.temp_dir.child("pyproject.toml"); - pyproject_toml.write_str( - r#" - [project] - name = "myapp" - version = "0.1.2" - - [tool.uv] - managed = false - "#, - )?; - - let filters = context - .filters() - .into_iter() - .chain([ - ( - r#"version": "\d+\.\d+\.\d+(-(alpha|beta|rc)\.\d+)?(\+\d+)?""#, - r#"version": "[VERSION]""#, - ), - ( - r#"short_commit_hash": ".*""#, - r#"short_commit_hash": "[HASH]""#, - ), - (r#"commit_hash": ".*""#, r#"commit_hash": "[LONGHASH]""#), - (r#"commit_date": ".*""#, r#"commit_date": "[DATE]""#), - (r#"last_tag": (".*"|null)"#, r#"last_tag": "[TAG]""#), - ( - r#"commits_since_last_tag": .*"#, - r#"commits_since_last_tag": [COUNT]"#, - ), - ]) - .collect::>(); - if git_version_info_expected() { - uv_snapshot!(filters, context.version() - .arg("--output-format").arg("json"), @r" - success: false - exit_code: 2 - ----- stdout ----- - - ----- stderr ----- - error: The project is marked as unmanaged: `[TEMP_DIR]/` - "); - } else { - uv_snapshot!(filters, context.version() - .arg("--output-format").arg("json"), @r#" - success: true - exit_code: 0 - ----- stdout ----- - { - "package_name": "uv", - "version": "[VERSION]", - "commit_info": null - } - - ----- stderr ----- - warning: Failed to read project metadata (The project is marked as unmanaged: `[TEMP_DIR]/`). Running `uv self version` for compatibility. This fallback will be removed in the future; pass `--preview` to force an error. - "#); - } - - let pyproject = fs_err::read_to_string(&pyproject_toml)?; - assert_snapshot!( - pyproject, - @r#" - [project] - name = "myapp" - version = "0.1.2" - - [tool.uv] - managed = false - "# - ); - Ok(()) -} - // Should error if this pyproject.toml isn't usable for whatever reason // and --project was passed explicitly. #[test] From aafeda2253369803bc315f0d894e7b58f83c007d Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 21 Jul 2025 10:37:14 -0400 Subject: [PATCH 300/349] Enforce `requires-python` in `pylock.toml` (#14787) ## Summary Turns out we weren't validating this at install-time. --- .../src/lock/export/pylock_toml.rs | 2 +- crates/uv/src/commands/pip/install.rs | 11 +++++ crates/uv/src/commands/pip/sync.rs | 11 +++++ crates/uv/tests/it/pip_install.rs | 45 +++++++++++++++++++ 4 files changed, 68 insertions(+), 1 deletion(-) diff --git a/crates/uv-resolver/src/lock/export/pylock_toml.rs b/crates/uv-resolver/src/lock/export/pylock_toml.rs index ef3ad8615..642b9488a 100644 --- a/crates/uv-resolver/src/lock/export/pylock_toml.rs +++ b/crates/uv-resolver/src/lock/export/pylock_toml.rs @@ -186,7 +186,7 @@ pub struct PylockToml { lock_version: Version, created_by: String, #[serde(skip_serializing_if = "Option::is_none")] - requires_python: Option, + pub requires_python: Option, #[serde(skip_serializing_if = "Vec::is_empty", default)] pub extras: Vec, #[serde(skip_serializing_if = "Vec::is_empty", default)] diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs index 72c532d7b..cb1229d72 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -444,6 +444,17 @@ pub(crate) async fn pip_install( 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, + )); + } + } + // Convert the extras and groups specifications into a concrete form. let extras = extras.with_defaults(DefaultExtras::default()); let extras = extras diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index 2f46ef502..2fe5fbe87 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -382,6 +382,17 @@ pub(crate) async fn pip_sync( 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, + )); + } + } + // Convert the extras and groups specifications into a concrete form. let extras = extras.with_defaults(DefaultExtras::default()); let extras = extras diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index de62c4222..936f77aff 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -11590,6 +11590,51 @@ requires_python = "==3.13.*" Ok(()) } +#[test] +fn pep_751_requires_python() -> Result<()> { + let context = TestContext::new_with_versions(&["3.12", "3.13"]); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.13" + dependencies = ["iniconfig"] + "#, + )?; + + context + .export() + .arg("-o") + .arg("pylock.toml") + .assert() + .success(); + + context + .venv() + .arg("--python") + .arg("3.12") + .assert() + .success(); + + uv_snapshot!(context.filters(), context.pip_install() + .arg("--preview") + .arg("-r") + .arg("pylock.toml"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: The requested interpreter resolved to Python 3.12.[X], which is incompatible with the `pylock.toml`'s Python requirement: `>=3.13` + " + ); + + Ok(()) +} + /// Test that uv doesn't hang if an index returns a distribution for the wrong package. #[tokio::test] async fn bogus_redirect() -> Result<()> { From 80708dea6e09c998204c61bb09230ac7beda8210 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 21 Jul 2025 10:48:52 -0400 Subject: [PATCH 301/349] Use a match for Windows executables in `venv` (#14766) ## Summary I found it confusing that the `else` case for `== "graalpy"` is still necessary for the `== "pypy"` branch (i.e., that `pythonw.exe` is copied for PyPy despite not being in the `== "pypy"` branch). Instead, we now use a match for PyP, GraalPy, and then everything else. --- crates/uv-virtualenv/src/virtualenv.rs | 163 +++++++++++++------------ 1 file changed, 88 insertions(+), 75 deletions(-) diff --git a/crates/uv-virtualenv/src/virtualenv.rs b/crates/uv-virtualenv/src/virtualenv.rs index fe464c04a..822e8d7bb 100644 --- a/crates/uv-virtualenv/src/virtualenv.rs +++ b/crates/uv-virtualenv/src/virtualenv.rs @@ -268,6 +268,7 @@ pub(crate) fn create( create_link_to_executable(targetw.as_path(), executable_target) .map_err(Error::Python)?; } else { + // Always copy `python.exe`. copy_launcher_windows( WindowsExecutable::Python, interpreter, @@ -276,81 +277,93 @@ pub(crate) fn create( python_home, )?; - if interpreter.markers().implementation_name() == "graalpy" { - copy_launcher_windows( - WindowsExecutable::GraalPy, - interpreter, - &base_python, - &scripts, - python_home, - )?; - copy_launcher_windows( - WindowsExecutable::PythonMajor, - interpreter, - &base_python, - &scripts, - python_home, - )?; - } else { - copy_launcher_windows( - WindowsExecutable::Pythonw, - interpreter, - &base_python, - &scripts, - python_home, - )?; - } - - if interpreter.markers().implementation_name() == "pypy" { - copy_launcher_windows( - WindowsExecutable::PythonMajor, - interpreter, - &base_python, - &scripts, - python_home, - )?; - copy_launcher_windows( - WindowsExecutable::PythonMajorMinor, - interpreter, - &base_python, - &scripts, - python_home, - )?; - copy_launcher_windows( - WindowsExecutable::PyPy, - interpreter, - &base_python, - &scripts, - python_home, - )?; - copy_launcher_windows( - WindowsExecutable::PyPyMajor, - interpreter, - &base_python, - &scripts, - python_home, - )?; - copy_launcher_windows( - WindowsExecutable::PyPyMajorMinor, - interpreter, - &base_python, - &scripts, - python_home, - )?; - copy_launcher_windows( - WindowsExecutable::PyPyw, - interpreter, - &base_python, - &scripts, - python_home, - )?; - copy_launcher_windows( - WindowsExecutable::PyPyMajorMinorw, - interpreter, - &base_python, - &scripts, - python_home, - )?; + match interpreter.implementation_name() { + "graalpy" => { + // For GraalPy, copy `graalpy.exe` and `python3.exe`. + copy_launcher_windows( + WindowsExecutable::GraalPy, + interpreter, + &base_python, + &scripts, + python_home, + )?; + copy_launcher_windows( + WindowsExecutable::PythonMajor, + interpreter, + &base_python, + &scripts, + python_home, + )?; + } + "pypy" => { + // For PyPy, copy all versioned executables and all PyPy-specific executables. + copy_launcher_windows( + WindowsExecutable::PythonMajor, + interpreter, + &base_python, + &scripts, + python_home, + )?; + copy_launcher_windows( + WindowsExecutable::PythonMajorMinor, + interpreter, + &base_python, + &scripts, + python_home, + )?; + copy_launcher_windows( + WindowsExecutable::Pythonw, + interpreter, + &base_python, + &scripts, + python_home, + )?; + copy_launcher_windows( + WindowsExecutable::PyPy, + interpreter, + &base_python, + &scripts, + python_home, + )?; + copy_launcher_windows( + WindowsExecutable::PyPyMajor, + interpreter, + &base_python, + &scripts, + python_home, + )?; + copy_launcher_windows( + WindowsExecutable::PyPyMajorMinor, + interpreter, + &base_python, + &scripts, + python_home, + )?; + copy_launcher_windows( + WindowsExecutable::PyPyw, + interpreter, + &base_python, + &scripts, + python_home, + )?; + copy_launcher_windows( + WindowsExecutable::PyPyMajorMinorw, + interpreter, + &base_python, + &scripts, + python_home, + )?; + } + _ => { + // For all other interpreters, copy `pythonw.exe`. + copy_launcher_windows( + WindowsExecutable::Pythonw, + interpreter, + &base_python, + &scripts, + python_home, + )?; + } } } } From d052427c374c7fe42e65b4e56fd61a6febc79453 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 21 Jul 2025 11:53:28 -0400 Subject: [PATCH 302/349] Accept `&Path` when creating executable links (#14791) ## Summary I don't see a great reason for this to take an owned value. It only needs an owned value for error cases. --- crates/uv-python/src/managed.rs | 12 ++++++------ crates/uv-virtualenv/src/virtualenv.rs | 4 ++-- crates/uv/src/commands/python/install.rs | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/crates/uv-python/src/managed.rs b/crates/uv-python/src/managed.rs index ad1dacac6..9ee72adda 100644 --- a/crates/uv-python/src/managed.rs +++ b/crates/uv-python/src/managed.rs @@ -847,7 +847,7 @@ fn executable_path_from_base( /// Create a link to a managed Python executable. /// /// If the file already exists at the link path, an error will be returned. -pub fn create_link_to_executable(link: &Path, executable: PathBuf) -> Result<(), Error> { +pub fn create_link_to_executable(link: &Path, executable: &Path) -> Result<(), Error> { let link_parent = link.parent().ok_or(Error::NoExecutableDirectory)?; fs_err::create_dir_all(link_parent).map_err(|err| Error::ExecutableDirectory { to: link_parent.to_path_buf(), @@ -856,20 +856,20 @@ pub fn create_link_to_executable(link: &Path, executable: PathBuf) -> Result<(), if cfg!(unix) { // Note this will never copy on Unix — we use it here to allow compilation on Windows - match symlink_or_copy_file(&executable, link) { + match symlink_or_copy_file(executable, link) { Ok(()) => Ok(()), Err(err) if err.kind() == io::ErrorKind::NotFound => { - Err(Error::MissingExecutable(executable.clone())) + Err(Error::MissingExecutable(executable.to_path_buf())) } Err(err) => Err(Error::LinkExecutable { - from: executable, + from: executable.to_path_buf(), to: link.to_path_buf(), err, }), } } else if cfg!(windows) { // TODO(zanieb): Install GUI launchers as well - let launcher = windows_python_launcher(&executable, false)?; + let launcher = windows_python_launcher(executable, false)?; // OK to use `std::fs` here, `fs_err` does not support `File::create_new` and we attach // error context anyway @@ -878,7 +878,7 @@ pub fn create_link_to_executable(link: &Path, executable: PathBuf) -> Result<(), std::fs::File::create_new(link) .and_then(|mut file| file.write_all(launcher.as_ref())) .map_err(|err| Error::LinkExecutable { - from: executable, + from: executable.to_path_buf(), to: link.to_path_buf(), err, }) diff --git a/crates/uv-virtualenv/src/virtualenv.rs b/crates/uv-virtualenv/src/virtualenv.rs index 822e8d7bb..5d3ab4a88 100644 --- a/crates/uv-virtualenv/src/virtualenv.rs +++ b/crates/uv-virtualenv/src/virtualenv.rs @@ -262,10 +262,10 @@ pub(crate) fn create( if cfg!(windows) { if using_minor_version_link { let target = scripts.join(WindowsExecutable::Python.exe(interpreter)); - create_link_to_executable(target.as_path(), executable_target.clone()) + create_link_to_executable(target.as_path(), &executable_target) .map_err(Error::Python)?; let targetw = scripts.join(WindowsExecutable::Pythonw.exe(interpreter)); - create_link_to_executable(targetw.as_path(), executable_target) + create_link_to_executable(targetw.as_path(), &executable_target) .map_err(Error::Python)?; } else { // Always copy `python.exe`. diff --git a/crates/uv/src/commands/python/install.rs b/crates/uv/src/commands/python/install.rs index 37d6a6777..e54c44424 100644 --- a/crates/uv/src/commands/python/install.rs +++ b/crates/uv/src/commands/python/install.rs @@ -768,7 +768,7 @@ fn create_bin_links( installation.executable(false) }; - match create_link_to_executable(&target, executable.clone()) { + match create_link_to_executable(&target, &executable) { Ok(()) => { debug!( "Installed executable at `{}` for {}", @@ -925,7 +925,7 @@ fn create_bin_links( .remove(&target); } - if let Err(err) = create_link_to_executable(&target, executable) { + if let Err(err) = create_link_to_executable(&target, &executable) { errors.push(( InstallErrorKind::Bin, installation.key().clone(), @@ -953,7 +953,7 @@ fn create_bin_links( errors.push(( InstallErrorKind::Bin, installation.key().clone(), - anyhow::Error::new(err), + Error::new(err), )); } } From f3dc457d2a96fb27b88c7cfafe97b7f04ffe78aa Mon Sep 17 00:00:00 2001 From: konsti Date: Mon, 21 Jul 2025 18:21:46 +0200 Subject: [PATCH 303/349] Introduce a generic type for list operations (#14792) We currently have two marker keys that a list, `extras` and `dependency_groups`, both from PEP 751. With the variants PEP, we will add three more. This change is broken out of the wheel variants PR to introduce generic marker list support, plus a change to use `ContainerOperator` in more places. --- crates/uv-pep508/src/lib.rs | 4 +- crates/uv-pep508/src/marker/algebra.rs | 83 ++--- crates/uv-pep508/src/marker/lowering.rs | 41 +-- crates/uv-pep508/src/marker/mod.rs | 4 +- crates/uv-pep508/src/marker/parse.rs | 155 ++++---- crates/uv-pep508/src/marker/simplify.rs | 77 ++-- crates/uv-pep508/src/marker/tree.rs | 375 +++++++------------- crates/uv-pep508/src/verbatim_url.rs | 1 + crates/uv-resolver/src/marker.rs | 7 +- crates/uv-resolver/src/resolution/output.rs | 7 +- 10 files changed, 276 insertions(+), 478 deletions(-) diff --git a/crates/uv-pep508/src/lib.rs b/crates/uv-pep508/src/lib.rs index f63d46206..10e4142e7 100644 --- a/crates/uv-pep508/src/lib.rs +++ b/crates/uv-pep508/src/lib.rs @@ -32,8 +32,8 @@ pub use marker::{ CanonicalMarkerValueExtra, CanonicalMarkerValueString, CanonicalMarkerValueVersion, ContainsMarkerTree, ExtraMarkerTree, ExtraOperator, InMarkerTree, MarkerEnvironment, MarkerEnvironmentBuilder, MarkerExpression, MarkerOperator, MarkerTree, MarkerTreeContents, - MarkerTreeKind, MarkerValue, MarkerValueExtra, MarkerValueString, MarkerValueVersion, - MarkerWarningKind, StringMarkerTree, StringVersion, VersionMarkerTree, + MarkerTreeKind, MarkerValue, MarkerValueExtra, MarkerValueList, MarkerValueString, + MarkerValueVersion, MarkerWarningKind, StringMarkerTree, StringVersion, VersionMarkerTree, }; pub use origin::RequirementOrigin; #[cfg(feature = "non-pep508-extensions")] diff --git a/crates/uv-pep508/src/marker/algebra.rs b/crates/uv-pep508/src/marker/algebra.rs index d1a369491..6b166dbc6 100644 --- a/crates/uv-pep508/src/marker/algebra.rs +++ b/crates/uv-pep508/src/marker/algebra.rs @@ -59,10 +59,10 @@ use uv_pep440::{Operator, Version, VersionSpecifier, release_specifier_to_range} use crate::marker::MarkerValueExtra; use crate::marker::lowering::{ - CanonicalMarkerValueDependencyGroup, CanonicalMarkerValueExtra, CanonicalMarkerValueString, + CanonicalMarkerListPair, CanonicalMarkerValueExtra, CanonicalMarkerValueString, CanonicalMarkerValueVersion, }; -use crate::marker::tree::{ContainerOperator, MarkerValueDependencyGroup}; +use crate::marker::tree::ContainerOperator; use crate::{ ExtraOperator, MarkerExpression, MarkerOperator, MarkerValueString, MarkerValueVersion, }; @@ -188,19 +188,19 @@ impl InternerGuard<'_> { MarkerExpression::VersionIn { key, versions, - negated, + operator, } => match key { MarkerValueVersion::ImplementationVersion => ( Variable::Version(CanonicalMarkerValueVersion::ImplementationVersion), - Edges::from_versions(&versions, negated), + Edges::from_versions(&versions, operator), ), MarkerValueVersion::PythonFullVersion => ( Variable::Version(CanonicalMarkerValueVersion::PythonFullVersion), - Edges::from_versions(&versions, negated), + Edges::from_versions(&versions, operator), ), // Normalize `python_version` markers to `python_full_version` nodes. MarkerValueVersion::PythonVersion => { - match Edges::from_python_versions(versions, negated) { + match Edges::from_python_versions(versions, operator) { Ok(edges) => ( Variable::Version(CanonicalMarkerValueVersion::PythonFullVersion), edges, @@ -315,6 +315,10 @@ impl InternerGuard<'_> { }; (Variable::String(key), Edges::from_string(operator, value)) } + MarkerExpression::List { pair, operator } => ( + Variable::List(pair), + Edges::from_bool(operator == ContainerOperator::In), + ), // A variable representing the existence or absence of a particular extra. MarkerExpression::Extra { name: MarkerValueExtra::Extra(extra), @@ -335,48 +339,6 @@ impl InternerGuard<'_> { name: MarkerValueExtra::Arbitrary(_), .. } => return NodeId::FALSE, - // A variable representing the existence or absence of a particular extra, in the - // context of a PEP 751 lockfile. - MarkerExpression::Extras { - name: MarkerValueExtra::Extra(extra), - operator: ContainerOperator::In, - } => ( - Variable::Extras(CanonicalMarkerValueExtra::Extra(extra)), - Edges::from_bool(true), - ), - MarkerExpression::Extras { - name: MarkerValueExtra::Extra(extra), - operator: ContainerOperator::NotIn, - } => ( - Variable::Extras(CanonicalMarkerValueExtra::Extra(extra)), - Edges::from_bool(false), - ), - // Invalid `extras` names are always `false`. - MarkerExpression::Extras { - name: MarkerValueExtra::Arbitrary(_), - .. - } => return NodeId::FALSE, - // A variable representing the existence or absence of a particular extra, in the - // context of a PEP 751 lockfile. - MarkerExpression::DependencyGroups { - name: MarkerValueDependencyGroup::Group(group), - operator: ContainerOperator::In, - } => ( - Variable::DependencyGroups(CanonicalMarkerValueDependencyGroup::Group(group)), - Edges::from_bool(true), - ), - MarkerExpression::DependencyGroups { - name: MarkerValueDependencyGroup::Group(group), - operator: ContainerOperator::NotIn, - } => ( - Variable::DependencyGroups(CanonicalMarkerValueDependencyGroup::Group(group)), - Edges::from_bool(false), - ), - // Invalid `dependency_group` names are always `false`. - MarkerExpression::DependencyGroups { - name: MarkerValueDependencyGroup::Arbitrary(_), - .. - } => return NodeId::FALSE, }; self.create_node(var, children) @@ -1090,18 +1052,12 @@ pub(crate) enum Variable { /// We keep extras at the leaves of the tree, so when simplifying extras we can /// trivially remove the leaves without having to reconstruct the entire tree. Extra(CanonicalMarkerValueExtra), - /// A variable representing the existence or absence of a given extra, in the context of a - /// PEP 751 lockfile marker. + /// A variable representing whether a ` in ` or ` not in ` + /// expression, where the key is a list. /// - /// We keep extras at the leaves of the tree, so when simplifying extras we can + /// We keep extras and groups at the leaves of the tree, so when simplifying extras we can /// trivially remove the leaves without having to reconstruct the entire tree. - Extras(CanonicalMarkerValueExtra), - /// A variable representing the existence or absence of a given dependency group, in the context of a - /// PEP 751 lockfile marker. - /// - /// We keep groups at the leaves of the tree, so when simplifying groups we can - /// trivially remove the leaves without having to reconstruct the entire tree. - DependencyGroups(CanonicalMarkerValueDependencyGroup), + List(CanonicalMarkerListPair), } impl Variable { @@ -1279,7 +1235,10 @@ impl Edges { /// Returns an [`Edges`] where values in the given range are `true`. /// /// Only for use when the `key` is a `PythonVersion`. Normalizes to `PythonFullVersion`. - fn from_python_versions(versions: Vec, negated: bool) -> Result { + fn from_python_versions( + versions: Vec, + operator: ContainerOperator, + ) -> Result { let mut range: Ranges = versions .into_iter() .map(|version| { @@ -1290,7 +1249,7 @@ impl Edges { .flatten_ok() .collect::, NodeId>>()?; - if negated { + if operator == ContainerOperator::NotIn { range = range.complement(); } @@ -1300,7 +1259,7 @@ impl Edges { } /// Returns an [`Edges`] where values in the given range are `true`. - fn from_versions(versions: &[Version], negated: bool) -> Edges { + fn from_versions(versions: &[Version], operator: ContainerOperator) -> Edges { let mut range: Ranges = versions .iter() .map(|version| { @@ -1311,7 +1270,7 @@ impl Edges { }) .collect(); - if negated { + if operator == ContainerOperator::NotIn { range = range.complement(); } diff --git a/crates/uv-pep508/src/marker/lowering.rs b/crates/uv-pep508/src/marker/lowering.rs index dadfeac53..e52669840 100644 --- a/crates/uv-pep508/src/marker/lowering.rs +++ b/crates/uv-pep508/src/marker/lowering.rs @@ -2,7 +2,7 @@ use std::fmt::{Display, Formatter}; use uv_normalize::{ExtraName, GroupName}; -use crate::marker::tree::MarkerValueDependencyGroup; +use crate::marker::tree::MarkerValueList; use crate::{MarkerValueExtra, MarkerValueString, MarkerValueVersion}; /// Those environment markers with a PEP 440 version as value such as `python_version` @@ -161,34 +161,35 @@ impl Display for CanonicalMarkerValueExtra { } } -/// The [`GroupName`] value used in `dependency_group` markers. +/// A key-value pair for ` in ` or ` not in `, where the key is a list. +/// +/// Used for PEP 751 markers. #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] -pub enum CanonicalMarkerValueDependencyGroup { +pub enum CanonicalMarkerListPair { + /// A valid [`ExtraName`]. + Extras(ExtraName), /// A valid [`GroupName`]. - Group(GroupName), + DependencyGroup(GroupName), + /// For leniency, preserve invalid values. + Arbitrary { key: MarkerValueList, value: String }, } -impl CanonicalMarkerValueDependencyGroup { - /// Returns the [`GroupName`] value. - pub fn group(&self) -> &GroupName { +impl CanonicalMarkerListPair { + /// The key (RHS) of the marker expression. + pub(crate) fn key(&self) -> MarkerValueList { match self { - Self::Group(group) => group, + Self::Extras(_) => MarkerValueList::Extras, + Self::DependencyGroup(_) => MarkerValueList::DependencyGroups, + Self::Arbitrary { key, .. } => *key, } } -} -impl From for MarkerValueDependencyGroup { - fn from(value: CanonicalMarkerValueDependencyGroup) -> Self { - match value { - CanonicalMarkerValueDependencyGroup::Group(group) => Self::Group(group), - } - } -} - -impl Display for CanonicalMarkerValueDependencyGroup { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + /// The value (LHS) of the marker expression. + pub(crate) fn value(&self) -> String { match self { - Self::Group(group) => group.fmt(f), + Self::Extras(extra) => extra.to_string(), + Self::DependencyGroup(group) => group.to_string(), + Self::Arbitrary { value, .. } => value.clone(), } } } diff --git a/crates/uv-pep508/src/marker/mod.rs b/crates/uv-pep508/src/marker/mod.rs index f5ac7f1da..55d21c69f 100644 --- a/crates/uv-pep508/src/marker/mod.rs +++ b/crates/uv-pep508/src/marker/mod.rs @@ -23,8 +23,8 @@ pub use lowering::{ pub use tree::{ ContainsMarkerTree, ExtraMarkerTree, ExtraOperator, InMarkerTree, MarkerExpression, MarkerOperator, MarkerTree, MarkerTreeContents, MarkerTreeDebugGraph, MarkerTreeKind, - MarkerValue, MarkerValueExtra, MarkerValueString, MarkerValueVersion, MarkerWarningKind, - StringMarkerTree, StringVersion, VersionMarkerTree, + MarkerValue, MarkerValueExtra, MarkerValueList, MarkerValueString, MarkerValueVersion, + MarkerWarningKind, StringMarkerTree, StringVersion, VersionMarkerTree, }; /// `serde` helpers for [`MarkerTree`]. diff --git a/crates/uv-pep508/src/marker/parse.rs b/crates/uv-pep508/src/marker/parse.rs index de8bfac72..8e4a39078 100644 --- a/crates/uv-pep508/src/marker/parse.rs +++ b/crates/uv-pep508/src/marker/parse.rs @@ -5,7 +5,8 @@ use uv_pep440::{Version, VersionPattern, VersionSpecifier}; use crate::cursor::Cursor; use crate::marker::MarkerValueExtra; -use crate::marker::tree::{ContainerOperator, MarkerValueContains, MarkerValueDependencyGroup}; +use crate::marker::lowering::CanonicalMarkerListPair; +use crate::marker::tree::{ContainerOperator, MarkerValueList}; use crate::{ ExtraOperator, MarkerExpression, MarkerOperator, MarkerTree, MarkerValue, MarkerValueString, MarkerValueVersion, MarkerWarningKind, Pep508Error, Pep508ErrorSource, Pep508Url, Reporter, @@ -169,6 +170,7 @@ pub(crate) fn parse_marker_key_op_value( reporter: &mut impl Reporter, ) -> Result, Pep508Error> { cursor.eat_whitespace(); + let start = cursor.pos(); let l_value = parse_marker_value(cursor, reporter)?; cursor.eat_whitespace(); // "not in" and "in" must be preceded by whitespace. We must already have matched a whitespace @@ -177,6 +179,7 @@ pub(crate) fn parse_marker_key_op_value( let operator = parse_marker_operator(cursor)?; cursor.eat_whitespace(); let r_value = parse_marker_value(cursor, reporter)?; + let len = cursor.pos() - start; // Convert a ` ` expression into its // typed equivalent. @@ -211,7 +214,7 @@ pub(crate) fn parse_marker_key_op_value( MarkerValue::Extra | MarkerValue::MarkerEnvVersion(_) | MarkerValue::MarkerEnvString(_) - | MarkerValue::MarkerEnvContains(_) => { + | MarkerValue::MarkerEnvList(_) => { reporter.report( MarkerWarningKind::MarkerMarkerComparison, "Comparing two markers with each other doesn't make any sense, @@ -239,12 +242,23 @@ pub(crate) fn parse_marker_key_op_value( value, }) } + // `extras in "test"` or `dependency_groups not in "dev"` are invalid. + MarkerValue::MarkerEnvList(key) => { + return Err(Pep508Error { + message: Pep508ErrorSource::String(format!( + "The marker {key} must be on the right hand side of the expression" + )), + start, + len, + input: cursor.to_string(), + }); + } // `extra == '...'` MarkerValue::Extra => { let value = match r_value { MarkerValue::MarkerEnvVersion(_) | MarkerValue::MarkerEnvString(_) - | MarkerValue::MarkerEnvContains(_) + | MarkerValue::MarkerEnvList(_) | MarkerValue::Extra => { reporter.report( MarkerWarningKind::ExtraInvalidComparison, @@ -274,16 +288,56 @@ pub(crate) fn parse_marker_key_op_value( operator: operator.invert(), value: l_string, }), + // `"test" in extras` or `"dev" in dependency_groups` + MarkerValue::MarkerEnvList(key) => { + let operator = + ContainerOperator::from_marker_operator(operator).ok_or_else(|| { + Pep508Error { + message: Pep508ErrorSource::String(format!( + "The operator {operator} is not supported with the marker {key}, only the `in` and `not in` operators are supported" + )), + start, + len, + input: cursor.to_string(), + } + })?; + let pair = match key { + // `'...' in extras` + MarkerValueList::Extras => match ExtraName::from_str(&l_string) { + Ok(name) => CanonicalMarkerListPair::Extras(name), + Err(err) => { + reporter.report( + MarkerWarningKind::ExtrasInvalidComparison, + format!("Expected extra name (found `{l_string}`): {err}"), + ); + CanonicalMarkerListPair::Arbitrary { + key, + value: l_string.to_string(), + } + } + }, + // `'...' in dependency_groups` + MarkerValueList::DependencyGroups => { + match GroupName::from_str(&l_string) { + Ok(name) => CanonicalMarkerListPair::DependencyGroup(name), + Err(err) => { + reporter.report( + MarkerWarningKind::ExtrasInvalidComparison, + format!("Expected dependency group name (found `{l_string}`): {err}"), + ); + CanonicalMarkerListPair::Arbitrary { + key, + value: l_string.to_string(), + } + } + } + } + }; + + Some(MarkerExpression::List { pair, operator }) + } // `'...' == extra` MarkerValue::Extra => parse_extra_expr(operator, &l_string, reporter), - // `'...' in extras` - MarkerValue::MarkerEnvContains(MarkerValueContains::Extras) => { - parse_extras_expr(operator, &l_string, reporter) - } - // `'...' in dependency_groups` - MarkerValue::MarkerEnvContains(MarkerValueContains::DependencyGroups) => { - parse_dependency_groups_expr(operator, &l_string, reporter) - } // `'...' == '...'`, doesn't make much sense MarkerValue::QuotedString(_) => { // Not even pypa/packaging 22.0 supports this @@ -300,16 +354,6 @@ pub(crate) fn parse_marker_key_op_value( } } } - MarkerValue::MarkerEnvContains(key) => { - reporter.report( - MarkerWarningKind::Pep440Error, - format!( - "The `{key}` marker must be used as '...' in {key}' or '... not in {key}', - found `{key} {operator} {r_value}`, will be ignored" - ), - ); - return Ok(None); - } }; Ok(expr) @@ -340,10 +384,7 @@ fn parse_version_in_expr( value: &str, reporter: &mut impl Reporter, ) -> Option { - if !matches!(operator, MarkerOperator::In | MarkerOperator::NotIn) { - return None; - } - let negated = matches!(operator, MarkerOperator::NotIn); + let operator = ContainerOperator::from_marker_operator(operator)?; let mut cursor = Cursor::new(value); let mut versions = Vec::new(); @@ -379,7 +420,7 @@ fn parse_version_in_expr( Some(MarkerExpression::VersionIn { key, versions, - negated, + operator, }) } @@ -519,68 +560,6 @@ fn parse_extra_expr( None } -/// Creates an instance of [`MarkerExpression::Extras`] with the given values, falling back to -/// [`MarkerExpression::Arbitrary`] on failure. -fn parse_extras_expr( - operator: MarkerOperator, - value: &str, - reporter: &mut impl Reporter, -) -> Option { - let name = match ExtraName::from_str(value) { - Ok(name) => MarkerValueExtra::Extra(name), - Err(err) => { - reporter.report( - MarkerWarningKind::ExtrasInvalidComparison, - format!("Expected extra name (found `{value}`): {err}"), - ); - MarkerValueExtra::Arbitrary(value.to_string()) - } - }; - - if let Some(operator) = ContainerOperator::from_marker_operator(operator) { - return Some(MarkerExpression::Extras { operator, name }); - } - - reporter.report( - MarkerWarningKind::ExtrasInvalidComparison, - "Comparing `extras` with any operator other than `in` or `not in` is wrong and will be ignored" - .to_string(), - ); - - None -} - -/// Creates an instance of [`MarkerExpression::DependencyGroups`] with the given values, falling -/// back to [`MarkerExpression::Arbitrary`] on failure. -fn parse_dependency_groups_expr( - operator: MarkerOperator, - value: &str, - reporter: &mut impl Reporter, -) -> Option { - let name = match GroupName::from_str(value) { - Ok(name) => MarkerValueDependencyGroup::Group(name), - Err(err) => { - reporter.report( - MarkerWarningKind::ExtrasInvalidComparison, - format!("Expected extra name (found `{value}`): {err}"), - ); - MarkerValueDependencyGroup::Arbitrary(value.to_string()) - } - }; - - if let Some(operator) = ContainerOperator::from_marker_operator(operator) { - return Some(MarkerExpression::DependencyGroups { operator, name }); - } - - reporter.report( - MarkerWarningKind::ExtrasInvalidComparison, - "Comparing `extras` with any operator other than `in` or `not in` is wrong and will be ignored" - .to_string(), - ); - - None -} - /// ```text /// marker_expr = marker_var:l marker_op:o marker_var:r -> (o, l, r) /// | wsp* '(' marker:m wsp* ')' -> m diff --git a/crates/uv-pep508/src/marker/simplify.rs b/crates/uv-pep508/src/marker/simplify.rs index 6897615c4..b1565835b 100644 --- a/crates/uv-pep508/src/marker/simplify.rs +++ b/crates/uv-pep508/src/marker/simplify.rs @@ -162,6 +162,22 @@ fn collect_dnf( path.pop(); } } + MarkerTreeKind::List(marker) => { + for (is_high, tree) in marker.children() { + let expr = MarkerExpression::List { + pair: marker.pair().clone(), + operator: if is_high { + ContainerOperator::In + } else { + ContainerOperator::NotIn + }, + }; + + path.push(expr); + collect_dnf(tree, dnf, path); + path.pop(); + } + } MarkerTreeKind::Extra(marker) => { for (value, tree) in marker.children() { let operator = if value { @@ -175,42 +191,6 @@ fn collect_dnf( operator, }; - path.push(expr); - collect_dnf(tree, dnf, path); - path.pop(); - } - } - MarkerTreeKind::Extras(marker) => { - for (value, tree) in marker.children() { - let operator = if value { - ContainerOperator::In - } else { - ContainerOperator::NotIn - }; - - let expr = MarkerExpression::Extras { - name: marker.name().clone().into(), - operator, - }; - - path.push(expr); - collect_dnf(tree, dnf, path); - path.pop(); - } - } - MarkerTreeKind::DependencyGroups(marker) => { - for (value, tree) in marker.children() { - let operator = if value { - ContainerOperator::In - } else { - ContainerOperator::NotIn - }; - - let expr = MarkerExpression::DependencyGroups { - name: marker.name().clone().into(), - operator, - }; - path.push(expr); collect_dnf(tree, dnf, path); path.pop(); @@ -433,18 +413,18 @@ fn is_negation(left: &MarkerExpression, right: &MarkerExpression) -> bool { MarkerExpression::VersionIn { key, versions, - negated, + operator, } => { let MarkerExpression::VersionIn { key: key2, versions: versions2, - negated: negated2, + operator: operator2, } = right else { return false; }; - key == key2 && versions == versions2 && negated != negated2 + key == key2 && versions == versions2 && operator != operator2 } MarkerExpression::String { key, @@ -477,27 +457,16 @@ fn is_negation(left: &MarkerExpression, right: &MarkerExpression) -> bool { name == name2 && operator.negate() == *operator2 } - MarkerExpression::Extras { name, operator } => { - let MarkerExpression::Extras { - name: name2, + MarkerExpression::List { pair, operator } => { + let MarkerExpression::List { + pair: pair2, operator: operator2, } = right else { return false; }; - name == name2 && *operator == operator2.negate() - } - MarkerExpression::DependencyGroups { name, operator } => { - let MarkerExpression::DependencyGroups { - name: name2, - operator: operator2, - } = right - else { - return false; - }; - - name == name2 && *operator == operator2.negate() + pair == pair2 && operator != operator2 } } } diff --git a/crates/uv-pep508/src/marker/tree.rs b/crates/uv-pep508/src/marker/tree.rs index 756c90ade..f874fd447 100644 --- a/crates/uv-pep508/src/marker/tree.rs +++ b/crates/uv-pep508/src/marker/tree.rs @@ -16,12 +16,12 @@ use super::algebra::{Edges, INTERNER, NodeId, Variable}; use super::simplify; use crate::cursor::Cursor; use crate::marker::lowering::{ - CanonicalMarkerValueDependencyGroup, CanonicalMarkerValueExtra, CanonicalMarkerValueString, - CanonicalMarkerValueVersion, + CanonicalMarkerListPair, CanonicalMarkerValueString, CanonicalMarkerValueVersion, }; use crate::marker::parse; use crate::{ - MarkerEnvironment, Pep508Error, Pep508ErrorSource, Pep508Url, Reporter, TracingReporter, + CanonicalMarkerValueExtra, MarkerEnvironment, Pep508Error, Pep508ErrorSource, Pep508Url, + Reporter, TracingReporter, }; /// Ways in which marker evaluation can fail @@ -126,16 +126,18 @@ impl Display for MarkerValueString { } } -/// Those markers with exclusively `in` and `not in` operators (PEP 751) -#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] -pub enum MarkerValueContains { +/// Those markers with exclusively `in` and `not in` operators. +/// +/// Contains PEP 751 lockfile markers. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub enum MarkerValueList { /// `extras`. This one is special because it's a list, and user-provided Extras, /// `dependency_groups`. This one is special because it's a list, and user-provided DependencyGroups, } -impl Display for MarkerValueContains { +impl Display for MarkerValueList { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { Self::Extras => f.write_str("extras"), @@ -153,10 +155,10 @@ pub enum MarkerValue { MarkerEnvVersion(MarkerValueVersion), /// Those environment markers with an arbitrary string as value such as `sys_platform` MarkerEnvString(MarkerValueString), + /// Those markers with exclusively `in` and `not in` operators + MarkerEnvList(MarkerValueList), /// `extra`. This one is special because it's a list, and user-provided Extra, - /// Those markers with exclusively `in` and `not in` operators (PEP 751) - MarkerEnvContains(MarkerValueContains), /// Not a constant, but a user given quoted string with a value inside such as '3.8' or "windows" QuotedString(ArcStr), } @@ -196,9 +198,9 @@ impl FromStr for MarkerValue { "python_version" => Self::MarkerEnvVersion(MarkerValueVersion::PythonVersion), "sys_platform" => Self::MarkerEnvString(MarkerValueString::SysPlatform), "sys.platform" => Self::MarkerEnvString(MarkerValueString::SysPlatformDeprecated), + "extras" => Self::MarkerEnvList(MarkerValueList::Extras), + "dependency_groups" => Self::MarkerEnvList(MarkerValueList::DependencyGroups), "extra" => Self::Extra, - "extras" => Self::MarkerEnvContains(MarkerValueContains::Extras), - "dependency_groups" => Self::MarkerEnvContains(MarkerValueContains::DependencyGroups), _ => return Err(format!("Invalid key: {s}")), }; Ok(value) @@ -210,8 +212,8 @@ impl Display for MarkerValue { match self { Self::MarkerEnvVersion(marker_value_version) => marker_value_version.fmt(f), Self::MarkerEnvString(marker_value_string) => marker_value_string.fmt(f), + Self::MarkerEnvList(marker_value_contains) => marker_value_contains.fmt(f), Self::Extra => f.write_str("extra"), - Self::MarkerEnvContains(marker_value_contains) => marker_value_contains.fmt(f), Self::QuotedString(value) => write!(f, "'{value}'"), } } @@ -499,24 +501,6 @@ impl Display for MarkerValueExtra { } } -/// The [`GroupName`] value used in `dependency_group` markers. -#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] -pub enum MarkerValueDependencyGroup { - /// A valid [`GroupName`]. - Group(GroupName), - /// An invalid name, preserved as an arbitrary string. - Arbitrary(String), -} - -impl Display for MarkerValueDependencyGroup { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - Self::Group(group) => group.fmt(f), - Self::Arbitrary(string) => string.fmt(f), - } - } -} - /// Represents one clause such as `python_version > "3.8"`. #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] #[allow(missing_docs)] @@ -540,7 +524,7 @@ pub enum MarkerExpression { VersionIn { key: MarkerValueVersion, versions: Vec, - negated: bool, + operator: ContainerOperator, }, /// An string marker comparison, e.g. `sys_platform == '...'`. /// @@ -550,21 +534,16 @@ pub enum MarkerExpression { operator: MarkerOperator, value: ArcStr, }, + /// `'...' in `, a PEP 751 expression. + List { + pair: CanonicalMarkerListPair, + operator: ContainerOperator, + }, /// `extra '...'` or `'...' extra`. Extra { name: MarkerValueExtra, operator: ExtraOperator, }, - /// `'...' in extras` - Extras { - name: MarkerValueExtra, - operator: ContainerOperator, - }, - /// `'...' in dependency_groups` - DependencyGroups { - name: MarkerValueDependencyGroup, - operator: ContainerOperator, - }, } /// The kind of a [`MarkerExpression`]. @@ -572,16 +551,14 @@ pub enum MarkerExpression { pub(crate) enum MarkerExpressionKind { /// A version expression, e.g. ` `. Version(MarkerValueVersion), - /// A version "in" expression, e.g. ` in `. + /// A version `in` expression, e.g. ` in `. VersionIn(MarkerValueVersion), /// A string marker comparison, e.g. `sys_platform == '...'`. String(MarkerValueString), + /// A list `in` or `not in` expression, e.g. `'...' in dependency_groups`. + List(MarkerValueList), /// An extra expression, e.g. `extra == '...'`. Extra, - /// An extras expression, e.g. `'...' in extras`. - Extras, - /// A dependency groups expression, e.g. `'...' in dependency_groups`. - DependencyGroups, } /// The operator for an extra expression, either '==' or '!='. @@ -624,7 +601,7 @@ impl Display for ExtraOperator { } /// The operator for a container expression, either 'in' or 'not in'. -#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] pub enum ContainerOperator { /// `in` In, @@ -643,14 +620,6 @@ impl ContainerOperator { _ => None, } } - - /// Negates this operator. - pub(crate) fn negate(&self) -> ContainerOperator { - match *self { - ContainerOperator::In => ContainerOperator::NotIn, - ContainerOperator::NotIn => ContainerOperator::In, - } - } } impl Display for ContainerOperator { @@ -700,9 +669,8 @@ impl MarkerExpression { MarkerExpression::Version { key, .. } => MarkerExpressionKind::Version(*key), MarkerExpression::VersionIn { key, .. } => MarkerExpressionKind::VersionIn(*key), MarkerExpression::String { key, .. } => MarkerExpressionKind::String(*key), + MarkerExpression::List { pair, .. } => MarkerExpressionKind::List(pair.key()), MarkerExpression::Extra { .. } => MarkerExpressionKind::Extra, - MarkerExpression::Extras { .. } => MarkerExpressionKind::Extras, - MarkerExpression::DependencyGroups { .. } => MarkerExpressionKind::DependencyGroups, } } } @@ -721,11 +689,10 @@ impl Display for MarkerExpression { MarkerExpression::VersionIn { key, versions, - negated, + operator, } => { - let op = if *negated { "not in" } else { "in" }; let versions = versions.iter().map(ToString::to_string).join(" "); - write!(f, "{key} {op} '{versions}'") + write!(f, "{key} {operator} '{versions}'") } MarkerExpression::String { key, @@ -741,15 +708,12 @@ impl Display for MarkerExpression { write!(f, "{key} {operator} '{value}'") } + MarkerExpression::List { pair, operator } => { + write!(f, "'{}' {} {}", pair.value(), operator, pair.key()) + } MarkerExpression::Extra { operator, name } => { write!(f, "extra {operator} '{name}'") } - MarkerExpression::Extras { operator, name } => { - write!(f, "'{name}' {operator} extras") - } - MarkerExpression::DependencyGroups { operator, name } => { - write!(f, "'{name}' {operator} dependency_groups") - } } } } @@ -777,24 +741,24 @@ impl<'a> ExtrasEnvironment<'a> { /// Returns the `extra` names in this environment. fn extra(&self) -> &[ExtraName] { match self { - ExtrasEnvironment::Extras(extra) => extra, - ExtrasEnvironment::Pep751(..) => &[], + Self::Extras(extra) => extra, + Self::Pep751(..) => &[], } } /// Returns the `extras` names in this environment, as in a PEP 751 lockfile. fn extras(&self) -> &[ExtraName] { match self { - ExtrasEnvironment::Extras(..) => &[], - ExtrasEnvironment::Pep751(extras, ..) => extras, + Self::Extras(..) => &[], + Self::Pep751(extras, ..) => extras, } } /// Returns the `dependency_group` group names in this environment, as in a PEP 751 lockfile. fn dependency_groups(&self) -> &[GroupName] { match self { - ExtrasEnvironment::Extras(..) => &[], - ExtrasEnvironment::Pep751(.., groups) => groups, + Self::Extras(..) => &[], + Self::Pep751(.., groups) => groups, } } } @@ -1006,6 +970,16 @@ impl MarkerTree { low: low.negate(self.0), }) } + Variable::List(key) => { + let Edges::Boolean { low, high } = node.children else { + unreachable!() + }; + MarkerTreeKind::List(ListMarkerTree { + pair: key, + high: high.negate(self.0), + low: low.negate(self.0), + }) + } Variable::Extra(name) => { let Edges::Boolean { low, high } = node.children else { unreachable!() @@ -1016,26 +990,6 @@ impl MarkerTree { low: low.negate(self.0), }) } - Variable::Extras(name) => { - let Edges::Boolean { low, high } = node.children else { - unreachable!() - }; - MarkerTreeKind::Extras(ExtrasMarkerTree { - name, - high: high.negate(self.0), - low: low.negate(self.0), - }) - } - Variable::DependencyGroups(name) => { - let Edges::Boolean { low, high } = node.children else { - unreachable!() - }; - MarkerTreeKind::DependencyGroups(DependencyGroupsMarkerTree { - name, - high: high.negate(self.0), - low: low.negate(self.0), - }) - } } } @@ -1160,14 +1114,18 @@ impl MarkerTree { .edge(extras.extra().contains(marker.name().extra())) .evaluate_reporter_impl(env, extras, reporter); } - MarkerTreeKind::Extras(marker) => { + MarkerTreeKind::List(marker) => { + let edge = match marker.pair() { + CanonicalMarkerListPair::Extras(extra) => extras.extras().contains(extra), + CanonicalMarkerListPair::DependencyGroup(dependency_group) => { + extras.dependency_groups().contains(dependency_group) + } + // Invalid marker expression + CanonicalMarkerListPair::Arbitrary { .. } => return false, + }; + return marker - .edge(extras.extras().contains(marker.name().extra())) - .evaluate_reporter_impl(env, extras, reporter); - } - MarkerTreeKind::DependencyGroups(marker) => { - return marker - .edge(extras.dependency_groups().contains(marker.name().group())) + .edge(edge) .evaluate_reporter_impl(env, extras, reporter); } } @@ -1194,15 +1152,12 @@ impl MarkerTree { MarkerTreeKind::Contains(marker) => marker .children() .any(|(_, tree)| tree.evaluate_extras(extras)), + MarkerTreeKind::List(marker) => marker + .children() + .any(|(_, tree)| tree.evaluate_extras(extras)), MarkerTreeKind::Extra(marker) => marker .edge(extras.contains(marker.name().extra())) .evaluate_extras(extras), - MarkerTreeKind::Extras(marker) => marker - .children() - .any(|(_, tree)| tree.evaluate_extras(extras)), - MarkerTreeKind::DependencyGroups(marker) => marker - .children() - .any(|(_, tree)| tree.evaluate_extras(extras)), } } @@ -1430,6 +1385,11 @@ impl MarkerTree { imp(tree, f); } } + MarkerTreeKind::List(kind) => { + for (_, tree) in kind.children() { + imp(tree, f); + } + } MarkerTreeKind::Extra(kind) => { if kind.low.is_false() { f(MarkerOperator::Equal, kind.name().extra()); @@ -1440,16 +1400,6 @@ impl MarkerTree { imp(tree, f); } } - MarkerTreeKind::Extras(kind) => { - for (_, tree) in kind.children() { - imp(tree, f); - } - } - MarkerTreeKind::DependencyGroups(kind) => { - for (_, tree) in kind.children() { - imp(tree, f); - } - } } } imp(self, &mut f); @@ -1557,6 +1507,21 @@ impl MarkerTree { write!(f, "{} not in {} -> ", kind.value(), kind.key())?; kind.edge(false).fmt_graph(f, level + 1)?; } + MarkerTreeKind::List(kind) => { + writeln!(f)?; + for _ in 0..level { + write!(f, " ")?; + } + write!(f, "{} in {} -> ", kind.value(), kind.key())?; + kind.edge(true).fmt_graph(f, level + 1)?; + + writeln!(f)?; + for _ in 0..level { + write!(f, " ")?; + } + write!(f, "{} not in {} -> ", kind.value(), kind.key())?; + kind.edge(false).fmt_graph(f, level + 1)?; + } MarkerTreeKind::Extra(kind) => { writeln!(f)?; for _ in 0..level { @@ -1572,36 +1537,6 @@ impl MarkerTree { write!(f, "extra != {} -> ", kind.name())?; kind.edge(false).fmt_graph(f, level + 1)?; } - MarkerTreeKind::Extras(kind) => { - writeln!(f)?; - for _ in 0..level { - write!(f, " ")?; - } - write!(f, "{} in extras -> ", kind.name())?; - kind.edge(true).fmt_graph(f, level + 1)?; - - writeln!(f)?; - for _ in 0..level { - write!(f, " ")?; - } - write!(f, "{} not in extras -> ", kind.name())?; - kind.edge(false).fmt_graph(f, level + 1)?; - } - MarkerTreeKind::DependencyGroups(kind) => { - writeln!(f)?; - for _ in 0..level { - write!(f, " ")?; - } - write!(f, "{} in dependency_groups -> ", kind.name())?; - kind.edge(true).fmt_graph(f, level + 1)?; - - writeln!(f)?; - for _ in 0..level { - write!(f, " ")?; - } - write!(f, "{} not in dependency_groups -> ", kind.name())?; - kind.edge(false).fmt_graph(f, level + 1)?; - } } Ok(()) @@ -1671,12 +1606,10 @@ pub enum MarkerTreeKind<'a> { In(InMarkerTree<'a>), /// A string expression with the `contains` operator. Contains(ContainsMarkerTree<'a>), - /// A string expression (e.g., `extra == 'dev'`). + /// A `in` or `not in` expression. + List(ListMarkerTree<'a>), + /// An extra expression (e.g., `extra == 'dev'`). Extra(ExtraMarkerTree<'a>), - /// A string expression (e.g., `'dev' in extras`). - Extras(ExtrasMarkerTree<'a>), - /// A string expression (e.g., `'dev' in dependency_groups`). - DependencyGroups(DependencyGroupsMarkerTree<'a>), } /// A version marker node, such as `python_version < '3.7'`. @@ -1851,6 +1784,59 @@ impl Ord for ContainsMarkerTree<'_> { } } +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct ListMarkerTree<'a> { + // No separate canonical type, the type is already canonical. + pair: &'a CanonicalMarkerListPair, + high: NodeId, + low: NodeId, +} + +impl ListMarkerTree<'_> { + /// The key-value pair for this expression + pub fn pair(&self) -> &CanonicalMarkerListPair { + self.pair + } + + /// The key (RHS) for this expression. + pub fn key(&self) -> MarkerValueList { + self.pair.key() + } + + /// The value (LHS) for this expression. + pub fn value(&self) -> String { + self.pair.value() + } + + /// The edges of this node, corresponding to the boolean evaluation of the expression. + pub fn children(&self) -> impl Iterator { + [(true, MarkerTree(self.high)), (false, MarkerTree(self.low))].into_iter() + } + + /// Returns the subtree associated with the given edge value. + pub fn edge(&self, value: bool) -> MarkerTree { + if value { + MarkerTree(self.high) + } else { + MarkerTree(self.low) + } + } +} + +impl PartialOrd for ListMarkerTree<'_> { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for ListMarkerTree<'_> { + fn cmp(&self, other: &Self) -> Ordering { + self.pair() + .cmp(other.pair()) + .then_with(|| self.children().cmp(other.children())) + } +} + /// A node representing the existence or absence of a given extra, such as `extra == 'bar'`. #[derive(PartialEq, Eq, Clone, Debug)] pub struct ExtraMarkerTree<'a> { @@ -1894,93 +1880,6 @@ impl Ord for ExtraMarkerTree<'_> { } } -/// A node representing the existence or absence of a given extra, such as `'bar' in extras`. -#[derive(PartialEq, Eq, Clone, Debug)] -pub struct ExtrasMarkerTree<'a> { - name: &'a CanonicalMarkerValueExtra, - high: NodeId, - low: NodeId, -} - -impl ExtrasMarkerTree<'_> { - /// Returns the name of the extra in this expression. - pub fn name(&self) -> &CanonicalMarkerValueExtra { - self.name - } - - /// The edges of this node, corresponding to the boolean evaluation of the expression. - pub fn children(&self) -> impl Iterator { - [(true, MarkerTree(self.high)), (false, MarkerTree(self.low))].into_iter() - } - - /// Returns the subtree associated with the given edge value. - pub fn edge(&self, value: bool) -> MarkerTree { - if value { - MarkerTree(self.high) - } else { - MarkerTree(self.low) - } - } -} - -impl PartialOrd for ExtrasMarkerTree<'_> { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for ExtrasMarkerTree<'_> { - fn cmp(&self, other: &Self) -> Ordering { - self.name() - .cmp(other.name()) - .then_with(|| self.children().cmp(other.children())) - } -} - -/// A node representing the existence or absence of a given dependency group, such as -/// `'bar' in dependency_groups`. -#[derive(PartialEq, Eq, Clone, Debug)] -pub struct DependencyGroupsMarkerTree<'a> { - name: &'a CanonicalMarkerValueDependencyGroup, - high: NodeId, - low: NodeId, -} - -impl DependencyGroupsMarkerTree<'_> { - /// Returns the name of the group in this expression. - pub fn name(&self) -> &CanonicalMarkerValueDependencyGroup { - self.name - } - - /// The edges of this node, corresponding to the boolean evaluation of the expression. - pub fn children(&self) -> impl Iterator { - [(true, MarkerTree(self.high)), (false, MarkerTree(self.low))].into_iter() - } - - /// Returns the subtree associated with the given edge value. - pub fn edge(&self, value: bool) -> MarkerTree { - if value { - MarkerTree(self.high) - } else { - MarkerTree(self.low) - } - } -} - -impl PartialOrd for DependencyGroupsMarkerTree<'_> { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for DependencyGroupsMarkerTree<'_> { - fn cmp(&self, other: &Self) -> Ordering { - self.name() - .cmp(other.name()) - .then_with(|| self.children().cmp(other.children())) - } -} - /// A marker tree that contains at least one expression. /// /// See [`MarkerTree::contents`] for details. @@ -2090,7 +1989,7 @@ mod test { implementation_name: "", implementation_version: "3.7", os_name: "linux", - platform_machine: "", + platform_machine: "x86_64", platform_python_implementation: "", platform_release: "", platform_system: "", diff --git a/crates/uv-pep508/src/verbatim_url.rs b/crates/uv-pep508/src/verbatim_url.rs index 2911de938..9f0e9a5ee 100644 --- a/crates/uv-pep508/src/verbatim_url.rs +++ b/crates/uv-pep508/src/verbatim_url.rs @@ -62,6 +62,7 @@ impl VerbatimUrl { /// /// If no root directory is provided, relative paths are resolved against the current working /// directory. + #[cfg(feature = "non-pep508-extensions")] // PEP 508 arguably only allows absolute file URLs. pub fn from_url_or_path( input: &str, root_dir: Option<&Path>, diff --git a/crates/uv-resolver/src/marker.rs b/crates/uv-resolver/src/marker.rs index 5a2203f9b..02ea1d6df 100644 --- a/crates/uv-resolver/src/marker.rs +++ b/crates/uv-resolver/src/marker.rs @@ -54,12 +54,7 @@ pub(crate) fn requires_python(tree: MarkerTree) -> Option { collect_python_markers(tree, markers, range); } } - MarkerTreeKind::Extras(marker) => { - for (_, tree) in marker.children() { - collect_python_markers(tree, markers, range); - } - } - MarkerTreeKind::DependencyGroups(marker) => { + MarkerTreeKind::List(marker) => { for (_, tree) in marker.children() { collect_python_markers(tree, markers, range); } diff --git a/crates/uv-resolver/src/resolution/output.rs b/crates/uv-resolver/src/resolution/output.rs index 2afbf2c6b..8df52f4f0 100644 --- a/crates/uv-resolver/src/resolution/output.rs +++ b/crates/uv-resolver/src/resolution/output.rs @@ -698,12 +698,7 @@ impl ResolverOutput { add_marker_params_from_tree(tree, set); } } - MarkerTreeKind::Extras(marker) => { - for (_, tree) in marker.children() { - add_marker_params_from_tree(tree, set); - } - } - MarkerTreeKind::DependencyGroups(marker) => { + MarkerTreeKind::List(marker) => { for (_, tree) in marker.children() { add_marker_params_from_tree(tree, set); } From 2c8e394f03030542992d0a2475f2a6a063420488 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 21 Jul 2025 12:25:50 -0400 Subject: [PATCH 304/349] Create (e.g.) `python3.13t` executables in `uv venv` (#14764) ## Summary CPython's `venv` module creates these, so we should too. On non-Windows, we add `python3.13t`. On Windows, we add `python3.13t.exe` and `pythonw3.13t.exe` (see: https://github.com/python/cpython/blob/65d2c51c10425dcfacc0a13810d58c41240d7ff9/Lib/venv/__init__.py#L362). Closes https://github.com/astral-sh/uv/issues/14760. --- crates/uv-virtualenv/src/virtualenv.rs | 58 +++++++++++++++++++++++- crates/uv/tests/it/python_install.rs | 63 +++++++++++++++++++++++++- 2 files changed, 118 insertions(+), 3 deletions(-) diff --git a/crates/uv-virtualenv/src/virtualenv.rs b/crates/uv-virtualenv/src/virtualenv.rs index 5d3ab4a88..fb22a0724 100644 --- a/crates/uv-virtualenv/src/virtualenv.rs +++ b/crates/uv-virtualenv/src/virtualenv.rs @@ -242,6 +242,16 @@ pub(crate) fn create( interpreter.python_minor(), )), )?; + if interpreter.gil_disabled() { + uv_fs::replace_symlink( + "python", + scripts.join(format!( + "python{}.{}t", + interpreter.python_major(), + interpreter.python_minor(), + )), + )?; + } if interpreter.markers().implementation_name() == "pypy" { uv_fs::replace_symlink( @@ -267,6 +277,14 @@ pub(crate) fn create( let targetw = scripts.join(WindowsExecutable::Pythonw.exe(interpreter)); create_link_to_executable(targetw.as_path(), &executable_target) .map_err(Error::Python)?; + if interpreter.gil_disabled() { + let targett = scripts.join(WindowsExecutable::PythonMajorMinort.exe(interpreter)); + create_link_to_executable(targett.as_path(), &executable_target) + .map_err(Error::Python)?; + let targetwt = scripts.join(WindowsExecutable::PythonwMajorMinort.exe(interpreter)); + create_link_to_executable(targetwt.as_path(), &executable_target) + .map_err(Error::Python)?; + } } else { // Always copy `python.exe`. copy_launcher_windows( @@ -363,6 +381,24 @@ pub(crate) fn create( &scripts, python_home, )?; + + // If the GIL is disabled, copy `venvlaunchert.exe` and `venvwlaunchert.exe`. + if interpreter.gil_disabled() { + copy_launcher_windows( + WindowsExecutable::PythonMajorMinort, + interpreter, + &base_python, + &scripts, + python_home, + )?; + copy_launcher_windows( + WindowsExecutable::PythonwMajorMinort, + interpreter, + &base_python, + &scripts, + python_home, + )?; + } } } } @@ -580,8 +616,12 @@ enum WindowsExecutable { PythonMajor, /// The `python3..exe` executable (or `venvlauncher.exe` launcher shim). PythonMajorMinor, + /// The `python3.t.exe` executable (or `venvlaunchert.exe` launcher shim). + PythonMajorMinort, /// The `pythonw.exe` executable (or `venvwlauncher.exe` launcher shim). Pythonw, + /// The `pythonw3.t.exe` executable (or `venvwlaunchert.exe` launcher shim). + PythonwMajorMinort, /// The `pypy.exe` executable. PyPy, /// The `pypy3.exe` executable. @@ -592,7 +632,7 @@ enum WindowsExecutable { PyPyw, /// The `pypy3.w.exe` executable. PyPyMajorMinorw, - // The `graalpy.exe` executable + /// The `graalpy.exe` executable. GraalPy, } @@ -611,7 +651,21 @@ impl WindowsExecutable { interpreter.python_minor() ) } + WindowsExecutable::PythonMajorMinort => { + format!( + "python{}.{}t.exe", + interpreter.python_major(), + interpreter.python_minor() + ) + } WindowsExecutable::Pythonw => String::from("pythonw.exe"), + WindowsExecutable::PythonwMajorMinort => { + format!( + "pythonw{}.{}t.exe", + interpreter.python_major(), + interpreter.python_minor() + ) + } WindowsExecutable::PyPy => String::from("pypy.exe"), WindowsExecutable::PyPyMajor => { format!("pypy{}.exe", interpreter.python_major()) @@ -646,6 +700,8 @@ impl WindowsExecutable { Self::Python | Self::PythonMajor | Self::PythonMajorMinor => "venvlauncher.exe", Self::Pythonw if interpreter.gil_disabled() => "venvwlaunchert.exe", Self::Pythonw => "venvwlauncher.exe", + Self::PythonMajorMinort => "venvlaunchert.exe", + Self::PythonwMajorMinort => "venvwlaunchert.exe", // From 3.13 on these should replace the `python.exe` and `pythonw.exe` shims. // These are not relevant as of now for PyPy as it doesn't yet support Python 3.13. Self::PyPy | Self::PyPyMajor | Self::PyPyMajorMinor => "venvlauncher.exe", diff --git a/crates/uv/tests/it/python_install.rs b/crates/uv/tests/it/python_install.rs index 51e394aad..19b3a5c7f 100644 --- a/crates/uv/tests/it/python_install.rs +++ b/crates/uv/tests/it/python_install.rs @@ -1087,6 +1087,65 @@ fn python_install_freethreaded() { ----- stderr ----- "###); + // Create a virtual environment with the freethreaded Python + uv_snapshot!(context.filters(), context.venv().arg("--python").arg("3.13t"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.13.5 + Creating virtual environment at: .venv + Activate with: source .venv/[BIN]/activate + "); + + // `python`, `python3`, `python3.13`, and `python3.13t` should all be present + let scripts = context + .venv + .join(if cfg!(windows) { "Scripts" } else { "bin" }); + assert!( + scripts + .join(format!("python{}", std::env::consts::EXE_SUFFIX)) + .exists() + ); + + #[cfg(windows)] + assert!( + scripts + .join(format!("pythonw{}", std::env::consts::EXE_SUFFIX)) + .exists() + ); + + #[cfg(unix)] + assert!( + scripts + .join(format!("python3{}", std::env::consts::EXE_SUFFIX)) + .exists() + ); + + #[cfg(unix)] + assert!( + scripts + .join(format!("python3.13{}", std::env::consts::EXE_SUFFIX)) + .exists() + ); + + assert!( + scripts + .join(format!("python3.13t{}", std::env::consts::EXE_SUFFIX)) + .exists() + ); + + #[cfg(windows)] + assert!( + scripts + .join(format!("pythonw3.13t{}", std::env::consts::EXE_SUFFIX)) + .exists() + ); + + // Remove the virtual environment + fs_err::remove_dir_all(&context.venv).unwrap(); + // Should be distinct from 3.13 uv_snapshot!(context.filters(), context.python_install().arg("3.13"), @r" success: true @@ -1099,14 +1158,14 @@ fn python_install_freethreaded() { "); // Should not work with older Python versions - uv_snapshot!(context.filters(), context.python_install().arg("3.12t"), @r###" + uv_snapshot!(context.filters(), context.python_install().arg("3.12t"), @r" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- error: No download found for request: cpython-3.12t-[PLATFORM] - "###); + "); uv_snapshot!(context.filters(), context.python_uninstall().arg("--all"), @r" success: true From 7a56950bab323ab8f77f211e1d2522f2e3ead2ba Mon Sep 17 00:00:00 2001 From: Ali Chaudry Date: Mon, 21 Jul 2025 15:48:47 -0400 Subject: [PATCH 305/349] Update `setup-uv` docs for Github Actions integration guide (re-order python and uv setup) (#14741) I updated the Github Actions integration guide to run Github's `setup-python` before Astral's `setup-uv`, as `setup-uv`'s `activate-environment: true` doesn't work with the original ordering. There is a discussion about this behavior in the `setup-uv` repo [here](https://github.com/astral-sh/setup-uv/issues/479). ## Summary Update the documentation for the Github Actions integration. Caveat: I'm unsure if there are any other reasons where the original ordering (that is,`setup-uv` before `setup-python`) might be preferred. ## Test Plan Tested in a private Github Actions push, as documented in the aforementioned discussion on `setup-uv`'s repo. Confirmed that removing `source .venv/bin/activate` and replacing it with `activate-environment: true` now works in this ordering (but didn't work with the original ordering where `uv` installs before Github's `python`). --- docs/guides/integration/github.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/guides/integration/github.md b/docs/guides/integration/github.md index 15d26b280..220db8fc8 100644 --- a/docs/guides/integration/github.md +++ b/docs/guides/integration/github.md @@ -92,13 +92,13 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Install uv - uses: astral-sh/setup-uv@v6 - - name: "Set up Python" uses: actions/setup-python@v5 with: python-version-file: ".python-version" + + - name: Install uv + uses: astral-sh/setup-uv@v6 ``` Or, specify the `pyproject.toml` file to ignore the pin and use the latest version compatible with @@ -115,13 +115,13 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Install uv - uses: astral-sh/setup-uv@v6 - - name: "Set up Python" uses: actions/setup-python@v5 with: python-version-file: "pyproject.toml" + + - name: Install uv + uses: astral-sh/setup-uv@v6 ``` ## Multiple Python versions From a3ea1b69f28dc4a0ab1fee400e7509d1fa3bf136 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 21 Jul 2025 16:55:33 -0400 Subject: [PATCH 306/349] Add support for `HF_TOKEN` (#14797) ## Summary If `HF_TOKEN` is set, we'll automatically wire it up to authenticate requests when hitting private `huggingface.co` URLs in `uv run`. ## Test Plan An unauthenticated request: ``` > cargo run -- run https://huggingface.co/datasets/cmarsh/test/resolve/main/main.py File "/var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/mainYadr5M.py", line 1 Invalid username or password. ^^^^^^^^ SyntaxError: invalid syntax ``` An authenticated request: ``` > HF_TOKEN=hf_... cargo run run https://huggingface.co/datasets/cmarsh/test/resolve/main/main.py Hello from main.py! ``` --- crates/uv-auth/src/lib.rs | 1 + crates/uv-auth/src/middleware.rs | 13 +++++++-- crates/uv-auth/src/providers.rs | 49 ++++++++++++++++++++++++++++++++ crates/uv-static/src/env_vars.rs | 7 +++++ docs/concepts/authentication.md | 15 ++++++++++ docs/reference/environment.md | 9 ++++++ 6 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 crates/uv-auth/src/providers.rs diff --git a/crates/uv-auth/src/lib.rs b/crates/uv-auth/src/lib.rs index 90a957630..8e8a0e057 100644 --- a/crates/uv-auth/src/lib.rs +++ b/crates/uv-auth/src/lib.rs @@ -15,6 +15,7 @@ mod credentials; mod index; mod keyring; mod middleware; +mod providers; mod realm; // TODO(zanieb): Consider passing a cache explicitly throughout diff --git a/crates/uv-auth/src/middleware.rs b/crates/uv-auth/src/middleware.rs index 1842effb3..605675b61 100644 --- a/crates/uv-auth/src/middleware.rs +++ b/crates/uv-auth/src/middleware.rs @@ -7,6 +7,7 @@ use reqwest::{Request, Response}; use reqwest_middleware::{Error, Middleware, Next}; use tracing::{debug, trace, warn}; +use crate::providers::HuggingFaceProvider; use crate::{ CREDENTIALS_CACHE, CredentialsCache, KeyringProvider, cache::FetchUrl, @@ -457,9 +458,8 @@ impl AuthMiddleware { Some(credentials) }; - return self - .complete_request(credentials, request, extensions, next, auth_policy) - .await; + self.complete_request(credentials, request, extensions, next, auth_policy) + .await } /// Fetch credentials for a URL. @@ -503,6 +503,13 @@ impl AuthMiddleware { return credentials; } + // Support for known providers, like Hugging Face. + if let Some(credentials) = HuggingFaceProvider::credentials_for(url).map(Arc::new) { + debug!("Found Hugging Face credentials for {url}"); + self.cache().fetches.done(key, Some(credentials.clone())); + return Some(credentials); + } + // Netrc support based on: . let credentials = if let Some(credentials) = self.netrc.get().and_then(|netrc| { debug!("Checking netrc for credentials for {url}"); diff --git a/crates/uv-auth/src/providers.rs b/crates/uv-auth/src/providers.rs new file mode 100644 index 000000000..85a5a0ec7 --- /dev/null +++ b/crates/uv-auth/src/providers.rs @@ -0,0 +1,49 @@ +use std::sync::LazyLock; +use tracing::debug; +use url::Url; + +use uv_static::EnvVars; + +use crate::Credentials; +use crate::realm::Realm; + +/// The [`Realm`] for the Hugging Face platform. +static HUGGING_FACE_REALM: LazyLock = LazyLock::new(|| { + let url = Url::parse("https://huggingface.co").expect("Failed to parse Hugging Face URL"); + Realm::from(&url) +}); + +/// The authentication token for the Hugging Face platform, if set. +static HUGGING_FACE_TOKEN: LazyLock>> = LazyLock::new(|| { + // Extract the Hugging Face token from the environment variable, if it exists. + let hf_token = std::env::var(EnvVars::HF_TOKEN) + .ok() + .map(String::into_bytes) + .filter(|token| !token.is_empty())?; + + if std::env::var_os(EnvVars::UV_NO_HF_TOKEN).is_some() { + debug!("Ignoring Hugging Face token from environment due to `UV_NO_HF_TOKEN`"); + return None; + } + + debug!("Found Hugging Face token in environment"); + Some(hf_token) +}); + +/// A provider for authentication credentials for the Hugging Face platform. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct HuggingFaceProvider; + +impl HuggingFaceProvider { + /// Returns the credentials for the Hugging Face platform, if available. + pub(crate) fn credentials_for(url: &Url) -> Option { + if Realm::from(url) == *HUGGING_FACE_REALM { + if let Some(token) = HUGGING_FACE_TOKEN.as_ref() { + return Some(Credentials::Bearer { + token: token.clone(), + }); + } + } + None + } +} diff --git a/crates/uv-static/src/env_vars.rs b/crates/uv-static/src/env_vars.rs index f7fa6cb31..0e99ec549 100644 --- a/crates/uv-static/src/env_vars.rs +++ b/crates/uv-static/src/env_vars.rs @@ -765,4 +765,11 @@ impl EnvVars { /// Disable GitHub-specific requests that allow uv to skip `git fetch` in some circumstances. pub const UV_NO_GITHUB_FAST_PATH: &'static str = "UV_NO_GITHUB_FAST_PATH"; + + /// Authentication token for Hugging Face requests. When set, uv will use this token + /// when making requests to `https://huggingface.co/` and any subdomains. + pub const HF_TOKEN: &'static str = "HF_TOKEN"; + + /// Disable Hugging Face authentication, even if `HF_TOKEN` is set. + pub const UV_NO_HF_TOKEN: &'static str = "UV_NO_HF_TOKEN"; } diff --git a/docs/concepts/authentication.md b/docs/concepts/authentication.md index 10bf57c21..fe5314b85 100644 --- a/docs/concepts/authentication.md +++ b/docs/concepts/authentication.md @@ -151,3 +151,18 @@ insecure. Use `allow-insecure-host` with caution and only in trusted environments, as it can expose you to security risks due to the lack of certificate verification. + +## Hugging Face support + +uv supports automatic authentication for the Hugging Face Hub. Specifically, if the `HF_TOKEN` +environment variable is set, uv will propagate it to requests to `huggingface.co`. + +This is particularly useful for accessing private scripts in Hugging Face Datasets. For example, you +can run the following command to execute the script `main.py` script from a private dataset: + +```console +$ HF_TOKEN=hf_... uv run https://huggingface.co/datasets///resolve//main.py +``` + +You can disable automatic Hugging Face authentication by setting the `UV_NO_HF_TOKEN=1` environment +variable. diff --git a/docs/reference/environment.md b/docs/reference/environment.md index e848d4a41..a4d686192 100644 --- a/docs/reference/environment.md +++ b/docs/reference/environment.md @@ -252,6 +252,10 @@ Ignore `.env` files when executing `uv run` commands. Disable GitHub-specific requests that allow uv to skip `git fetch` in some circumstances. +### `UV_NO_HF_TOKEN` + +Disable Hugging Face authentication, even if `HF_TOKEN` is set. + ### `UV_NO_INSTALLER_METADATA` Skip writing `uv` installer metadata files (e.g., `INSTALLER`, `REQUESTED`, and `direct_url.json`) to site-packages `.dist-info` directories. @@ -528,6 +532,11 @@ See [force-color.org](https://force-color.org). Used for trusted publishing via `uv publish`. +### `HF_TOKEN` + +Authentication token for Hugging Face requests. When set, uv will use this token +when making requests to `https://huggingface.co/` and any subdomains. + ### `HOME` The standard `HOME` env var. From 036c9bef3f3c29c785e4e1c96edbed9f745e23c8 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 21 Jul 2025 17:07:35 -0400 Subject: [PATCH 307/349] Add a borrowed `Realm` type (#14798) ## Summary Allows zero-cost comparisons against URL references. --- crates/uv-auth/src/providers.rs | 4 +- crates/uv-auth/src/realm.rs | 74 ++++++++++++++++++++++++++++++++- 2 files changed, 74 insertions(+), 4 deletions(-) diff --git a/crates/uv-auth/src/providers.rs b/crates/uv-auth/src/providers.rs index 85a5a0ec7..2c531d3da 100644 --- a/crates/uv-auth/src/providers.rs +++ b/crates/uv-auth/src/providers.rs @@ -5,7 +5,7 @@ use url::Url; use uv_static::EnvVars; use crate::Credentials; -use crate::realm::Realm; +use crate::realm::{Realm, RealmRef}; /// The [`Realm`] for the Hugging Face platform. static HUGGING_FACE_REALM: LazyLock = LazyLock::new(|| { @@ -37,7 +37,7 @@ pub(crate) struct HuggingFaceProvider; impl HuggingFaceProvider { /// Returns the credentials for the Hugging Face platform, if available. pub(crate) fn credentials_for(url: &Url) -> Option { - if Realm::from(url) == *HUGGING_FACE_REALM { + if RealmRef::from(url) == *HUGGING_FACE_REALM { if let Some(token) = HUGGING_FACE_TOKEN.as_ref() { return Some(Credentials::Bearer { token: token.clone(), diff --git a/crates/uv-auth/src/realm.rs b/crates/uv-auth/src/realm.rs index cfedf299c..03b3c8fcf 100644 --- a/crates/uv-auth/src/realm.rs +++ b/crates/uv-auth/src/realm.rs @@ -1,5 +1,5 @@ +use std::hash::{Hash, Hasher}; use std::{fmt::Display, fmt::Formatter}; - use url::Url; use uv_small_str::SmallString; @@ -22,7 +22,7 @@ use uv_small_str::SmallString; // The port is only allowed to differ if it matches the "default port" for the scheme. // However, `url` (and therefore `reqwest`) sets the `port` to `None` if it matches the default port // so we do not need any special handling here. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone)] pub(crate) struct Realm { scheme: SmallString, host: Option, @@ -59,6 +59,76 @@ impl Display for Realm { } } +impl PartialEq for Realm { + fn eq(&self, other: &Self) -> bool { + RealmRef::from(self) == RealmRef::from(other) + } +} + +impl Eq for Realm {} + +impl Hash for Realm { + fn hash(&self, state: &mut H) { + RealmRef::from(self).hash(state); + } +} + +/// A reference to a [`Realm`] that can be used for zero-allocation comparisons. +#[derive(Debug, Copy, Clone)] +pub(crate) struct RealmRef<'a> { + scheme: &'a str, + host: Option<&'a str>, + port: Option, +} + +impl<'a> From<&'a Url> for RealmRef<'a> { + fn from(url: &'a Url) -> Self { + Self { + scheme: url.scheme(), + host: url.host_str(), + port: url.port(), + } + } +} + +impl PartialEq for RealmRef<'_> { + fn eq(&self, other: &Self) -> bool { + self.scheme == other.scheme && self.host == other.host && self.port == other.port + } +} + +impl Eq for RealmRef<'_> {} + +impl Hash for RealmRef<'_> { + fn hash(&self, state: &mut H) { + self.scheme.hash(state); + self.host.hash(state); + self.port.hash(state); + } +} + +impl<'a> PartialEq> for Realm { + fn eq(&self, rhs: &RealmRef<'a>) -> bool { + RealmRef::from(self) == *rhs + } +} + +impl PartialEq for RealmRef<'_> { + fn eq(&self, rhs: &Realm) -> bool { + *self == RealmRef::from(rhs) + } +} + +impl<'a> From<&'a Realm> for RealmRef<'a> { + fn from(realm: &'a Realm) -> Self { + Self { + scheme: &realm.scheme, + host: realm.host.as_deref(), + port: realm.port, + } + } +} + #[cfg(test)] mod tests { use url::{ParseError, Url}; From ecfa38608864f99cdb887edc97632fc3ed352fc3 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 21 Jul 2025 18:15:03 -0400 Subject: [PATCH 308/349] Error on unknown fields in `dependency-metadata` (#14801) ## Summary Closes https://github.com/astral-sh/uv/issues/14800. --- .../src/dependency_metadata.rs | 24 ++++++------- crates/uv/tests/it/lock.rs | 36 +++++++++++++++++++ crates/uv/tests/it/sync.rs | 6 ++-- uv.schema.json | 1 + 4 files changed, 52 insertions(+), 15 deletions(-) diff --git a/crates/uv-distribution-types/src/dependency_metadata.rs b/crates/uv-distribution-types/src/dependency_metadata.rs index ccda34795..1e978db3d 100644 --- a/crates/uv-distribution-types/src/dependency_metadata.rs +++ b/crates/uv-distribution-types/src/dependency_metadata.rs @@ -30,21 +30,20 @@ impl DependencyMetadata { if let Some(version) = version { // If a specific version was requested, search for an exact match, then a global match. - let metadata = versions + let metadata = if let Some(metadata) = versions .iter() - .find(|v| v.version.as_ref() == Some(version)) - .inspect(|_| { - debug!("Found dependency metadata entry for `{package}=={version}`"); - }) - .or_else(|| versions.iter().find(|v| v.version.is_none())) - .inspect(|_| { - debug!("Found global metadata entry for `{package}`"); - }); - let Some(metadata) = metadata else { + .find(|entry| entry.version.as_ref() == Some(version)) + { + debug!("Found dependency metadata entry for `{package}=={version}`"); + metadata + } else if let Some(metadata) = versions.iter().find(|entry| entry.version.is_none()) { + debug!("Found global metadata entry for `{package}`"); + metadata + } else { warn!("No dependency metadata entry found for `{package}=={version}`"); return None; }; - debug!("Found dependency metadata entry for `{package}=={version}`"); + Some(ResolutionMetadata { name: metadata.name.clone(), version: version.clone(), @@ -65,6 +64,7 @@ impl DependencyMetadata { return None; }; debug!("Found dependency metadata entry for `{package}` (assuming: `{version}`)"); + Some(ResolutionMetadata { name: metadata.name.clone(), version, @@ -86,7 +86,7 @@ impl DependencyMetadata { /// . #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -#[serde(rename_all = "kebab-case")] +#[serde(rename_all = "kebab-case", deny_unknown_fields)] pub struct StaticMetadata { // Mandatory fields pub name: PackageName, diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index 0962ff6d2..f4ccb7bf7 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -18597,6 +18597,42 @@ fn lock_dependency_metadata() -> Result<()> { Removed sniffio v1.3.1 "###); + // Update the static metadata. + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["anyio==3.7.0"] + + [[tool.uv.dependency-metadata]] + name = "anyio" + version = "3.7.0" + requires_dist = ["typing-extensions"] + "#, + )?; + + // The operation should warn. + uv_snapshot!(context.filters(), context.lock(), @r#" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: Failed to parse `pyproject.toml` during settings discovery: + TOML parse error at line 11, column 9 + | + 11 | requires_dist = ["typing-extensions"] + | ^^^^^^^^^^^^^ + unknown field `requires_dist`, expected one of `name`, `version`, `requires-dist`, `requires-python`, `provides-extras` + + Resolved 4 packages in [TIME] + Added idna v3.6 + Removed iniconfig v2.0.0 + Added sniffio v1.3.1 + "#); + Ok(()) } diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index 1639ecaae..4f2853e61 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -7560,7 +7560,7 @@ fn sync_derivation_chain() -> Result<()> { [[tool.uv.dependency-metadata]] name = "wsgiref" version = "0.1.2" - dependencies = [] + requires-dist = [] "#, )?; @@ -7623,7 +7623,7 @@ fn sync_derivation_chain_extra() -> Result<()> { [[tool.uv.dependency-metadata]] name = "wsgiref" version = "0.1.2" - dependencies = [] + requires-dist = [] "#, )?; @@ -7688,7 +7688,7 @@ fn sync_derivation_chain_group() -> Result<()> { [[tool.uv.dependency-metadata]] name = "wsgiref" version = "0.1.2" - dependencies = [] + requires-dist = [] "#, )?; diff --git a/uv.schema.json b/uv.schema.json index 22b30cd06..d8346aab1 100644 --- a/uv.schema.json +++ b/uv.schema.json @@ -2104,6 +2104,7 @@ ] } }, + "additionalProperties": false, "required": [ "name" ] From c1bf934721dd327d4cb1cb3b915cfff475e54bc5 Mon Sep 17 00:00:00 2001 From: Ping Shuijie Date: Tue, 22 Jul 2025 18:13:05 +0800 Subject: [PATCH 309/349] chore: fix some minor issues in comments (#14807) ## Summary fix some minor issues in comments ## Test Plan Signed-off-by: pingshuijie --- crates/uv/src/commands/project/environment.rs | 2 +- crates/uv/tests/it/pip_compile.rs | 4 ++-- crates/uv/tests/it/version.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/uv/src/commands/project/environment.rs b/crates/uv/src/commands/project/environment.rs index 4f9d936c5..b5bb3fd23 100644 --- a/crates/uv/src/commands/project/environment.rs +++ b/crates/uv/src/commands/project/environment.rs @@ -63,7 +63,7 @@ impl EphemeralEnvironment { /// environment's `site-packages` directory to Python's import search paths in addition to /// the ephemeral environment's `site-packages` directory. This works well at runtime, but /// is too dynamic for static analysis tools like ty to understand. As such, we - /// additionally write the `sys.prefix` of the parent environment to to the + /// additionally write the `sys.prefix` of the parent environment to the /// `extends-environment` key of the ephemeral environment's `pyvenv.cfg` file, making it /// easier for these tools to statically and reliably understand the relationship between /// the two environments. diff --git a/crates/uv/tests/it/pip_compile.rs b/crates/uv/tests/it/pip_compile.rs index 69da12fd6..3a4dc28c4 100644 --- a/crates/uv/tests/it/pip_compile.rs +++ b/crates/uv/tests/it/pip_compile.rs @@ -12186,7 +12186,7 @@ requires-python = ">3.8" fn prerelease_path_requirement() -> Result<()> { let context = TestContext::new("3.12"); - // Create an a package that requires a pre-release version of `flask`. + // Create a package that requires a pre-release version of `flask`. let pyproject_toml = context.temp_dir.child("pyproject.toml"); pyproject_toml.write_str( r#"[project] @@ -12240,7 +12240,7 @@ requires-python = ">3.8" fn prerelease_editable_requirement() -> Result<()> { let context = TestContext::new("3.12"); - // Create an a package that requires a pre-release version of `flask`.r + // Create a package that requires a pre-release version of `flask`.r let pyproject_toml = context.temp_dir.child("pyproject.toml"); pyproject_toml.write_str( r#"[project] diff --git a/crates/uv/tests/it/version.rs b/crates/uv/tests/it/version.rs index e5f6e1687..53cb0de06 100644 --- a/crates/uv/tests/it/version.rs +++ b/crates/uv/tests/it/version.rs @@ -1993,7 +1993,7 @@ fn version_set_workspace() -> Result<()> { ); }); - // Set the other child's version, refereshing the lock and sync + // Set the other child's version, refreshing the lock and sync let mut version_cmd = context.version(); version_cmd .arg("--package") From 8bffa693b41e4a8c8d47e7ee9a95c8e64103305d Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 22 Jul 2025 07:11:05 -0500 Subject: [PATCH 310/349] Copy entry points and Jupyter data directories into ephemeral environments (#14790) This is an alternative to https://github.com/astral-sh/uv/pull/14788 which has the benefit that it addresses https://github.com/astral-sh/uv/issues/13327 which would be an issue even if we reverted #14447. There are two changes here 1. We copy entry points into the ephemeral environment, and rewrite their shebangs (or trampoline target) to ensure the ephemeral environment is not bypassed. 2. We link `etc/jupyter` and `share/jupyter` data directories into the ephemeral environment, this is in order to ensure the above doesn't break Jupyter which unfortunately cannot find the `share` directory otherwise. I'd love not to do this, as it seems brittle and we don't have a motivating use-case beyond Jupyter. I've opened https://github.com/jupyterlab/jupyterlab/issues/17716 upstream for discussion, as there is a viable patch that could be made upstream to resolve the problem. I've limited the fix to Jupyter directories so we can remove it without breakage. Closes https://github.com/astral-sh/uv/issues/14729 Closes https://github.com/astral-sh/uv/issues/13327 Closes https://github.com/astral-sh/uv/issues/14749 --------- Co-authored-by: Charlie Marsh --- crates/uv-fs/src/lib.rs | 34 ++++ crates/uv-trampoline-builder/src/lib.rs | 61 ++++++ crates/uv/src/commands/project/environment.rs | 14 ++ crates/uv/src/commands/project/run.rs | 155 +++++++++++++++- crates/uv/tests/it/run.rs | 175 ++++++++++++++++++ 5 files changed, 437 insertions(+), 2 deletions(-) diff --git a/crates/uv-fs/src/lib.rs b/crates/uv-fs/src/lib.rs index dcc0f00b2..17f52dcf5 100644 --- a/crates/uv-fs/src/lib.rs +++ b/crates/uv-fs/src/lib.rs @@ -84,6 +84,8 @@ pub async fn read_to_string_transcode(path: impl AsRef) -> std::io::Result /// junction at the same path. /// /// Note that because junctions are used, the source must be a directory. +/// +/// Changes to this function should be reflected in [`create_symlink`]. #[cfg(windows)] pub fn replace_symlink(src: impl AsRef, dst: impl AsRef) -> std::io::Result<()> { // If the source is a file, we can't create a junction @@ -138,6 +140,38 @@ pub fn replace_symlink(src: impl AsRef, dst: impl AsRef) -> std::io: } } +/// Create a symlink at `dst` pointing to `src`. +/// +/// On Windows, this uses the `junction` crate to create a junction point. +/// +/// Note that because junctions are used, the source must be a directory. +/// +/// Changes to this function should be reflected in [`replace_symlink`]. +#[cfg(windows)] +pub fn create_symlink(src: impl AsRef, dst: impl AsRef) -> std::io::Result<()> { + // If the source is a file, we can't create a junction + if src.as_ref().is_file() { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + format!( + "Cannot create a junction for {}: is not a directory", + src.as_ref().display() + ), + )); + } + + junction::create( + dunce::simplified(src.as_ref()), + dunce::simplified(dst.as_ref()), + ) +} + +/// Create a symlink at `dst` pointing to `src`. +#[cfg(unix)] +pub fn create_symlink(src: impl AsRef, dst: impl AsRef) -> std::io::Result<()> { + fs_err::os::unix::fs::symlink(src.as_ref(), dst.as_ref()) +} + #[cfg(unix)] pub fn remove_symlink(path: impl AsRef) -> std::io::Result<()> { fs_err::remove_file(path.as_ref()) diff --git a/crates/uv-trampoline-builder/src/lib.rs b/crates/uv-trampoline-builder/src/lib.rs index 2e1cde872..1a25b9454 100644 --- a/crates/uv-trampoline-builder/src/lib.rs +++ b/crates/uv-trampoline-builder/src/lib.rs @@ -41,6 +41,7 @@ const MAGIC_NUMBER_SIZE: usize = 4; pub struct Launcher { pub kind: LauncherKind, pub python_path: PathBuf, + payload: Vec, } impl Launcher { @@ -109,11 +110,69 @@ impl Launcher { String::from_utf8(buffer).map_err(|err| Error::InvalidPath(err.utf8_error()))?, ); + #[allow(clippy::cast_possible_truncation)] + let file_size = { + let raw_length = file + .seek(io::SeekFrom::End(0)) + .map_err(|e| Error::InvalidLauncherSeek("size probe".into(), 0, e))?; + + if raw_length > usize::MAX as u64 { + return Err(Error::InvalidDataLength(raw_length)); + } + + // SAFETY: Above we guarantee the length is less than uszie + raw_length as usize + }; + + // Read the payload + file.seek(io::SeekFrom::Start(0)) + .map_err(|e| Error::InvalidLauncherSeek("rewind".into(), 0, e))?; + let payload_len = + file_size.saturating_sub(MAGIC_NUMBER_SIZE + PATH_LENGTH_SIZE + path_length); + let mut buffer = vec![0u8; payload_len]; + file.read_exact(&mut buffer) + .map_err(|err| Error::InvalidLauncherRead("payload".into(), err))?; + Ok(Some(Self { kind, + payload: buffer, python_path: path, })) } + + pub fn write_to_file(self, file: &mut File) -> Result<(), Error> { + let python_path = self.python_path.simplified_display().to_string(); + + if python_path.len() > MAX_PATH_LENGTH as usize { + return Err(Error::InvalidPathLength( + u32::try_from(python_path.len()).expect("path length already checked"), + )); + } + + let mut launcher: Vec = Vec::with_capacity( + self.payload.len() + python_path.len() + PATH_LENGTH_SIZE + MAGIC_NUMBER_SIZE, + ); + launcher.extend_from_slice(&self.payload); + launcher.extend_from_slice(python_path.as_bytes()); + launcher.extend_from_slice( + &u32::try_from(python_path.len()) + .expect("file path should be smaller than 4GB") + .to_le_bytes(), + ); + launcher.extend_from_slice(self.kind.magic_number()); + + file.write_all(&launcher)?; + Ok(()) + } + + #[must_use] + pub fn with_python_path(self, path: PathBuf) -> Self { + Self { + kind: self.kind, + payload: self.payload, + python_path: path, + } + } } /// The kind of trampoline launcher to create. @@ -177,6 +236,8 @@ pub enum Error { Io(#[from] io::Error), #[error("Only paths with a length up to 32KB are supported but found a length of {0} bytes")] InvalidPathLength(u32), + #[error("Only data with a length up to usize is supported but found a length of {0} bytes")] + InvalidDataLength(u64), #[error("Failed to parse executable path")] InvalidPath(#[source] Utf8Error), #[error("Failed to seek to {0} at offset {1}")] diff --git a/crates/uv/src/commands/project/environment.rs b/crates/uv/src/commands/project/environment.rs index b5bb3fd23..af3b3b351 100644 --- a/crates/uv/src/commands/project/environment.rs +++ b/crates/uv/src/commands/project/environment.rs @@ -78,6 +78,20 @@ impl EphemeralEnvironment { )?; Ok(()) } + + /// Returns the path to the environment's scripts directory. + pub(crate) fn scripts(&self) -> &Path { + self.0.scripts() + } + + /// Returns the path to the environment's Python executable. + pub(crate) fn sys_executable(&self) -> &Path { + self.0.interpreter().sys_executable() + } + + pub(crate) fn sys_prefix(&self) -> &Path { + self.0.interpreter().sys_prefix() + } } /// A [`PythonEnvironment`] stored in the cache. diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index ba8935013..44d0dc474 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -9,8 +9,9 @@ use anyhow::{Context, anyhow, bail}; use futures::StreamExt; use itertools::Itertools; use owo_colors::OwoColorize; +use thiserror::Error; use tokio::process::Command; -use tracing::{debug, warn}; +use tracing::{debug, trace, warn}; use url::Url; use uv_cache::Cache; @@ -22,7 +23,7 @@ use uv_configuration::{ }; use uv_distribution_types::Requirement; use uv_fs::which::is_executable; -use uv_fs::{PythonExt, Simplified}; +use uv_fs::{PythonExt, Simplified, create_symlink}; use uv_installer::{SatisfiesResult, SitePackages}; use uv_normalize::{DefaultExtras, DefaultGroups, PackageName}; use uv_python::{ @@ -1071,6 +1072,67 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl requirements_site_packages.escape_for_python(), ))?; + // N.B. The order here matters — earlier interpreters take precedence over the + // later ones. + for interpreter in [requirements_env.interpreter(), &base_interpreter] { + // Copy each entrypoint from the base environments to the ephemeral environment, + // updating the Python executable target to ensure they run in the ephemeral + // environment. + for entry in fs_err::read_dir(interpreter.scripts())? { + let entry = entry?; + if !entry.file_type()?.is_file() { + continue; + } + match copy_entrypoint( + &entry.path(), + &ephemeral_env.scripts().join(entry.file_name()), + interpreter.sys_executable(), + ephemeral_env.sys_executable(), + ) { + Ok(()) => {} + // If the entrypoint already exists, skip it. + Err(CopyEntrypointError::Io(err)) + if err.kind() == std::io::ErrorKind::AlreadyExists => + { + trace!( + "Skipping copy of entrypoint `{}`: already exists", + &entry.path().display() + ); + } + Err(err) => return Err(err.into()), + } + } + + // Link data directories from the base environment to the ephemeral environment. + // + // This is critical for Jupyter Lab, which cannot operate without the files it + // writes to `/share/jupyter`. + // + // See https://github.com/jupyterlab/jupyterlab/issues/17716 + for dir in &["etc/jupyter", "share/jupyter"] { + let source = interpreter.sys_prefix().join(dir); + if !matches!(source.try_exists(), Ok(true)) { + continue; + } + if !source.is_dir() { + continue; + } + let target = ephemeral_env.sys_prefix().join(dir); + if let Some(parent) = target.parent() { + fs_err::create_dir_all(parent)?; + } + match create_symlink(&source, &target) { + Ok(()) => trace!( + "Created link for {} -> {}", + target.user_display(), + source.user_display() + ), + Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => {} + Err(err) => return Err(err.into()), + } + } + } + // Write the `sys.prefix` of the parent environment to the `extends-environment` key of the `pyvenv.cfg` // file. This helps out static-analysis tools such as ty (see docs on // `CachedEnvironment::set_parent_environment`). @@ -1669,3 +1731,92 @@ fn read_recursion_depth_from_environment_variable() -> anyhow::Result { .parse::() .with_context(|| format!("invalid value for {}", EnvVars::UV_RUN_RECURSION_DEPTH)) } + +#[derive(Error, Debug)] +enum CopyEntrypointError { + #[error(transparent)] + Io(#[from] std::io::Error), + #[cfg(windows)] + #[error(transparent)] + Trampoline(#[from] uv_trampoline_builder::Error), +} + +/// Create a copy of the entrypoint at `source` at `target`, if it has a Python shebang, replacing +/// the previous Python executable with a new one. +/// +/// This is a no-op if the target already exists. +/// +/// Note on Windows, the entrypoints do not use shebangs and require a rewrite of the trampoline. +#[cfg(unix)] +fn copy_entrypoint( + source: &Path, + target: &Path, + previous_executable: &Path, + python_executable: &Path, +) -> Result<(), CopyEntrypointError> { + use std::io::Write; + use std::os::unix::fs::PermissionsExt; + + use fs_err::os::unix::fs::OpenOptionsExt; + + let contents = fs_err::read_to_string(source)?; + + let Some(contents) = contents + // Check for a relative path or relocatable shebang + .strip_prefix( + r#"#!/bin/sh +'''exec' "$(dirname -- "$(realpath -- "$0")")"/'python' "$0" "$@" +' ''' +"#, + ) + // Or an absolute path shebang + .or_else(|| contents.strip_prefix(&format!("#!{}\n", previous_executable.display()))) + else { + // If it's not a Python shebang, we'll skip it + trace!( + "Skipping copy of entrypoint `{}`: does not start with expected shebang", + source.user_display() + ); + return Ok(()); + }; + + let contents = format!("#!{}\n{}", python_executable.display(), contents); + let mode = fs_err::metadata(source)?.permissions().mode(); + let mut file = fs_err::OpenOptions::new() + .create_new(true) + .write(true) + .mode(mode) + .open(target)?; + file.write_all(contents.as_bytes())?; + + trace!("Updated entrypoint at {}", target.user_display()); + + Ok(()) +} + +/// Create a copy of the entrypoint at `source` at `target`, if it's a Python script launcher, +/// replacing the target Python executable with a new one. +#[cfg(windows)] +fn copy_entrypoint( + source: &Path, + target: &Path, + _previous_executable: &Path, + python_executable: &Path, +) -> Result<(), CopyEntrypointError> { + use uv_trampoline_builder::Launcher; + + let Some(launcher) = Launcher::try_from_path(source)? else { + return Ok(()); + }; + + let launcher = launcher.with_python_path(python_executable.to_path_buf()); + let mut file = fs_err::OpenOptions::new() + .create_new(true) + .write(true) + .open(target)?; + launcher.write_to_file(&mut file)?; + + trace!("Updated entrypoint at {}", target.user_display()); + + Ok(()) +} diff --git a/crates/uv/tests/it/run.rs b/crates/uv/tests/it/run.rs index ad8672788..2e9762a60 100644 --- a/crates/uv/tests/it/run.rs +++ b/crates/uv/tests/it/run.rs @@ -1319,6 +1319,181 @@ fn run_with_pyvenv_cfg_file() -> Result<()> { Ok(()) } +#[test] +fn run_with_overlay_interpreter() -> Result<()> { + let context = TestContext::new("3.12").with_filtered_exe_suffix(); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str(indoc! { r#" + [project] + name = "foo" + version = "1.0.0" + requires-python = ">=3.8" + dependencies = ["anyio"] + + [build-system] + requires = ["setuptools>=42"] + build-backend = "setuptools.build_meta" + + [project.scripts] + main = "foo:main" + "# + })?; + + let foo = context.temp_dir.child("src").child("foo"); + foo.create_dir_all()?; + let init_py = foo.child("__init__.py"); + init_py.write_str(indoc! { r#" + import sys + import shutil + from pathlib import Path + + def show_python(): + print(sys.executable) + + def copy_entrypoint(): + base = Path(sys.executable) + shutil.copyfile(base.with_name("main").with_suffix(base.suffix), sys.argv[1]) + + def main(): + show_python() + if len(sys.argv) > 1: + copy_entrypoint() + "# + })?; + + // The project's entrypoint should be rewritten to use the overlay interpreter. + uv_snapshot!(context.filters(), context.run().arg("--with").arg("iniconfig").arg("main").arg(context.temp_dir.child("main").as_os_str()), @r" + success: true + exit_code: 0 + ----- stdout ----- + [CACHE_DIR]/builds-v0/[TMP]/python + + ----- stderr ----- + Resolved 6 packages in [TIME] + Prepared 4 packages in [TIME] + Installed 4 packages in [TIME] + + anyio==4.3.0 + + foo==1.0.0 (from file://[TEMP_DIR]/) + + idna==3.6 + + sniffio==1.3.1 + Resolved 1 package in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + iniconfig==2.0.0 + "); + + #[cfg(unix)] + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + context.read("main"), @r##" + #![CACHE_DIR]/builds-v0/[TMP]/python + # -*- coding: utf-8 -*- + import sys + from foo import main + if __name__ == "__main__": + if sys.argv[0].endswith("-script.pyw"): + sys.argv[0] = sys.argv[0][:-11] + elif sys.argv[0].endswith(".exe"): + sys.argv[0] = sys.argv[0][:-4] + sys.exit(main()) + "## + ); + } + ); + + // The package, its dependencies, and the overlay dependencies should be available. + context + .run() + .arg("--with") + .arg("iniconfig") + .arg("python") + .arg("-c") + .arg("import foo; import anyio; import iniconfig") + .assert() + .success(); + + // When layering the project on top (via `--with`), the overlay interpreter also should be used. + uv_snapshot!(context.filters(), context.run().arg("--no-project").arg("--with").arg(".").arg("main"), @r" + success: true + exit_code: 0 + ----- stdout ----- + [CACHE_DIR]/builds-v0/[TMP]/python + + ----- stderr ----- + Resolved 4 packages in [TIME] + Prepared 1 package in [TIME] + Installed 4 packages in [TIME] + + anyio==4.3.0 + + foo==1.0.0 (from file://[TEMP_DIR]/) + + idna==3.6 + + sniffio==1.3.1 + "); + + // Switch to a relocatable virtual environment. + context.venv().arg("--relocatable").assert().success(); + + // The project's entrypoint should be rewritten to use the overlay interpreter. + uv_snapshot!(context.filters(), context.run().arg("--with").arg("iniconfig").arg("main").arg(context.temp_dir.child("main").as_os_str()), @r" + success: true + exit_code: 0 + ----- stdout ----- + [CACHE_DIR]/builds-v0/[TMP]/python + + ----- stderr ----- + Resolved 6 packages in [TIME] + Audited 4 packages in [TIME] + Resolved 1 package in [TIME] + "); + + // The package, its dependencies, and the overlay dependencies should be available. + context + .run() + .arg("--with") + .arg("iniconfig") + .arg("python") + .arg("-c") + .arg("import foo; import anyio; import iniconfig") + .assert() + .success(); + + #[cfg(unix)] + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + context.read("main"), @r##" + #![CACHE_DIR]/builds-v0/[TMP]/python + # -*- coding: utf-8 -*- + import sys + from foo import main + if __name__ == "__main__": + if sys.argv[0].endswith("-script.pyw"): + sys.argv[0] = sys.argv[0][:-11] + elif sys.argv[0].endswith(".exe"): + sys.argv[0] = sys.argv[0][:-4] + sys.exit(main()) + "## + ); + } + ); + + // When layering the project on top (via `--with`), the overlay interpreter also should be used. + uv_snapshot!(context.filters(), context.run().arg("--no-project").arg("--with").arg(".").arg("main"), @r" + success: true + exit_code: 0 + ----- stdout ----- + [CACHE_DIR]/builds-v0/[TMP]/python + + ----- stderr ----- + Resolved 4 packages in [TIME] + "); + + Ok(()) +} + #[test] fn run_with_build_constraints() -> Result<()> { let context = TestContext::new("3.9"); From c8486da495674686467b75709cebc8f4f11553f5 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 22 Jul 2025 08:13:38 -0500 Subject: [PATCH 311/349] Update virtual environment removal to delete `pyvenv.cfg` last (#14808) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An alternative to https://github.com/astral-sh/uv/pull/14569 This isn't a complete solution to https://github.com/astral-sh/uv/issues/13986, in the sense that it's still "fatal" to `uv sync` if we fail to delete an environment, but I think that's okay — deferring deletion is much more complicated. This at least doesn't break users once the deletion fails. The downside is we'll generally treat this virtual environment is valid, even if we nuked a bunch of it. Closes https://github.com/astral-sh/uv/issues/13986 --- crates/uv-virtualenv/src/lib.rs | 2 +- crates/uv-virtualenv/src/virtualenv.rs | 34 +++++++++++++++++++++----- crates/uv/src/commands/project/mod.rs | 8 +++--- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/crates/uv-virtualenv/src/lib.rs b/crates/uv-virtualenv/src/lib.rs index b04a500a5..bcf1e9f97 100644 --- a/crates/uv-virtualenv/src/lib.rs +++ b/crates/uv-virtualenv/src/lib.rs @@ -6,7 +6,7 @@ use thiserror::Error; use uv_configuration::PreviewMode; use uv_python::{Interpreter, PythonEnvironment}; -pub use virtualenv::OnExisting; +pub use virtualenv::{OnExisting, remove_virtualenv}; mod virtualenv; diff --git a/crates/uv-virtualenv/src/virtualenv.rs b/crates/uv-virtualenv/src/virtualenv.rs index fb22a0724..2227b71b9 100644 --- a/crates/uv-virtualenv/src/virtualenv.rs +++ b/crates/uv-virtualenv/src/virtualenv.rs @@ -97,7 +97,8 @@ pub(crate) fn create( } OnExisting::Remove => { debug!("Removing existing {name} due to `--clear`"); - remove_venv_directory(location)?; + remove_virtualenv(location)?; + fs::create_dir_all(location)?; } OnExisting::Fail if location @@ -110,7 +111,8 @@ pub(crate) fn create( match confirm_clear(location, name)? { Some(true) => { debug!("Removing existing {name} due to confirmation"); - remove_venv_directory(location)?; + remove_virtualenv(location)?; + fs::create_dir_all(location)?; } Some(false) => { let hint = format!( @@ -566,9 +568,10 @@ fn confirm_clear(location: &Path, name: &'static str) -> Result, io } } -fn remove_venv_directory(location: &Path) -> Result<(), Error> { - // On Windows, if the current executable is in the directory, guard against - // self-deletion. +/// Perform a safe removal of a virtual environment. +pub fn remove_virtualenv(location: &Path) -> Result<(), Error> { + // On Windows, if the current executable is in the directory, defer self-deletion since Windows + // won't let you unlink a running executable. #[cfg(windows)] if let Ok(itself) = std::env::current_exe() { let target = std::path::absolute(location)?; @@ -578,8 +581,27 @@ fn remove_venv_directory(location: &Path) -> Result<(), Error> { } } + // We defer removal of the `pyvenv.cfg` until the end, so if we fail to remove the environment, + // uv can still identify it as a Python virtual environment that can be deleted. + for entry in fs::read_dir(location)? { + let entry = entry?; + let path = entry.path(); + if path == location.join("pyvenv.cfg") { + continue; + } + if path.is_dir() { + fs::remove_dir_all(&path)?; + } else { + fs::remove_file(&path)?; + } + } + + match fs::remove_file(location.join("pyvenv.cfg")) { + Ok(()) => {} + Err(err) if err.kind() == io::ErrorKind::NotFound => {} + Err(err) => return Err(err.into()), + } fs::remove_dir_all(location)?; - fs::create_dir_all(location)?; Ok(()) } diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index becd2a26e..3ad530edd 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -43,6 +43,7 @@ use uv_scripts::Pep723ItemRef; use uv_settings::PythonInstallMirrors; use uv_static::EnvVars; use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy}; +use uv_virtualenv::remove_virtualenv; use uv_warnings::{warn_user, warn_user_once}; use uv_workspace::dependency_groups::DependencyGroupError; use uv_workspace::pyproject::PyProjectToml; @@ -1373,7 +1374,7 @@ impl ProjectEnvironment { // Remove the existing virtual environment if it doesn't meet the requirements. if replace { - match fs_err::remove_dir_all(&root) { + match remove_virtualenv(&root) { Ok(()) => { writeln!( printer.stderr(), @@ -1381,8 +1382,9 @@ impl ProjectEnvironment { root.user_display().cyan() )?; } - Err(e) if e.kind() == std::io::ErrorKind::NotFound => {} - Err(e) => return Err(e.into()), + Err(uv_virtualenv::Error::Io(err)) + if err.kind() == std::io::ErrorKind::NotFound => {} + Err(err) => return Err(err.into()), } } From 2677e85df952d2ec6908380dd1c3d6a4f52b5e5a Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 22 Jul 2025 09:20:09 -0400 Subject: [PATCH 312/349] Disallow writing symlinks outside the source distribution target directory (#12259) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Closes #12163. ## Test Plan Created an offending source distribution with this script: ```python import io import tarfile import textwrap import time PKG_NAME = "badpkg" VERSION = "0.1" DIST_NAME = f"{PKG_NAME}-{VERSION}" ARCHIVE = f"{DIST_NAME}.tar.gz" def _bytes(data: str) -> io.BytesIO: """Helper: wrap a text blob as a BytesIO for tarfile.addfile().""" return io.BytesIO(data.encode()) def main(out_path: str = ARCHIVE) -> None: now = int(time.time()) with tarfile.open(out_path, mode="w:gz") as tar: def add_file(path: str, data: str, mode: int = 0o644) -> None: """Add a regular file whose *content* is supplied as a string.""" buf = _bytes(data) info = tarfile.TarInfo(path) info.size = len(buf.getbuffer()) info.mtime = now info.mode = mode tar.addfile(info, buf) # ── top‑level setup.py ─────────────────────────────────────────────── setup_py = textwrap.dedent(f"""\ from setuptools import setup, find_packages setup( name="{PKG_NAME}", version="{VERSION}", packages=find_packages(), ) """) add_file(f"{DIST_NAME}/setup.py", setup_py) # ── minimal package code ───────────────────────────────────────────── add_file(f"{DIST_NAME}/{PKG_NAME}/__init__.py", "# placeholder\\n") # ── the malicious symlink ──────────────────────────────────────────── link = tarfile.TarInfo(f"{DIST_NAME}/{PKG_NAME}/evil_link") link.type = tarfile.SYMTYPE link.mtime = now link.mode = 0o777 link.linkname = "../../../outside.txt" tar.addfile(link) print(f"Created {out_path}") if __name__ == "__main__": main() ``` Verified that both `pip install` and `uv pip install` rejected it. I also changed `link.linkname = "../../../outside.txt"` to `link.linkname = "/etc/outside"`, and verified that the absolute path was rejected too. --- Cargo.lock | 40 ++++++++++++++++----------------- Cargo.toml | 2 +- crates/uv-extract/src/stream.rs | 5 +++++ crates/uv/tests/it/build.rs | 4 +++- 4 files changed, 28 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e8f91b076..9ff891ecc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -500,22 +500,20 @@ dependencies = [ [[package]] name = "bzip2" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bafdbf26611df8c14810e268ddceda071c297570a5fb360ceddf617fe417ef58" +checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" dependencies = [ "bzip2-sys", - "libc", ] [[package]] name = "bzip2-sys" -version = "0.1.11+1.0.8" +version = "0.1.13+1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" dependencies = [ "cc", - "libc", "pkg-config", ] @@ -690,9 +688,9 @@ checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "codspeed" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7524e02ff6173bc143d9abc01b518711b77addb60de871bbe5686843f88fb48" +checksum = "d29180405ab3b37bb020246ea66bf8ae233708766fd59581ae929feaef10ce91" dependencies = [ "anyhow", "bincode", @@ -708,9 +706,9 @@ dependencies = [ [[package]] name = "codspeed-criterion-compat" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f71662331c4f854131a42b95055f3f8cbca53640348985f699635b1f96d8c26" +checksum = "2454d874ca820ffd71273565530ad318f413195bbc99dce6c958ca07db362c63" dependencies = [ "codspeed", "codspeed-criterion-compat-walltime", @@ -719,9 +717,9 @@ dependencies = [ [[package]] name = "codspeed-criterion-compat-walltime" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c9bd9e895e0aa263d139a8b5f58a4ea4abb86d5982ec7f58d3c7b8465c1e01" +checksum = "093a9383cdd1a5a0bd1a47cdafb49ae0c6dcd0793c8fb8f79768bab423128c9c" dependencies = [ "anes", "cast", @@ -761,7 +759,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -1593,11 +1591,11 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "home" -version = "0.5.11" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3347,7 +3345,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.2", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -6234,9 +6232,9 @@ dependencies = [ [[package]] name = "wasmtimer" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0048ad49a55b9deb3953841fa1fc5858f0efbcb7a18868c899a360269fac1b23" +checksum = "d8d49b5d6c64e8558d9b1b065014426f35c18de636895d24893dbbd329743446" dependencies = [ "futures", "js-sys", @@ -6341,7 +6339,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -6972,7 +6970,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84e9a772a54b54236b9b744aaaf8d7be01b4d6e99725523cb82cb32d1c81b1d7" dependencies = [ "arbitrary", - "bzip2 0.5.0", + "bzip2 0.5.2", "crc32fast", "crossbeam-utils", "displaydoc", diff --git a/Cargo.toml b/Cargo.toml index 7c858a81d..e89f786ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,7 +76,7 @@ anstream = { version = "0.6.15" } anyhow = { version = "1.0.89" } arcstr = { version = "1.2.0" } arrayvec = { version = "0.7.6" } -astral-tokio-tar = { version = "0.5.1" } +astral-tokio-tar = { version = "0.5.2" } async-channel = { version = "2.3.1" } async-compression = { version = "0.4.12", features = ["bzip2", "gzip", "xz", "zstd"] } async-trait = { version = "0.1.82" } diff --git a/crates/uv-extract/src/stream.rs b/crates/uv-extract/src/stream.rs index bed8f43bf..f7fc797d7 100644 --- a/crates/uv-extract/src/stream.rs +++ b/crates/uv-extract/src/stream.rs @@ -236,6 +236,7 @@ pub async fn untar_gz( ) .set_preserve_mtime(false) .set_preserve_permissions(false) + .set_allow_external_symlinks(false) .build(); Ok(untar_in(archive, target.as_ref()).await?) } @@ -255,6 +256,7 @@ pub async fn untar_bz2( ) .set_preserve_mtime(false) .set_preserve_permissions(false) + .set_allow_external_symlinks(false) .build(); Ok(untar_in(archive, target.as_ref()).await?) } @@ -274,6 +276,7 @@ pub async fn untar_zst( ) .set_preserve_mtime(false) .set_preserve_permissions(false) + .set_allow_external_symlinks(false) .build(); Ok(untar_in(archive, target.as_ref()).await?) } @@ -293,6 +296,7 @@ pub async fn untar_xz( ) .set_preserve_mtime(false) .set_preserve_permissions(false) + .set_allow_external_symlinks(false) .build(); untar_in(archive, target.as_ref()).await?; Ok(()) @@ -311,6 +315,7 @@ pub async fn untar( tokio_tar::ArchiveBuilder::new(&mut reader as &mut (dyn tokio::io::AsyncRead + Unpin)) .set_preserve_mtime(false) .set_preserve_permissions(false) + .set_allow_external_symlinks(false) .build(); untar_in(archive, target.as_ref()).await?; Ok(()) diff --git a/crates/uv/tests/it/build.rs b/crates/uv/tests/it/build.rs index 656c68d3f..1956e8b85 100644 --- a/crates/uv/tests/it/build.rs +++ b/crates/uv/tests/it/build.rs @@ -1754,13 +1754,14 @@ fn build_with_symlink() -> Result<()> { build-backend = "hatchling.build" "#})?; fs_err::os::unix::fs::symlink( - context.temp_dir.child("pyproject.toml.real"), + "pyproject.toml.real", context.temp_dir.child("pyproject.toml"), )?; context .temp_dir .child("src/softlinked/__init__.py") .touch()?; + fs_err::remove_dir_all(&context.venv)?; uv_snapshot!(context.filters(), context.build(), @r###" success: true exit_code: 0 @@ -1799,6 +1800,7 @@ fn build_with_hardlink() -> Result<()> { .temp_dir .child("src/hardlinked/__init__.py") .touch()?; + fs_err::remove_dir_all(&context.venv)?; uv_snapshot!(context.filters(), context.build(), @r###" success: true exit_code: 0 From e49d61db1fb808819fdf86ddc5bee45fcab047f7 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 22 Jul 2025 08:21:54 -0500 Subject: [PATCH 313/349] Emit JSON output with `--quiet` (#14810) --- crates/uv/src/commands/project/sync.rs | 4 +-- crates/uv/tests/it/sync.rs | 40 ++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index adf3b61f2..cbce0082a 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -273,7 +273,7 @@ pub(crate) async fn sync( dry_run: dry_run.enabled(), }; if let Some(output) = report.format(output_format) { - writeln!(printer.stdout(), "{output}")?; + writeln!(printer.stdout_important(), "{output}")?; } return Ok(ExitStatus::Success); } @@ -363,7 +363,7 @@ pub(crate) async fn sync( }; if let Some(output) = report.format(output_format) { - writeln!(printer.stdout(), "{output}")?; + writeln!(printer.stdout_important(), "{output}")?; } // Identify the installation target. diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index 4f2853e61..5ee22eed6 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -432,6 +432,46 @@ fn sync_json() -> Result<()> { The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. "); + // Test that JSON output is shown even with --quiet flag + uv_snapshot!(context.filters(), context.sync() + .arg("--quiet") + .arg("--frozen") + .arg("--output-format").arg("json"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + { + "schema": { + "version": "preview" + }, + "target": "project", + "project": { + "path": "[TEMP_DIR]/", + "workspace": { + "path": "[TEMP_DIR]/" + } + }, + "sync": { + "environment": { + "path": "[VENV]/", + "python": { + "path": "[VENV]/[BIN]/[PYTHON]", + "version": "3.12.[X]", + "implementation": "cpython" + } + }, + "action": "check" + }, + "lock": { + "path": "[TEMP_DIR]/uv.lock", + "action": "use" + }, + "dry_run": false + } + + ----- stderr ----- + "#); + Ok(()) } From 96b889bce39b8e58142a2e045c285d60684a78b4 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 22 Jul 2025 08:32:45 -0500 Subject: [PATCH 314/349] Add hint to use `uv self version` when `uv version` cannot find a project (#14738) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When users run `uv version` in a directory without a `pyproject.toml` file, they often intend to check uv's own version rather than a project's version. This change adds a helpful hint to guide users to the correct command. **Before:** ``` ❯ uv version error: No `pyproject.toml` found in current directory or any parent directory ``` **After:** ``` ❯ uv version error: No `pyproject.toml` found in current directory or any parent directory hint: If you meant to view uv's version, use `uv self version` instead ``` ## Changes - Modified `find_target()` function in `crates/uv/src/commands/project/version.rs` to catch `WorkspaceError::MissingPyprojectToml` specifically and enhance the error message with a helpful hint - Added import for `WorkspaceError` to access the specific error type - Updated existing tests to expect the new hint message in error output - Added new test case `version_get_missing_with_hint()` to verify behavior The hint appears consistently across all scenarios where `uv version` fails to find a project: - `uv version` (normal mode) - `uv version --project .` (explicit project mode) - `uv version --preview` (preview mode) The change maintains all existing functionality - when a `pyproject.toml` is found, `uv version` continues to work normally without showing the hint. Fixes #14730. --- 💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: zanieb <2586601+zanieb@users.noreply.github.com> --- crates/uv/src/commands/project/version.rs | 33 +++++++++++++++++++---- crates/uv/src/lib.rs | 3 +++ crates/uv/tests/it/version.rs | 10 +++---- 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/crates/uv/src/commands/project/version.rs b/crates/uv/src/commands/project/version.rs index efba226b9..c4b32485d 100644 --- a/crates/uv/src/commands/project/version.rs +++ b/crates/uv/src/commands/project/version.rs @@ -21,7 +21,7 @@ use uv_python::{PythonDownloads, PythonPreference, PythonRequest}; use uv_settings::PythonInstallMirrors; use uv_workspace::pyproject_mut::Error; use uv_workspace::{ - DiscoveryOptions, WorkspaceCache, + DiscoveryOptions, WorkspaceCache, WorkspaceError, pyproject_mut::{DependencyTarget, PyProjectTomlMut}, }; use uv_workspace::{VirtualProject, Workspace}; @@ -59,6 +59,7 @@ pub(crate) async fn project_version( output_format: VersionFormat, project_dir: &Path, package: Option, + explicit_project: bool, dry_run: bool, locked: bool, frozen: bool, @@ -78,7 +79,7 @@ pub(crate) async fn project_version( preview: PreviewMode, ) -> Result { // Read the metadata - let project = find_target(project_dir, package.as_ref()).await?; + let project = find_target(project_dir, package.as_ref(), explicit_project).await?; let pyproject_path = project.root().join("pyproject.toml"); let Some(name) = project.project_name().cloned() else { @@ -325,10 +326,30 @@ pub(crate) async fn project_version( Ok(status) } +/// Add hint to use `uv self version` when workspace discovery fails due to missing pyproject.toml +/// and --project was not explicitly passed +fn hint_uv_self_version(err: WorkspaceError, explicit_project: bool) -> anyhow::Error { + if matches!(err, WorkspaceError::MissingPyprojectToml) && !explicit_project { + anyhow!( + "{}\n\n{}{} If you meant to view uv's version, use `{}` instead", + err, + "hint".bold().cyan(), + ":".bold(), + "uv self version".green() + ) + } else { + err.into() + } +} + /// Find the pyproject.toml we're modifying /// /// Note that `uv version` never needs to support PEP 723 scripts, as those are unversioned. -async fn find_target(project_dir: &Path, package: Option<&PackageName>) -> Result { +async fn find_target( + project_dir: &Path, + package: Option<&PackageName>, + explicit_project: bool, +) -> Result { // Find the project in the workspace. // No workspace caching since `uv version` changes the workspace definition. let project = if let Some(package) = package { @@ -338,7 +359,8 @@ async fn find_target(project_dir: &Path, package: Option<&PackageName>) -> Resul &DiscoveryOptions::default(), &WorkspaceCache::default(), ) - .await? + .await + .map_err(|err| hint_uv_self_version(err, explicit_project))? .with_current_project(package.clone()) .with_context(|| format!("Package `{package}` not found in workspace"))?, ) @@ -348,7 +370,8 @@ async fn find_target(project_dir: &Path, package: Option<&PackageName>) -> Resul &DiscoveryOptions::default(), &WorkspaceCache::default(), ) - .await? + .await + .map_err(|err| hint_uv_self_version(err, explicit_project))? }; Ok(project) } diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 9a67bb877..4a937b0db 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -1058,6 +1058,7 @@ async fn run(mut cli: Cli) -> Result { script, globals, cli.top_level.no_config, + cli.top_level.global_args.project.is_some(), filesystem, cache, printer, @@ -1659,6 +1660,7 @@ async fn run_project( globals: GlobalSettings, // TODO(zanieb): Determine a better story for passing `no_config` in here no_config: bool, + explicit_project: bool, filesystem: Option, cache: Cache, printer: Printer, @@ -2050,6 +2052,7 @@ async fn run_project( args.output_format, project_dir, args.package, + explicit_project, args.dry_run, args.locked, args.frozen, diff --git a/crates/uv/tests/it/version.rs b/crates/uv/tests/it/version.rs index 53cb0de06..e2f9f1201 100644 --- a/crates/uv/tests/it/version.rs +++ b/crates/uv/tests/it/version.rs @@ -1607,20 +1607,20 @@ fn version_get_fallback_missing_strict() -> Result<()> { Ok(()) } -// Should error if this pyproject.toml is missing -// and --preview was passed explicitly. +/// Should error with hint if pyproject.toml is missing in normal mode #[test] -fn version_get_fallback_missing_strict_preview() -> Result<()> { +fn version_get_missing_with_hint() -> Result<()> { let context = TestContext::new("3.12"); - uv_snapshot!(context.filters(), context.version() - .arg("--preview"), @r" + uv_snapshot!(context.filters(), context.version(), @r" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- error: No `pyproject.toml` found in current directory or any parent directory + + hint: If you meant to view uv's version, use `uv self version` instead "); Ok(()) From 7d41bdb3089637f790a4ca49a35881d8af33091e Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 22 Jul 2025 10:16:59 -0500 Subject: [PATCH 315/349] Allow removal of virtual environments with missing interpreters (#14812) Co-authored-by: konsti --- crates/uv/src/commands/project/mod.rs | 16 ++-- crates/uv/tests/it/sync.rs | 124 +++++++++++++++++++++----- 2 files changed, 115 insertions(+), 25 deletions(-) diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index 3ad530edd..e6f41c2af 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -940,13 +940,19 @@ impl ProjectInterpreter { )); } InvalidEnvironmentKind::MissingExecutable(_) => { + // If it's not an empty directory if fs_err::read_dir(&root).is_ok_and(|mut dir| dir.next().is_some()) { - return Err(ProjectError::InvalidProjectEnvironmentDir( - root, - "it is not a valid Python environment (no Python executable was found)" - .to_string(), - )); + // ... and there's no `pyvenv.cfg` + if !root.join("pyvenv.cfg").try_exists().unwrap_or_default() { + // ... then it's not a valid Python environment + return Err(ProjectError::InvalidProjectEnvironmentDir( + root, + "it is not a valid Python environment (no Python executable was found)" + .to_string(), + )); + } } + // Otherwise, we'll delete it } // If the environment is an empty directory, it's fine to use InvalidEnvironmentKind::Empty => {} diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index 5ee22eed6..9905c8a65 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -60,14 +60,14 @@ fn locked() -> Result<()> { )?; // Running with `--locked` should error, if no lockfile is present. - uv_snapshot!(context.filters(), context.sync().arg("--locked"), @r" + uv_snapshot!(context.filters(), context.sync().arg("--locked"), @r###" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- error: Unable to find lockfile at `uv.lock`. To create a lockfile, run `uv lock` or `uv sync`. - "); + "###); // Lock the initial requirements. context.lock().assert().success(); @@ -86,7 +86,7 @@ fn locked() -> Result<()> { )?; // Running with `--locked` should error. - uv_snapshot!(context.filters(), context.sync().arg("--locked"), @r" + uv_snapshot!(context.filters(), context.sync().arg("--locked"), @r###" success: false exit_code: 1 ----- stdout ----- @@ -94,7 +94,7 @@ fn locked() -> Result<()> { ----- stderr ----- Resolved 2 packages in [TIME] The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. - "); + "###); let updated = context.read("uv.lock"); @@ -120,14 +120,14 @@ fn frozen() -> Result<()> { )?; // Running with `--frozen` should error, if no lockfile is present. - uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r" + uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- error: Unable to find lockfile at `uv.lock`. To create a lockfile, run `uv lock` or `uv sync`. - "); + "###); context.lock().assert().success(); @@ -422,7 +422,7 @@ fn sync_json() -> Result<()> { uv_snapshot!(context.filters(), context.sync() .arg("--locked") - .arg("--output-format").arg("json"), @r" + .arg("--output-format").arg("json"), @r###" success: false exit_code: 1 ----- stdout ----- @@ -430,7 +430,7 @@ fn sync_json() -> Result<()> { ----- stderr ----- Resolved 2 packages in [TIME] The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. - "); + "###); // Test that JSON output is shown even with --quiet flag uv_snapshot!(context.filters(), context.sync() @@ -932,7 +932,7 @@ fn check() -> Result<()> { )?; // Running `uv sync --check` should fail. - uv_snapshot!(context.filters(), context.sync().arg("--check"), @r" + uv_snapshot!(context.filters(), context.sync().arg("--check"), @r###" success: false exit_code: 1 ----- stdout ----- @@ -945,7 +945,7 @@ fn check() -> Result<()> { Would install 1 package + iniconfig==2.0.0 The environment is outdated; run `uv sync` to update the environment - "); + "###); // Sync the environment. uv_snapshot!(context.filters(), context.sync(), @r" @@ -1753,7 +1753,7 @@ fn sync_environment() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync(), @r" + uv_snapshot!(context.filters(), context.sync(), @r###" success: false exit_code: 2 ----- stdout ----- @@ -1761,7 +1761,7 @@ fn sync_environment() -> Result<()> { ----- stderr ----- Resolved 2 packages in [TIME] error: The current Python platform is not compatible with the lockfile's supported environments: `python_full_version < '3.11'` - "); + "###); assert!(context.temp_dir.child("uv.lock").exists()); @@ -4785,7 +4785,7 @@ fn sync_active_project_environment() -> Result<()> { )?; // Running `uv sync` with `VIRTUAL_ENV` should warn - uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, "foo"), @r" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, "foo"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -4798,7 +4798,7 @@ fn sync_active_project_environment() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "); + "###); context .temp_dir @@ -6785,16 +6785,21 @@ fn sync_invalid_environment() -> Result<()> { "); } - // But if the Python executable is missing entirely we should also fail + // If the Python executable is missing entirely, we'll delete and use it fs_err::remove_dir_all(&bin)?; - uv_snapshot!(context.filters(), context.sync(), @r###" - success: false - exit_code: 2 + uv_snapshot!(context.filters(), context.sync(), @r" + success: true + exit_code: 0 ----- stdout ----- ----- stderr ----- - error: Project virtual environment directory `[VENV]/` cannot be used because it is not a valid Python environment (no Python executable was found) - "###); + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + Removed virtual environment at: .venv + Creating virtual environment at: .venv + Resolved 2 packages in [TIME] + Installed 1 package in [TIME] + + iniconfig==2.0.0 + "); // But if it's not a virtual environment... fs_err::remove_dir_all(context.temp_dir.join(".venv"))?; @@ -6827,6 +6832,17 @@ fn sync_invalid_environment() -> Result<()> { error: Project virtual environment directory `[VENV]/` cannot be used because it is not a compatible environment but cannot be recreated because it is not a virtual environment "###); + // Even if there's no Python executable + fs_err::remove_dir_all(&bin)?; + uv_snapshot!(context.filters(), context.sync(), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Project virtual environment directory `[VENV]/` cannot be used because it is not a valid Python environment (no Python executable was found) + "); + context .temp_dir .child(".venv") @@ -6841,6 +6857,74 @@ fn sync_invalid_environment() -> Result<()> { Ok(()) } +#[cfg(unix)] +#[test] +fn sync_partial_environment_delete() -> Result<()> { + use std::os::unix::fs::PermissionsExt; + + let context = TestContext::new_with_versions(&["3.13", "3.12"]); + + context.init().arg("-p").arg("3.12").assert().success(); + uv_snapshot!(context.filters(), context.sync().arg("-p").arg("3.13"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.13.[X] interpreter at: [PYTHON-3.13] + Creating virtual environment at: .venv + Resolved 1 package in [TIME] + Audited in [TIME] + "); + + // Create a directory that's unreadable, erroring on trying to delete its children. + // This relies on our implementation listing directory entries before deleting them — which is a + // bit of a hack but accomplishes the goal here. + let unreadable2 = context.temp_dir.child(".venv/z2.txt"); + fs_err::create_dir(&unreadable2)?; + let perms = std::fs::Permissions::from_mode(0o000); + fs_err::set_permissions(&unreadable2, perms)?; + + uv_snapshot!(context.filters(), context.sync().arg("-p").arg("3.12"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + error: failed to remove directory `[VENV]/z2.txt`: Permission denied (os error 13) + "); + + uv_snapshot!(context.filters(), context.sync().arg("-p").arg("3.12"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + error: failed to remove directory `[VENV]/z2.txt`: Permission denied (os error 13) + "); + + // Remove the unreadable directory + fs_err::remove_dir(unreadable2)?; + + // We should be able to remove the venv now + uv_snapshot!(context.filters(), context.sync().arg("-p").arg("3.12"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + Removed virtual environment at: .venv + Creating virtual environment at: .venv + Resolved 1 package in [TIME] + Audited in [TIME] + "); + + Ok(()) +} + /// Avoid validating workspace members when `--no-sources` is provided. Rather than reporting that /// `./anyio` is missing, install `anyio` from the registry. #[test] From f0151f3a183eaa687fde9f23fab3bbea923c4058 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 22 Jul 2025 11:36:20 -0500 Subject: [PATCH 316/349] Bump version to 0.8.1 (#14818) --- CHANGELOG.md | 208 ++++++++++++++------------ Cargo.lock | 6 +- crates/uv-build/Cargo.toml | 2 +- crates/uv-build/pyproject.toml | 2 +- crates/uv-version/Cargo.toml | 2 +- crates/uv/Cargo.toml | 2 +- docs/concepts/build-backend.md | 2 +- 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/pre-commit.md | 10 +- pyproject.toml | 2 +- 13 files changed, 139 insertions(+), 117 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b80747ed1..7b1d8f16a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,44 @@ +## 0.8.1 + +### Enhancements + +- Add support for `HF_TOKEN` ([#14797](https://github.com/astral-sh/uv/pull/14797)) +- Allow `--config-settings-package` to apply configuration settings at the package level ([#14573](https://github.com/astral-sh/uv/pull/14573)) +- Create (e.g.) `python3.13t` executables in `uv venv` ([#14764](https://github.com/astral-sh/uv/pull/14764)) +- Disallow writing symlinks outside the source distribution target directory ([#12259](https://github.com/astral-sh/uv/pull/12259)) +- Elide traceback when `python -m uv` in interrupted with Ctrl-C on Windows ([#14715](https://github.com/astral-sh/uv/pull/14715)) +- Match `--bounds` formatting for `uv_build` bounds in `uv init` ([#14731](https://github.com/astral-sh/uv/pull/14731)) +- Support `extras` and `dependency_groups` markers in PEP 508 grammar ([#14753](https://github.com/astral-sh/uv/pull/14753)) +- Support `extras` and `dependency_groups` markers on `uv pip install` and `uv pip sync` ([#14755](https://github.com/astral-sh/uv/pull/14755)) +- Add hint to use `uv self version` when `uv version` cannot find a project ([#14738](https://github.com/astral-sh/uv/pull/14738)) +- Improve error reporting when removing Python versions from the Windows registry ([#14722](https://github.com/astral-sh/uv/pull/14722)) +- Make warnings about masked `[tool.uv]` fields more precise ([#14325](https://github.com/astral-sh/uv/pull/14325)) + +### Preview features + +- Emit JSON output in `uv sync` with `--quiet` ([#14810](https://github.com/astral-sh/uv/pull/14810)) + +### Bug fixes + +- Allow removal of virtual environments with missing interpreters ([#14812](https://github.com/astral-sh/uv/pull/14812)) +- Apply `Cache-Control` overrides to response, not request headers ([#14736](https://github.com/astral-sh/uv/pull/14736)) +- Copy entry points into ephemeral environments to ensure layers are respected ([#14790](https://github.com/astral-sh/uv/pull/14790)) +- Workaround Jupyter Lab application directory discovery in ephemeral environments ([#14790](https://github.com/astral-sh/uv/pull/14790)) +- Enforce `requires-python` in `pylock.toml` ([#14787](https://github.com/astral-sh/uv/pull/14787)) +- Fix kebab casing of `README` variants in build backend ([#14762](https://github.com/astral-sh/uv/pull/14762)) +- Improve concurrency resilience of removing Python versions from the Windows registry ([#14717](https://github.com/astral-sh/uv/pull/14717)) +- Retry HTTP requests on invalid data errors ([#14703](https://github.com/astral-sh/uv/pull/14703)) +- Update virtual environment removal to delete `pyvenv.cfg` last ([#14808](https://github.com/astral-sh/uv/pull/14808)) +- Error on unknown fields in `dependency-metadata` ([#14801](https://github.com/astral-sh/uv/pull/14801)) + +### Documentation + +- Recommend installing `setup-uv` after `setup-python` in Github Actions integration guide ([#14741](https://github.com/astral-sh/uv/pull/14741)) +- Clarify which portions of `requires-python` behavior are consistent with pip ([#14752](https://github.com/astral-sh/uv/pull/14752)) + ## 0.8.0 Since we released uv [0.7.0](https://github.com/astral-sh/uv/releases/tag/0.7.0) in April, we've accumulated various changes that improve correctness and user experience, but could break some workflows. This release contains those changes; many have been marked as breaking out of an abundance of caution. We expect most users to be able to upgrade without making changes. @@ -12,140 +50,124 @@ This release also includes the stabilization of a couple `uv python install` fea ### Breaking changes - **Install Python executables into a directory on the `PATH` ([#14626](https://github.com/astral-sh/uv/pull/14626))** - + `uv python install` now installs a versioned Python executable (e.g., `python3.13`) into a directory on the `PATH` (e.g., `~/.local/bin`) by default. This behavior has been available under the `--preview` flag since [Oct 2024](https://github.com/astral-sh/uv/pull/8458). This change should not be breaking unless it shadows a Python executable elsewhere on the `PATH`. - + To install unversioned executables, i.e., `python3` and `python`, use the `--default` flag. The `--default` flag has also been in preview, but is not stabilized in this release. - + Note that these executables point to the base Python installation and only include the standard library. That means they will not include dependencies from your current project (use `uv run python` instead) and you cannot install packages into their environment (use `uvx --with python` instead). - + As with tool installation, the target directory respects common variables like `XDG_BIN_HOME` and can be overridden with a `UV_PYTHON_BIN_DIR` variable. - + You can opt out of this behavior with `uv python install --no-bin` or `UV_PYTHON_INSTALL_BIN=0`. - + See the [documentation on installing Python executables](https://docs.astral.sh/uv/concepts/python-versions/#installing-python-executables) for more details. - - **Register Python versions with the Windows Registry ([#14625](https://github.com/astral-sh/uv/pull/14625))** - + `uv python install` now registers the installed Python version with the Windows Registry as specified by [PEP 514](https://peps.python.org/pep-0514/). This allows using uv installed Python versions via the `py` launcher. This behavior has been available under the `--preview` flag since [Jan 2025](https://github.com/astral-sh/uv/pull/10634). This change should not be breaking, as using the uv Python versions with `py` requires explicit opt in. - + You can opt out of this behavior with `uv python install --no-registry` or `UV_PYTHON_INSTALL_REGISTRY=0`. - - **Prompt before removing an existing directory in `uv venv` ([#14309](https://github.com/astral-sh/uv/pull/14309))** - + Previously, `uv venv` would remove an existing virtual environment without confirmation. While this is consistent with the behavior of project commands (e.g., `uv sync`), it's surprising to users that are using imperative workflows (i.e., `uv pip`). Now, `uv venv` will prompt for confirmation before removing an existing virtual environment. **If not in an interactive context, uv will still remove the virtual environment for backwards compatibility. However, this behavior is likely to change in a future release.** - + The behavior for other commands (e.g., `uv sync`) is unchanged. - + You can opt out of this behavior by setting `UV_VENV_CLEAR=1` or passing the `--clear` flag. - - **Validate that discovered interpreters meet the Python preference ([#7934](https://github.com/astral-sh/uv/pull/7934))** - + uv allows opting out of its managed Python versions with the `--no-managed-python` and `python-preference` options. - - Previously, uv would not enforce this option for Python interpreters discovered on the `PATH`. For example, if a symlink to a managed Python interpreter was created, uv would allow it to be used even if `--no-managed-python` was provided. Now, uv ignores Python interpreters that do not match the Python preference _unless_ they are in an active virtual environment or are explicitly requested, e.g., with `--python /path/to/python3.13`. - + + Previously, uv would not enforce this option for Python interpreters discovered on the `PATH`. For example, if a symlink to a managed Python interpreter was created, uv would allow it to be used even if `--no-managed-python` was provided. Now, uv ignores Python interpreters that do not match the Python preference *unless* they are in an active virtual environment or are explicitly requested, e.g., with `--python /path/to/python3.13`. + Similarly, uv would previously not invalidate existing project environments if they did not match the Python preference. Now, uv will invalidate and recreate project environments when the Python preference changes. - + You can opt out of this behavior by providing the explicit path to the Python interpreter providing `--managed-python` / `--no-managed-python` matching the interpreter you want. - - **Install dependencies without build systems when they are `path` sources ([#14413](https://github.com/astral-sh/uv/pull/14413))** - + When working on a project, uv uses the [presence of a build system](https://docs.astral.sh/uv/concepts/projects/config/#build-systems) to determine if it should be built and installed into the environment. However, when a project is a dependency of another project, it can be surprising for the dependency to be missing from the environment. - + Previously, uv would not build and install dependencies with [`path` sources](https://docs.astral.sh/uv/concepts/projects/dependencies/#path) unless they declared a build system or set `tool.uv.package = true`. Now, dependencies with `path` sources are built and installed regardless of the presence of a build system. If a build system is not present, the `setuptools.build_meta:__legacy__ ` backend will be used (per [PEP 517](https://peps.python.org/pep-0517/#source-trees)). - + You can opt out of this behavior by setting `package = false` in the source declaration, e.g.: - + ```toml [tool.uv.sources] foo = { path = "./foo", package = false } ``` - + Or, by setting `tool.uv.package = false` in the dependent `pyproject.toml`. - + See the documentation on [virtual dependencies](https://docs.astral.sh/uv/concepts/projects/dependencies/#virtual-dependencies) for details. - - **Install dependencies without build systems when they are workspace members ([#14663](https://github.com/astral-sh/uv/pull/14663))** - - As described above for dependencies with `path` sources, uv previously would not build and install workspace members that did not declare a build system. Now, uv will build and install workspace members that are a dependency of _another_ workspace member regardless of the presence of a build system. The behavior is unchanged for workspace members that are not included in the `project.dependencies`, `project.optional-dependencies`, or `dependency-groups` tables of another workspace member. - + + As described above for dependencies with `path` sources, uv previously would not build and install workspace members that did not declare a build system. Now, uv will build and install workspace members that are a dependency of *another* workspace member regardless of the presence of a build system. The behavior is unchanged for workspace members that are not included in the `project.dependencies`, `project.optional-dependencies`, or `dependency-groups` tables of another workspace member. + You can opt out of this behavior by setting `tool.uv.package = false` in the workspace member's `pyproject.toml`. - + See the documentation on [virtual dependencies](https://docs.astral.sh/uv/concepts/projects/dependencies/#virtual-dependencies) for details. - - **Bump `--python-platform linux` to `manylinux_2_28` ([#14300](https://github.com/astral-sh/uv/pull/14300))** - + uv allows performing [platform-specific resolution](https://docs.astral.sh/uv/concepts/resolution/#platform-specific-resolution) for explicit targets and provides short aliases, e.g., `linux`, for common targets. - + Previously, the default target for `--python-platform linux` was `manylinux_2_17`, which is compatible with most Linux distributions from 2014 or newer. We now default to `manylinux_2_28`, which is compatible with most Linux distributions from 2019 or newer. This change follows the lead of other tools, such as `cibuildwheel`, which changed their default to `manylinux_2_28` in [Mar 2025](https://github.com/pypa/cibuildwheel/pull/2330). - + This change only affects users requesting a specific target platform. Otherwise, uv detects the `manylinux` target from your local glibc version. - + You can opt out of this behavior by using `--python-platform x86_64-manylinux_2_17` instead. - - **Remove `uv version` fallback ([#14161](https://github.com/astral-sh/uv/pull/14161))** - + In [Apr 2025](https://github.com/astral-sh/uv/pull/12349), uv changed the `uv version` command to an interface for viewing and updating the version of the current project. However, when outside a project, `uv version` would continue to display uv's version for backwards compatibility. Now, when used outside of a project, `uv version` will fail. - + You cannot opt out of this behavior. Use `uv self version` instead. - - **Require `--global` for removal of the global Python pin ([#14169](https://github.com/astral-sh/uv/pull/14169))** - + Previously, `uv python pin --rm` would allow you to remove the global Python pin without opt in. Now, uv requires the `--global` flag to remove the global Python pin. - + You cannot opt out of this behavior. Use the `--global` flag instead. - - **Support conflicting editable settings across groups ([#14197](https://github.com/astral-sh/uv/pull/14197))** - + Previously, uv would always treat a package as editable if any requirement requested it as editable. However, this prevented users from declaring `path` sources that toggled the `editable` setting across dependency groups. Now, uv allows declaring different `editable` values for conflicting groups. However, if a project includes a path dependency twice, once with `editable = true` and once without any editable annotation, those are now considered conflicting, and uv will exit with an error. - + You cannot opt out of this behavior. Use consistent `editable` settings or [mark groups as conflicting](https://docs.astral.sh/uv/concepts/projects/config/#conflicting-dependencies). - - **Make `uv_build` the default build backend in `uv init` ([#14661](https://github.com/astral-sh/uv/pull/14661))** - + The uv build backend (`uv_build`) was [stabilized in uv 0.7.19](https://github.com/astral-sh/uv/releases/tag/0.7.19). Now, it is the default build backend for `uv init --package` and `uv init --lib`. Previously, `hatchling` was the default build backend. A build backend is still not used without opt-in in `uv init`, but we expect to change this in a future release. - + You can opt out of this behavior with `uv init --build-backend hatchling`. - - **Set default `UV_TOOL_BIN_DIR` on Docker images ([#13391](https://github.com/astral-sh/uv/pull/13391))** - + Previously, `UV_TOOL_BIN_DIR` was not set in Docker images which meant that `uv tool install` did not install tools into a directory on the `PATH` without additional configuration. Now, `UV_TOOL_BIN_DIR` is set to `/usr/local/bin` in all Docker derived images. - + When the default image user is overridden (e.g. `USER `) with a less privileged user, this may cause `uv tool install` to fail. - + You can opt out of this behavior by setting an alternative `UV_TOOL_BIN_DIR`. - - **Update `--check` to return an exit code of 1 ([#14167](https://github.com/astral-sh/uv/pull/14167))** - + uv uses an exit code of 1 to indicate a "successful failure" and an exit code of 2 to indicate an "error". - + Previously, `uv lock --check` and `uv sync --check` would exit with a code of 2 when the lockfile or environment were outdated. Now, uv will exit with a code of 1. - + You cannot opt out of this behavior. - - **Use an ephemeral environment for `uv run --with` invocations ([#14447](https://github.com/astral-sh/uv/pull/14447))** - - When using `uv run --with`, uv layers the requirements requested using `--with` into another virtual environment and caches it. Previously, uv would invoke the Python interpreter in this layered environment. However, this allows poisoning the cached environment and introduces race conditions for concurrent invocations. Now, uv will layer _another_ empty virtual environment on top of the cached environment and invoke the Python interpreter there. This should only cause breakage in cases where the environment is being inspected at runtime. - + + When using `uv run --with`, uv layers the requirements requested using `--with` into another virtual environment and caches it. Previously, uv would invoke the Python interpreter in this layered environment. However, this allows poisoning the cached environment and introduces race conditions for concurrent invocations. Now, uv will layer *another* empty virtual environment on top of the cached environment and invoke the Python interpreter there. This should only cause breakage in cases where the environment is being inspected at runtime. + You cannot opt out of this behavior. - - **Restructure the `uv venv` command output and exit codes ([#14546](https://github.com/astral-sh/uv/pull/14546))** - + Previously, uv used `miette` to format the `uv venv` output. However, this was inconsistent with most of the uv CLI. Now, the output is a little different and the exit code has switched from 1 to 2 for some error cases. - + You cannot opt out of this behavior. - - **Default to `--workspace` when adding subdirectories ([#14529](https://github.com/astral-sh/uv/pull/14529))** - + When using `uv add` to add a subdirectory in a workspace, uv now defaults to adding the target as a workspace member. - + You can opt out of this behavior by providing `--no-workspace`. - - **Add missing validations for disallowed `uv.toml` fields ([#14322](https://github.com/astral-sh/uv/pull/14322))** - + uv does not allow some settings in the `uv.toml`. Previously, some settings were silently ignored when present in the `uv.toml`. Now, uv will error. - + You cannot opt out of this behavior. Use `--no-config` or remove the invalid settings. ### Configuration @@ -302,7 +324,7 @@ See the [python-build-standalone release](https://github.com/astral-sh/python-bu ### Python - Added arm64 Windows Python 3.11, 3.12, 3.13, and 3.14 - + These are not downloaded by default, since x86-64 Python has broader ecosystem support on Windows. However, they can be requested with `cpython--windows-aarch64`. @@ -782,11 +804,11 @@ This release contains various changes that improve correctness and user experien ### Breaking changes - **Update `uv version` to display and update project versions ([#12349](https://github.com/astral-sh/uv/pull/12349))** - + Previously, `uv version` displayed uv's version. Now, `uv version` will display or update the project's version. This interface was [heavily requested](https://github.com/astral-sh/uv/issues/6298) and, after much consideration, we decided that transitioning the top-level command was the best option. - + Here's a brief example: - + ```console $ uv init example Initialized project `example` at `./example` @@ -798,72 +820,72 @@ This release contains various changes that improve correctness and user experien $ uv version --short 1.0.0 ``` - + If used outside of a project, uv will fallback to showing its own version still: - + ```console $ uv version warning: failed to read project: No `pyproject.toml` found in current directory or any parent directory running `uv self version` for compatibility with old `uv version` command. this fallback will be removed soon, pass `--preview` to make this an error. - + uv 0.7.0 (4433f41c9 2025-04-29) ``` - + As described in the warning, `--preview` can be used to error instead: - + ```console $ uv version --preview error: No `pyproject.toml` found in current directory or any parent directory ``` - + The previous functionality of `uv version` was moved to `uv self version`. - **Avoid fallback to subsequent indexes on authentication failure ([#12805](https://github.com/astral-sh/uv/pull/12805))** - + When using the `first-index` strategy (the default), uv will stop searching indexes for a package once it is found on a single index. Previously, uv considered a package as "missing" from an index during authentication failures, such as an HTTP 401 or HTTP 403 (normally, missing packages are represented by an HTTP 404). This behavior was motivated by unusual responses from some package indexes, but reduces the safety of uv's index strategy when authentication fails. Now, uv will consider an authentication failure as a stop-point when searching for a package across indexes. The `index.ignore-error-codes` option can be used to recover the existing behavior, e.g.: - + ```toml [[tool.uv.index]] name = "pytorch" url = "https://download.pytorch.org/whl/cpu" ignore-error-codes = [401, 403] ``` - + Since PyTorch's indexes always return a HTTP 403 for missing packages, uv special-cases indexes on the `pytorch.org` domain to ignore that error code by default. - **Require the command in `uvx ` to be available in the Python environment ([#11603](https://github.com/astral-sh/uv/pull/11603))** - + Previously, `uvx` would attempt to execute a command even if it was not provided by a Python package. For example, if we presume `foo` is an empty Python package which provides no command, `uvx foo` would invoke the `foo` command on the `PATH` (if present). Now, uv will error early if the `foo` executable is not provided by the requested Python package. This check is not enforced when `--from` is used, so patterns like `uvx --from foo bash -c "..."` are still valid. uv also still allows `uvx foo` where the `foo` executable is provided by a dependency of `foo` instead of `foo` itself, as this is fairly common for packages which depend on a dedicated package for their command-line interface. - **Use index URL instead of package URL for keyring credential lookups ([#12651](https://github.com/astral-sh/uv/pull/12651))** - + When determining credentials for querying a package URL, uv previously sent the full URL to the `keyring` command. However, some keyring plugins expect to receive the *index URL* (which is usually a parent of the package URL). Now, uv requests credentials for the index URL instead. This behavior matches `pip`. - **Remove `--version` from subcommands ([#13108](https://github.com/astral-sh/uv/pull/13108))** - + Previously, uv allowed the `--version` flag on arbitrary subcommands, e.g., `uv run --version`. However, the `--version` flag is useful for other operations since uv is a package manager. Consequently, we've removed the `--version` flag from subcommands — it is only available as `uv --version`. - **Omit Python 3.7 downloads from managed versions ([#13022](https://github.com/astral-sh/uv/pull/13022))** - + Python 3.7 is EOL and not formally supported by uv; however, Python 3.7 was previously available for download on a subset of platforms. - **Reject non-PEP 751 TOML files in install, compile, and export commands ([#13120](https://github.com/astral-sh/uv/pull/13120), [#13119](https://github.com/astral-sh/uv/pull/13119))** - + Previously, uv treated arbitrary `.toml` files passed to commands (e.g., `uv pip install -r foo.toml` or `uv pip compile -o foo.toml`) as `requirements.txt`-formatted files. Now, uv will error instead. If using PEP 751 lockfiles, use the standardized format for custom names instead, e.g., `pylock.foo.toml`. - **Ignore arbitrary Python requests in version files ([#12909](https://github.com/astral-sh/uv/pull/12909))** - + uv allows arbitrary strings to be used for Python version requests, in which they are treated as an executable name to search for in the `PATH`. However, using this form of request in `.python-version` files is non-standard and conflicts with `pyenv-virtualenv` which writes environment names to `.python-version` files. In this release, uv will now ignore requests that are arbitrary strings when found in `.python-version` files. - **Error on unknown dependency object specifiers ([12811](https://github.com/astral-sh/uv/pull/12811))** - + The `[dependency-groups]` entries can include "object specifiers", e.g. `set-phasers-to = ...` in: - + ```toml [dependency-groups] foo = ["pyparsing"] bar = [{set-phasers-to = "stun"}] ``` - + However, the only current spec-compliant object specifier is `include-group`. Previously, uv would ignore unknown object specifiers. Now, uv will error. - **Make `--frozen` and `--no-sources` conflicting options ([#12671](https://github.com/astral-sh/uv/pull/12671))** - + Using `--no-sources` always requires a new resolution and `--frozen` will always fail when used with it. Now, this conflict is encoded in the CLI options for clarity. - **Treat empty `UV_PYTHON_INSTALL_DIR` and `UV_TOOL_DIR` as unset ([#12907](https://github.com/astral-sh/uv/pull/12907), [#12905](https://github.com/astral-sh/uv/pull/12905))** - + Previously, these variables were treated as set to the current working directory when set to an empty string. Now, uv will ignore these variables when empty. This matches uv's behavior for other environment variables which configure directories. ### Enhancements diff --git a/Cargo.lock b/Cargo.lock index 9ff891ecc..b38eafa97 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4645,7 +4645,7 @@ dependencies = [ [[package]] name = "uv" -version = "0.8.0" +version = "0.8.1" dependencies = [ "anstream", "anyhow", @@ -4811,7 +4811,7 @@ dependencies = [ [[package]] name = "uv-build" -version = "0.8.0" +version = "0.8.1" dependencies = [ "anyhow", "uv-build-backend", @@ -6005,7 +6005,7 @@ dependencies = [ [[package]] name = "uv-version" -version = "0.8.0" +version = "0.8.1" [[package]] name = "uv-virtualenv" diff --git a/crates/uv-build/Cargo.toml b/crates/uv-build/Cargo.toml index dcf61a435..d1da52fc7 100644 --- a/crates/uv-build/Cargo.toml +++ b/crates/uv-build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-build" -version = "0.8.0" +version = "0.8.1" edition.workspace = true rust-version.workspace = true homepage.workspace = true diff --git a/crates/uv-build/pyproject.toml b/crates/uv-build/pyproject.toml index 53bcbf49b..17f99f538 100644 --- a/crates/uv-build/pyproject.toml +++ b/crates/uv-build/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uv-build" -version = "0.8.0" +version = "0.8.1" description = "The uv build backend" authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }] requires-python = ">=3.8" diff --git a/crates/uv-version/Cargo.toml b/crates/uv-version/Cargo.toml index 02f940b30..db8381053 100644 --- a/crates/uv-version/Cargo.toml +++ b/crates/uv-version/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-version" -version = "0.8.0" +version = "0.8.1" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml index d160cce7b..699f98c50 100644 --- a/crates/uv/Cargo.toml +++ b/crates/uv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv" -version = "0.8.0" +version = "0.8.1" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } diff --git a/docs/concepts/build-backend.md b/docs/concepts/build-backend.md index d29420085..cdc37ea40 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.8.0,<0.9.0"] +requires = ["uv_build>=0.8.1,<0.9.0"] build-backend = "uv_build" ``` diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 5e8165824..b6d2e2efc 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.8.0/install.sh | sh + $ curl -LsSf https://astral.sh/uv/0.8.1/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.8.0/install.ps1 | iex" + PS> powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/0.8.1/install.ps1 | iex" ``` !!! tip diff --git a/docs/guides/integration/aws-lambda.md b/docs/guides/integration/aws-lambda.md index d9fc06d29..c0b82ad12 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.8.0 AS uv +FROM ghcr.io/astral-sh/uv:0.8.1 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.8.0 AS uv +FROM ghcr.io/astral-sh/uv:0.8.1 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 0eeaed62d..b707bc7cd 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.8.0` +- `ghcr.io/astral-sh/uv:{major}.{minor}.{patch}`, e.g., `ghcr.io/astral-sh/uv:0.8.1` - `ghcr.io/astral-sh/uv:{major}.{minor}`, e.g., `ghcr.io/astral-sh/uv:0.8` (the latest patch version) @@ -75,7 +75,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.8.0-alpine`. +`ghcr.io/astral-sh/uv:{major}.{minor}-{base}`, e.g., `ghcr.io/astral-sh/uv:0.8.1-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. @@ -116,7 +116,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.8.0 /uv /uvx /bin/ +COPY --from=ghcr.io/astral-sh/uv:0.8.1 /uv /uvx /bin/ ``` !!! tip @@ -134,7 +134,7 @@ COPY --from=ghcr.io/astral-sh/uv:0.8.0 /uv /uvx /bin/ Or, with the installer: ```dockerfile -ADD https://astral.sh/uv/0.8.0/install.sh /uv-installer.sh +ADD https://astral.sh/uv/0.8.1/install.sh /uv-installer.sh ``` ### Installing a project @@ -560,5 +560,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.8.0`, or (even better) the specific image digest, + version tag, e.g., `ghcr.io/astral-sh/uv:0.8.1`, 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 220db8fc8..512085b02 100644 --- a/docs/guides/integration/github.md +++ b/docs/guides/integration/github.md @@ -47,7 +47,7 @@ jobs: uses: astral-sh/setup-uv@v6 with: # Install a specific version of uv. - version: "0.8.0" + version: "0.8.1" ``` ## Setting up Python diff --git a/docs/guides/integration/pre-commit.md b/docs/guides/integration/pre-commit.md index bbc21ab45..b2a2db826 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.8.0 + rev: 0.8.1 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.8.0 + rev: 0.8.1 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.8.0 + rev: 0.8.1 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.8.0 + rev: 0.8.1 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.8.0 + rev: 0.8.1 hooks: # Compile requirements - id: pip-compile diff --git a/pyproject.toml b/pyproject.toml index 1d0a1e713..492efb7e3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "uv" -version = "0.8.0" +version = "0.8.1" 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" From 076677da20d52886494bc7d7c5b8eeaa981ce00a Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 22 Jul 2025 13:50:14 -0500 Subject: [PATCH 317/349] Avoid removing empty directories when constructing virtual environments (#14822) Closes https://github.com/astral-sh/uv/issues/14815 I tested this with the docker-compose reproduction. You can also see a regression test change at https://github.com/astral-sh/uv/pull/14822/commits/2ae4464b7e9ed57f464b0d2d6066160b26809e1f --- crates/uv-virtualenv/src/virtualenv.rs | 21 +++++++----- crates/uv/tests/it/sync.rs | 46 ++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/crates/uv-virtualenv/src/virtualenv.rs b/crates/uv-virtualenv/src/virtualenv.rs index 2227b71b9..5fa77034e 100644 --- a/crates/uv-virtualenv/src/virtualenv.rs +++ b/crates/uv-virtualenv/src/virtualenv.rs @@ -10,7 +10,7 @@ use fs_err as fs; use fs_err::File; use itertools::Itertools; use owo_colors::OwoColorize; -use tracing::debug; +use tracing::{debug, trace}; use uv_configuration::PreviewMode; use uv_fs::{CWD, Simplified, cachedir}; @@ -85,6 +85,18 @@ pub(crate) fn create( format!("File exists at `{}`", location.user_display()), ))); } + Ok(metadata) + if metadata.is_dir() + && location + .read_dir() + .is_ok_and(|mut dir| dir.next().is_none()) => + { + // If it's an empty directory, we can proceed + trace!( + "Using empty directory at `{}` for virtual environment", + location.user_display() + ); + } Ok(metadata) if metadata.is_dir() => { let name = if uv_fs::is_virtualenv_base(location) { "virtual environment" @@ -100,13 +112,6 @@ pub(crate) fn create( remove_virtualenv(location)?; fs::create_dir_all(location)?; } - OnExisting::Fail - if location - .read_dir() - .is_ok_and(|mut dir| dir.next().is_none()) => - { - debug!("Ignoring empty directory"); - } OnExisting::Fail => { match confirm_clear(location, name)? { Some(true) => { diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index 9905c8a65..a5ea6aded 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -11393,3 +11393,49 @@ fn sync_config_settings_package() -> Result<()> { Ok(()) } + +/// Ensure that when we sync to an empty virtual environment directory, we don't attempt to remove +/// it, which breaks Docker volume mounts. +#[test] +#[cfg(unix)] +fn sync_does_not_remove_empty_virtual_environment_directory() -> Result<()> { + use std::os::unix::fs::PermissionsExt; + + let context = TestContext::new_with_versions(&["3.12"]); + + let project_dir = context.temp_dir.child("project"); + fs_err::create_dir(&project_dir)?; + + let pyproject_toml = project_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["iniconfig"] + "#, + )?; + + let venv_dir = project_dir.child(".venv"); + fs_err::create_dir(&venv_dir)?; + + // Ensure the parent is read-only, to prevent deletion of the virtual environment + fs_err::set_permissions(&project_dir, std::fs::Permissions::from_mode(0o555))?; + + // Note we do _not_ fail to create the virtual environment — we fail later when writing to the + // project directory + uv_snapshot!(context.filters(), context.sync().current_dir(&project_dir), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + Creating virtual environment at: .venv + Resolved 2 packages in [TIME] + error: failed to write to file `[TEMP_DIR]/project/uv.lock`: Permission denied (os error 13) + "); + + Ok(()) +} From 3d1fec2732b25d75a3309466d03e4faf18d16a84 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 22 Jul 2025 15:08:33 -0400 Subject: [PATCH 318/349] Add derivation chains for dependency errors (#14824) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary This PR adds derivation chain for another class of resolver failures. For example, if we encounter a transitive URL dependency, we now tell the user which package included it, and the full derivation chain: ``` × Failed to resolve dependencies for `foo` (v0.1.0) ╰─▶ Package `flask` was included as a URL dependency. URL dependencies must be expressed as direct requirements or constraints. Consider adding `flask @ https://files.pythonhosted.org/packages/3d/68/9d4508e893976286d2ead7f8f571314af6c2037af34853a30fd769c02e9d/flask-3.1.1-py3-none-any.whl` to your dependencies or constraints file. help: `foo` (v0.1.0) was included because `baz` (v0.1.0) depends on `foo` ``` Closes #14795. --- crates/uv-resolver/src/error.rs | 14 ++++- crates/uv-resolver/src/resolver/mod.rs | 72 +++++++++++++++++-------- crates/uv-resolver/src/resolver/urls.rs | 8 +-- crates/uv/src/commands/diagnostics.rs | 57 ++++++++++++++++++++ crates/uv/tests/it/branching_urls.rs | 40 +++++++------- crates/uv/tests/it/lock.rs | 22 ++++---- crates/uv/tests/it/pip_compile.rs | 13 ++--- 7 files changed, 164 insertions(+), 62 deletions(-) diff --git a/crates/uv-resolver/src/error.rs b/crates/uv-resolver/src/error.rs index 0916f54ac..0cc1f6847 100644 --- a/crates/uv-resolver/src/error.rs +++ b/crates/uv-resolver/src/error.rs @@ -37,6 +37,14 @@ use crate::{InMemoryIndex, Options}; #[derive(Debug, thiserror::Error)] pub enum ResolveError { + #[error("Failed to resolve dependencies for package `{1}=={2}`")] + Dependencies( + #[source] Box, + PackageName, + Version, + DerivationChain, + ), + #[error(transparent)] Client(#[from] uv_client::Error), @@ -92,9 +100,11 @@ pub enum ResolveError { ConflictingIndexes(PackageName, String, String), #[error( - "Package `{0}` attempted to resolve via URL: {1}. URL dependencies must be expressed as direct requirements or constraints. Consider adding `{0} @ {1}` to your dependencies or constraints file." + "Package `{name}` was included as a URL dependency. URL dependencies must be expressed as direct requirements or constraints. Consider adding `{requirement}` to your dependencies or constraints file.", + name = name.cyan(), + requirement = format!("{name} @ {url}").cyan(), )] - DisallowedUrl(PackageName, String), + DisallowedUrl { name: PackageName, url: String }, #[error(transparent)] DistributionType(#[from] uv_distribution_types::Error), diff --git a/crates/uv-resolver/src/resolver/mod.rs b/crates/uv-resolver/src/resolver/mod.rs index c30c4e947..fb4092099 100644 --- a/crates/uv-resolver/src/resolver/mod.rs +++ b/crates/uv-resolver/src/resolver/mod.rs @@ -635,19 +635,26 @@ impl ResolverState { // Enrich the state with any URLs, etc. - state.visit_package_version_dependencies( - next_id, - &version, - &self.urls, - &self.indexes, - &dependencies, - &self.git, - &self.workspace_members, - self.selector.resolution_strategy(), - )?; + state + .visit_package_version_dependencies( + next_id, + &version, + &self.urls, + &self.indexes, + &dependencies, + &self.git, + &self.workspace_members, + self.selector.resolution_strategy(), + ) + .map_err(|err| { + enrich_dependency_error(err, next_id, &version, &state.pubgrub) + })?; // Emit a request to fetch the metadata for each registry package. - self.visit_dependencies(&dependencies, &state, &request_sink)?; + self.visit_dependencies(&dependencies, &state, &request_sink) + .map_err(|err| { + enrich_dependency_error(err, next_id, &version, &state.pubgrub) + })?; // Add the dependencies to the state. state.add_package_version_dependencies(next_id, &version, dependencies); @@ -870,19 +877,26 @@ impl ResolverState, } +/// Enrich a [`ResolveError`] with additional information about why a given package was included. +fn enrich_dependency_error( + error: ResolveError, + id: Id, + version: &Version, + pubgrub: &State, +) -> ResolveError { + let Some(name) = pubgrub.package_store[id].name_no_root() else { + return error; + }; + let chain = DerivationChainBuilder::from_state(id, version, pubgrub).unwrap_or_default(); + ResolveError::Dependencies(Box::new(error), name.clone(), version.clone(), chain) +} + /// Compute the set of markers for which a package is known to be relevant. fn find_environments(id: Id, state: &State) -> MarkerTree { let package = &state.package_store[id]; diff --git a/crates/uv-resolver/src/resolver/urls.rs b/crates/uv-resolver/src/resolver/urls.rs index 57803ed0b..eca87ef05 100644 --- a/crates/uv-resolver/src/resolver/urls.rs +++ b/crates/uv-resolver/src/resolver/urls.rs @@ -155,10 +155,10 @@ impl Urls { parsed_url: &'a ParsedUrl, ) -> Result<&'a VerbatimParsedUrl, ResolveError> { let Some(expected) = self.get_regular(package_name) else { - return Err(ResolveError::DisallowedUrl( - package_name.clone(), - verbatim_url.to_string(), - )); + return Err(ResolveError::DisallowedUrl { + name: package_name.clone(), + url: verbatim_url.to_string(), + }); }; let matching_urls: Vec<_> = expected diff --git a/crates/uv/src/commands/diagnostics.rs b/crates/uv/src/commands/diagnostics.rs index f24aa3406..2ee04220a 100644 --- a/crates/uv/src/commands/diagnostics.rs +++ b/crates/uv/src/commands/diagnostics.rs @@ -92,6 +92,15 @@ impl OperationDiagnostic { requested_dist_error(kind, dist, &chain, err, self.hint); None } + pip::operations::Error::Resolve(uv_resolver::ResolveError::Dependencies( + error, + name, + version, + chain, + )) => { + dependencies_error(error, &name, &version, &chain, self.hint.clone()); + None + } pip::operations::Error::Requirements(uv_requirements::Error::Dist(kind, dist, err)) => { dist_error( kind, @@ -232,6 +241,54 @@ pub(crate) fn requested_dist_error( anstream::eprint!("{report:?}"); } +/// Render an error in fetching a package's dependencies. +pub(crate) fn dependencies_error( + error: Box, + name: &PackageName, + version: &Version, + chain: &DerivationChain, + help: Option, +) { + #[derive(Debug, miette::Diagnostic, thiserror::Error)] + #[error("Failed to resolve dependencies for `{}` ({})", name.cyan(), format!("v{version}").cyan())] + #[diagnostic()] + struct Diagnostic { + name: PackageName, + version: Version, + #[source] + cause: Box, + #[help] + help: Option, + } + + let help = help.or_else(|| { + SUGGESTIONS + .get(name) + .map(|suggestion| { + format!( + "`{}` is often confused for `{}` Did you mean to install `{}` instead?", + name.cyan(), + suggestion.cyan(), + suggestion.cyan(), + ) + }) + .or_else(|| { + if chain.is_empty() { + None + } else { + Some(format_chain(name, Some(version), chain)) + } + }) + }); + let report = miette::Report::new(Diagnostic { + name: name.clone(), + version: version.clone(), + cause: error, + help, + }); + anstream::eprint!("{report:?}"); +} + /// Render a [`uv_resolver::NoSolutionError`]. pub(crate) fn no_solution(err: &uv_resolver::NoSolutionError) { let report = miette::Report::msg(format!("{err}")).context(err.header()); diff --git a/crates/uv/tests/it/branching_urls.rs b/crates/uv/tests/it/branching_urls.rs index a02ec0de3..aa6edd090 100644 --- a/crates/uv/tests/it/branching_urls.rs +++ b/crates/uv/tests/it/branching_urls.rs @@ -61,16 +61,17 @@ fn branching_urls_overlapping() -> Result<()> { "# }; make_project(context.temp_dir.path(), "a", deps)?; - uv_snapshot!(context.filters(), context.lock().current_dir(&context.temp_dir), @r###" + uv_snapshot!(context.filters(), context.lock().current_dir(&context.temp_dir), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- - error: Requirements contain conflicting URLs for package `iniconfig` in split `python_full_version == '3.11.*'`: - - https://files.pythonhosted.org/packages/9b/dd/b3c12c6d707058fa947864b67f0c4e0c39ef8610988d7baea9578f3c48f3/iniconfig-1.1.1-py2.py3-none-any.whl - - https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl - "### + × Failed to resolve dependencies for `a` (v0.1.0) + ╰─▶ Requirements contain conflicting URLs for package `iniconfig` in split `python_full_version == '3.11.*'`: + - https://files.pythonhosted.org/packages/9b/dd/b3c12c6d707058fa947864b67f0c4e0c39ef8610988d7baea9578f3c48f3/iniconfig-1.1.1-py2.py3-none-any.whl + - https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl + " ); Ok(()) @@ -128,16 +129,18 @@ fn root_package_splits_but_transitive_conflict() -> Result<()> { "# }; make_project(&context.temp_dir.path().join("b2"), "b2", deps)?; - uv_snapshot!(context.filters(), context.lock().current_dir(&context.temp_dir), @r###" + uv_snapshot!(context.filters(), context.lock().current_dir(&context.temp_dir), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- - error: Requirements contain conflicting URLs for package `iniconfig` in split `python_full_version >= '3.12'`: - - https://files.pythonhosted.org/packages/9b/dd/b3c12c6d707058fa947864b67f0c4e0c39ef8610988d7baea9578f3c48f3/iniconfig-1.1.1-py2.py3-none-any.whl - - https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl - "### + × Failed to resolve dependencies for `b2` (v0.1.0) + ╰─▶ Requirements contain conflicting URLs for package `iniconfig` in split `python_full_version >= '3.12'`: + - https://files.pythonhosted.org/packages/9b/dd/b3c12c6d707058fa947864b67f0c4e0c39ef8610988d7baea9578f3c48f3/iniconfig-1.1.1-py2.py3-none-any.whl + - https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl + help: `b2` (v0.1.0) was included because `a` (v0.1.0) depends on `b` (v0.1.0) which depends on `b2` + " ); Ok(()) @@ -727,16 +730,17 @@ fn branching_urls_of_different_sources_conflict() -> Result<()> { "# }; make_project(context.temp_dir.path(), "a", deps)?; - uv_snapshot!(context.filters(), context.lock().current_dir(&context.temp_dir), @r###" + uv_snapshot!(context.filters(), context.lock().current_dir(&context.temp_dir), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- - error: Requirements contain conflicting URLs for package `iniconfig` in split `python_full_version == '3.11.*'`: - - git+https://github.com/pytest-dev/iniconfig@93f5930e668c0d1ddf4597e38dd0dea4e2665e7a - - https://files.pythonhosted.org/packages/9b/dd/b3c12c6d707058fa947864b67f0c4e0c39ef8610988d7baea9578f3c48f3/iniconfig-1.1.1-py2.py3-none-any.whl - "### + × Failed to resolve dependencies for `a` (v0.1.0) + ╰─▶ Requirements contain conflicting URLs for package `iniconfig` in split `python_full_version == '3.11.*'`: + - git+https://github.com/pytest-dev/iniconfig@93f5930e668c0d1ddf4597e38dd0dea4e2665e7a + - https://files.pythonhosted.org/packages/9b/dd/b3c12c6d707058fa947864b67f0c4e0c39ef8610988d7baea9578f3c48f3/iniconfig-1.1.1-py2.py3-none-any.whl + " ); Ok(()) diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index f4ccb7bf7..cdc246e9f 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -11088,13 +11088,14 @@ fn lock_editable() -> Result<()> { uv_snapshot!(context.filters(), context.lock(), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- - error: Requirements contain conflicting URLs for package `library` in all marker environments: - - file://[TEMP_DIR]/library - - file://[TEMP_DIR]/library (editable) + × Failed to resolve dependencies for `workspace` (v0.1.0) + ╰─▶ Requirements contain conflicting URLs for package `library` in all marker environments: + - file://[TEMP_DIR]/library + - file://[TEMP_DIR]/library (editable) "); Ok(()) @@ -20728,16 +20729,17 @@ fn lock_multiple_sources_index_overlapping_extras() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.lock(), @r###" + uv_snapshot!(context.filters(), context.lock(), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- - error: Requirements contain conflicting indexes for package `jinja2` in all marker environments: - - https://astral-sh.github.io/pytorch-mirror/whl/cu118 - - https://astral-sh.github.io/pytorch-mirror/whl/cu124 - "###); + × Failed to resolve dependencies for `project` (v0.1.0) + ╰─▶ Requirements contain conflicting indexes for package `jinja2` in all marker environments: + - https://astral-sh.github.io/pytorch-mirror/whl/cu118 + - https://astral-sh.github.io/pytorch-mirror/whl/cu124 + "); Ok(()) } diff --git a/crates/uv/tests/it/pip_compile.rs b/crates/uv/tests/it/pip_compile.rs index 3a4dc28c4..8874949c2 100644 --- a/crates/uv/tests/it/pip_compile.rs +++ b/crates/uv/tests/it/pip_compile.rs @@ -14860,16 +14860,17 @@ fn universal_conflicting_override_urls() -> Result<()> { .arg("requirements.in") .arg("--overrides") .arg("overrides.txt") - .arg("--universal"), @r###" + .arg("--universal"), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- - error: Requirements contain conflicting URLs for package `sniffio` in split `sys_platform == 'win32'`: - - https://files.pythonhosted.org/packages/c3/a0/5dba8ed157b0136607c7f2151db695885606968d1fae123dc3391e0cfdbf/sniffio-1.3.0-py3-none-any.whl - - https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl - "### + × Failed to resolve dependencies for `anyio` (v4.3.0) + ╰─▶ Requirements contain conflicting URLs for package `sniffio` in split `sys_platform == 'win32'`: + - https://files.pythonhosted.org/packages/c3/a0/5dba8ed157b0136607c7f2151db695885606968d1fae123dc3391e0cfdbf/sniffio-1.3.0-py3-none-any.whl + - https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl + " ); Ok(()) From 27ade0676ff88a3920a3965a921bbaa1e5ee82fc Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 22 Jul 2025 15:09:59 -0400 Subject: [PATCH 319/349] Preserve index URL priority order when writing to pyproject.toml (#14831) ## Summary A little nuanced, but... When you add multiple `--index` URLs on the CLI (e.g., in `uv pip install`), we check the first-provided index, then the second index, etc. However, when we _write_ those URLs to the `pyproject.toml` in `uv add`, we were adding them in reverse-order. We now add them in a way that preserves the priority order. Closes https://github.com/astral-sh/uv/issues/14817. --- crates/uv-workspace/src/pyproject_mut.rs | 7 +- crates/uv/src/commands/project/add.rs | 4 +- crates/uv/tests/it/edit.rs | 115 ++++++++++++++++++++++- crates/uv/tests/it/lock.rs | 33 +++++-- 4 files changed, 146 insertions(+), 13 deletions(-) diff --git a/crates/uv-workspace/src/pyproject_mut.rs b/crates/uv-workspace/src/pyproject_mut.rs index 73e3833ae..85c36d03d 100644 --- a/crates/uv-workspace/src/pyproject_mut.rs +++ b/crates/uv-workspace/src/pyproject_mut.rs @@ -392,6 +392,7 @@ impl PyProjectTomlMut { /// Add an [`Index`] to `tool.uv.index`. pub fn add_index(&mut self, index: &Index) -> Result<(), Error> { + let size = self.doc.len(); let existing = self .doc .entry("tool") @@ -472,8 +473,7 @@ impl PyProjectTomlMut { if table .get("url") .and_then(|item| item.as_str()) - .and_then(|url| DisplaySafeUrl::parse(url).ok()) - .is_none_or(|url| CanonicalUrl::new(&url) != CanonicalUrl::new(index.url.url())) + .is_none_or(|url| url != index.url.without_credentials().as_str()) { let mut formatted = Formatted::new(index.url.without_credentials().to_string()); if let Some(value) = table.get("url").and_then(Item::as_value) { @@ -552,6 +552,9 @@ impl PyProjectTomlMut { table.set_position(position + 1); } } + } else { + let position = isize::try_from(size).expect("TOML table size fits in `isize`"); + table.set_position(position); } // Push the item to the table. diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index 4bf5905d2..64419bb02 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -644,7 +644,9 @@ pub(crate) async fn add( // Add any indexes that were provided on the command-line, in priority order. if !raw { let urls = IndexUrls::from_indexes(indexes); - for index in urls.defined_indexes() { + let mut indexes = urls.defined_indexes().collect::>(); + indexes.reverse(); + for index in indexes { toml.add_index(index)?; } } diff --git a/crates/uv/tests/it/edit.rs b/crates/uv/tests/it/edit.rs index a7d11091b..05527b139 100644 --- a/crates/uv/tests/it/edit.rs +++ b/crates/uv/tests/it/edit.rs @@ -11307,6 +11307,115 @@ fn remove_all_with_comments() -> Result<()> { Ok(()) } +/// If multiple indexes are provided on the CLI, the first-provided index should take precedence +/// during resolution, and should appear first in the `pyproject.toml` file. +/// +/// See: +#[test] +fn multiple_index_cli() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + "#})?; + + uv_snapshot!(context.filters(), context + .add() + .arg("requests") + .arg("--index") + .arg("https://test.pypi.org/simple") + .arg("--index") + .arg("https://pypi.org/simple"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + requests==2.5.4.1 + "); + + let pyproject_toml = context.read("pyproject.toml"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + pyproject_toml, @r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [ + "requests>=2.5.4.1", + ] + + [[tool.uv.index]] + url = "https://test.pypi.org/simple" + + [[tool.uv.index]] + url = "https://pypi.org/simple" + "# + ); + }); + + let lock = context.read("uv.lock"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + lock, @r#" + version = 1 + revision = 2 + requires-python = ">=3.12" + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [[package]] + name = "project" + version = "0.1.0" + source = { virtual = "." } + dependencies = [ + { name = "requests" }, + ] + + [package.metadata] + requires-dist = [{ name = "requests", specifier = ">=2.5.4.1" }] + + [[package]] + name = "requests" + version = "2.5.4.1" + source = { registry = "https://test.pypi.org/simple" } + sdist = { url = "https://test-files.pythonhosted.org/packages/6e/93/638dbb5f2c1f4120edaad4f3d45ffb1718e463733ad07d68f59e042901d6/requests-2.5.4.1.tar.gz", hash = "sha256:b19df51fa3e52a2bd7fc80a1ac11fb6b2f51a7c0bf31ba9ff6b5d11ea8605ae9", size = 448691, upload-time = "2015-03-13T21:30:03.228Z" } + wheels = [ + { url = "https://test-files.pythonhosted.org/packages/6d/00/8ed1b6ea43b10bfe28d08e6af29fd6aa5d8dab5e45ead9394a6268a2d2ec/requests-2.5.4.1-py2.py3-none-any.whl", hash = "sha256:0a2c98e46121e7507afb0edc89d342641a1fb9e8d56f7d592d4975ee6b685f9a", size = 468942, upload-time = "2015-03-13T21:29:55.769Z" }, + ] + "# + ); + }); + + // Install from the lockfile. + uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Audited 1 package in [TIME] + "###); + + Ok(()) +} + /// If an index is repeated by the CLI and an environment variable, the CLI value should take /// precedence. /// @@ -11418,7 +11527,7 @@ fn repeated_index_cli_environment_variable() -> Result<()> { Ok(()) } -/// If an index is repeated on the CLI, the last-provided index should take precedence. +/// If an index is repeated on the CLI, the first-provided index should take precedence. /// Newlines in `UV_INDEX` should be treated as separators. /// /// The index that appears in the `pyproject.toml` should also be consistent with the index that @@ -11524,7 +11633,7 @@ fn repeated_index_cli_environment_variable_newline() -> Result<()> { Ok(()) } -/// If an index is repeated on the CLI, the last-provided index should take precedence. +/// If an index is repeated on the CLI, the first-provided index should take precedence. /// /// The index that appears in the `pyproject.toml` should also be consistent with the index that /// appears in the `uv.lock`. @@ -11634,7 +11743,7 @@ fn repeated_index_cli() -> Result<()> { Ok(()) } -/// If an index is repeated on the CLI, the last-provided index should take precedence. +/// If an index is repeated on the CLI, the first-provided index should take precedence. /// /// The index that appears in the `pyproject.toml` should also be consistent with the index that /// appears in the `uv.lock`. diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index cdc246e9f..8a006ffb6 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -28813,8 +28813,7 @@ fn lock_trailing_slash_index_url_in_pyproject_not_index_argument() -> Result<()> let context = TestContext::new("3.12"); let pyproject_toml = context.temp_dir.child("pyproject.toml"); - pyproject_toml.write_str( - r#" + pyproject_toml.write_str(indoc! {r#" [project] name = "project" version = "0.1.0" @@ -28824,8 +28823,7 @@ fn lock_trailing_slash_index_url_in_pyproject_not_index_argument() -> Result<()> [[tool.uv.index]] name = "pypi-proxy" url = "https://pypi-proxy.fly.dev/simple/" - "#, - )?; + "#})?; let no_trailing_slash_url = "https://pypi-proxy.fly.dev/simple"; @@ -28843,6 +28841,28 @@ fn lock_trailing_slash_index_url_in_pyproject_not_index_argument() -> Result<()> + sniffio==1.3.1 "); + let pyproject_toml = context.read("pyproject.toml"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + pyproject_toml, @r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [ + "anyio>=4.3.0", + ] + + [[tool.uv.index]] + name = "pypi-proxy" + url = "https://pypi-proxy.fly.dev/simple" + "# + ); + }); + let lock = context.read("uv.lock"); insta::with_settings!({ @@ -28904,13 +28924,12 @@ fn lock_trailing_slash_index_url_in_pyproject_not_index_argument() -> Result<()> // Re-run with `--locked`. uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" - success: false - exit_code: 1 + success: true + exit_code: 0 ----- stdout ----- ----- stderr ----- Resolved 4 packages in [TIME] - The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. "); Ok(()) From 8289e38e8f06883ee1adca4c69708dff43f16340 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 22 Jul 2025 14:10:08 -0500 Subject: [PATCH 320/349] Add `UV_INIT_BUILD_BACKEND` (#14821) Closes https://github.com/astral-sh/uv/issues/14820 --- crates/uv-cli/src/lib.rs | 2 +- crates/uv-static/src/env_vars.rs | 4 ++++ docs/reference/cli.md | 2 +- docs/reference/environment.md | 5 +++++ 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index e1084f035..b6a41f3e7 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -2891,7 +2891,7 @@ pub struct InitArgs { /// Initialize a build-backend of choice for the project. /// /// Implicitly sets `--package`. - #[arg(long, value_enum, conflicts_with_all=["script", "no_package"])] + #[arg(long, value_enum, conflicts_with_all=["script", "no_package"], env = EnvVars::UV_INIT_BUILD_BACKEND)] pub build_backend: Option, /// Invalid option name for build backend. diff --git a/crates/uv-static/src/env_vars.rs b/crates/uv-static/src/env_vars.rs index 0e99ec549..a18bc11a8 100644 --- a/crates/uv-static/src/env_vars.rs +++ b/crates/uv-static/src/env_vars.rs @@ -257,6 +257,10 @@ impl EnvVars { /// Specifies the "bin" directory for installing tool executables. pub const UV_TOOL_BIN_DIR: &'static str = "UV_TOOL_BIN_DIR"; + /// Equivalent to the `--build-backend` argument for `uv init`. Determines the default backend + /// to use when creating a new project. + pub const UV_INIT_BUILD_BACKEND: &'static str = "UV_INIT_BUILD_BACKEND"; + /// Specifies the path to the directory to use for a project virtual environment. /// /// See the [project documentation](../concepts/projects/config.md#project-environment-path) diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 409ef5911..9b27ea5cb 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -302,7 +302,7 @@ uv init [OPTIONS] [PATH]

    Disables creating extra files like README.md, the src/ tree, .python-version files, etc.

    --build-backend build-backend

    Initialize a build-backend of choice for the project.

    Implicitly sets --package.

    -

    Possible values:

    +

    May also be set with the UV_INIT_BUILD_BACKEND environment variable.

    Possible values:

    • hatch: Use hatchling as the project build backend
    • flit: Use flit-core as the project build backend
    • diff --git a/docs/reference/environment.md b/docs/reference/environment.md index a4d686192..6b93dbe7e 100644 --- a/docs/reference/environment.md +++ b/docs/reference/environment.md @@ -147,6 +147,11 @@ Provides the HTTP Basic authentication username for a named index. The `name` parameter is the name of the index. For example, given an index named `foo`, the environment variable key would be `UV_INDEX_FOO_USERNAME`. +### `UV_INIT_BUILD_BACKEND` + +Equivalent to the `--build-backend` argument for `uv init`. Determines the default backend +to use when creating a new project. + ### `UV_INSECURE_HOST` Equivalent to the `--allow-insecure-host` argument. From fe17b753b34f80d84aa76144609188774cd5aa3b Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 22 Jul 2025 14:10:15 -0500 Subject: [PATCH 321/349] Archive the 0.7.x changelog (#14819) --- CHANGELOG.md | 737 +------------------------------- changelogs/0.6.x.md | 2 + changelogs/0.7.x.md | 995 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 999 insertions(+), 735 deletions(-) create mode 100644 changelogs/0.7.x.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b1d8f16a..16b551e80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -174,742 +174,9 @@ This release also includes the stabilization of a couple `uv python install` fea - Add support for toggling Python bin and registry install options via env vars ([#14662](https://github.com/astral-sh/uv/pull/14662)) -## 0.7.22 +## 0.7.x -### Python - -- Upgrade GraalPy to 24.2.2 - -See the [GraalPy release notes](https://github.com/oracle/graalpython/releases/tag/graal-24.2.2) for more details. - -### Configuration - -- Add `UV_COMPILE_BYTECODE_TIMEOUT` environment variable ([#14369](https://github.com/astral-sh/uv/pull/14369)) -- Allow users to override index `cache-control` headers ([#14620](https://github.com/astral-sh/uv/pull/14620)) -- Add `UV_LIBC` to override libc selection in multi-libc environment ([#14646](https://github.com/astral-sh/uv/pull/14646)) - -### Bug fixes - -- Fix `--all-arches` when paired with `--only-downloads` ([#14629](https://github.com/astral-sh/uv/pull/14629)) -- Skip Windows Python interpreters that return a broken MSIX package code ([#14636](https://github.com/astral-sh/uv/pull/14636)) -- Warn on invalid `uv.toml` when provided via direct path ([#14653](https://github.com/astral-sh/uv/pull/14653)) -- Improve async signal safety in Windows exception handler ([#14619](https://github.com/astral-sh/uv/pull/14619)) - -### Documentation - -- Mention the `revision` in the lockfile versioning doc ([#14634](https://github.com/astral-sh/uv/pull/14634)) -- Move "Conflicting dependencies" to the "Resolution" page ([#14633](https://github.com/astral-sh/uv/pull/14633)) -- Rename "Dependency specifiers" section to exclude PEP 508 reference ([#14631](https://github.com/astral-sh/uv/pull/14631)) -- Suggest `uv cache clean` prior to `--reinstall` ([#14659](https://github.com/astral-sh/uv/pull/14659)) - -### Preview features - -- Make preview Python registration on Windows non-fatal ([#14614](https://github.com/astral-sh/uv/pull/14614)) -- Update preview installation of Python executables to be non-fatal ([#14612](https://github.com/astral-sh/uv/pull/14612)) -- Add `uv python update-shell` ([#14627](https://github.com/astral-sh/uv/pull/14627)) - -## 0.7.21 - -### Python - -- Restore the SQLite `fts4`, `fts5`, `rtree`, and `geopoly` extensions on macOS and Linux - -See the -[`python-build-standalone` release notes](https://github.com/astral-sh/python-build-standalone/releases/tag/20250712) -for more details. - -### Enhancements - -- Add `--python-platform` to `uv sync` ([#14320](https://github.com/astral-sh/uv/pull/14320)) -- Support pre-releases in `uv version --bump` ([#13578](https://github.com/astral-sh/uv/pull/13578)) -- Add `-w` shorthand for `--with` ([#14530](https://github.com/astral-sh/uv/pull/14530)) -- Add an exception handler on Windows to display information on crash ([#14582](https://github.com/astral-sh/uv/pull/14582)) -- Add hint when Python downloads are disabled ([#14522](https://github.com/astral-sh/uv/pull/14522)) -- Add `UV_HTTP_RETRIES` to customize retry counts ([#14544](https://github.com/astral-sh/uv/pull/14544)) -- Follow leaf symlinks matched by globs in `cache-key` ([#13438](https://github.com/astral-sh/uv/pull/13438)) -- Support parent path components (`..`) in globs in `cache-key` ([#13469](https://github.com/astral-sh/uv/pull/13469)) -- Improve `cache-key` performance ([#13469](https://github.com/astral-sh/uv/pull/13469)) - -### Preview features - -- Add `uv sync --output-format json` ([#13689](https://github.com/astral-sh/uv/pull/13689)) - -### Bug fixes - -- Do not re-resolve with a new Python version in `uv tool` if it is incompatible with `--python` ([#14606](https://github.com/astral-sh/uv/pull/14606)) - -### Documentation - -- Document how to nest dependency groups with `include-group` ([#14539](https://github.com/astral-sh/uv/pull/14539)) -- Fix repeated word in Pyodide doc ([#14554](https://github.com/astral-sh/uv/pull/14554)) -- Update CONTRIBUTING.md with instructions to format Markdown files via Docker ([#14246](https://github.com/astral-sh/uv/pull/14246)) -- Fix version number for `setup-python` ([#14533](https://github.com/astral-sh/uv/pull/14533)) - -## 0.7.20 - -### Python - -- Add Python 3.14.0b4 -- Add zstd support to Python 3.14 on Unix (it already was available on Windows) -- Add PyPy 7.3.20 (for Python 3.11.13) - -See the [PyPy](https://pypy.org/posts/2025/07/pypy-v7320-release.html) and [`python-build-standalone`](https://github.com/astral-sh/python-build-standalone/releases/tag/20250708) release notes for more details. - -### Enhancements - -- Add `--workspace` flag to `uv add` ([#14496](https://github.com/astral-sh/uv/pull/14496)) -- Add auto-detection for Intel GPUs ([#14386](https://github.com/astral-sh/uv/pull/14386)) -- Drop trailing arguments when writing shebangs ([#14519](https://github.com/astral-sh/uv/pull/14519)) -- Add debug message when skipping Python downloads ([#14509](https://github.com/astral-sh/uv/pull/14509)) -- Add support for declaring multiple modules in namespace packages ([#14460](https://github.com/astral-sh/uv/pull/14460)) - -### Bug fixes - -- Revert normalization of trailing slashes on index URLs ([#14511](https://github.com/astral-sh/uv/pull/14511)) -- Fix forced resolution with all extras in `uv version` ([#14434](https://github.com/astral-sh/uv/pull/14434)) -- Fix handling of pre-releases in preferences ([#14498](https://github.com/astral-sh/uv/pull/14498)) -- Remove transparent variants in `uv-extract` to enable retries ([#14450](https://github.com/astral-sh/uv/pull/14450)) - -### Rust API - -- Add method to get packages involved in a `NoSolutionError` ([#14457](https://github.com/astral-sh/uv/pull/14457)) -- Make `ErrorTree` for `NoSolutionError` public ([#14444](https://github.com/astral-sh/uv/pull/14444)) - -### Documentation - -- Finish incomplete sentence in pip migration guide ([#14432](https://github.com/astral-sh/uv/pull/14432)) -- Remove `cache-dependency-glob` examples for `setup-uv` ([#14493](https://github.com/astral-sh/uv/pull/14493)) -- Remove `uv pip sync` suggestion with `pyproject.toml` ([#14510](https://github.com/astral-sh/uv/pull/14510)) -- Update documentation for GitHub to use `setup-uv@v6` ([#14490](https://github.com/astral-sh/uv/pull/14490)) - -## 0.7.19 - -The **[uv build backend](https://docs.astral.sh/uv/concepts/build-backend/) is now stable**, and considered ready for production use. - -The uv build backend is a great choice for pure Python projects. It has reasonable defaults, with the goal of requiring zero configuration for most users, but provides flexible configuration to accommodate most Python project structures. It integrates tightly with uv, to improve messaging and user experience. It validates project metadata and structures, preventing common mistakes. And, finally, it's very fast — `uv sync` on a new project (from `uv init`) is 10-30x faster than with other build backends. - -To use uv as a build backend in an existing project, add `uv_build` to the `[build-system]` section in your `pyproject.toml`: - -```toml -[build-system] -requires = ["uv_build>=0.7.19,<0.8.0"] -build-backend = "uv_build" -``` - -In a future release, it will replace `hatchling` as the default in `uv init`. As before, uv will remain compatible with all standards-compliant build backends. - -### Python - -- Add PGO distributions of Python for aarch64 Linux, which are more optimized for better performance - -See the [python-build-standalone release](https://github.com/astral-sh/python-build-standalone/releases/tag/20250702) for more details. - -### Enhancements - -- Ignore Python patch version for `--universal` pip compile ([#14405](https://github.com/astral-sh/uv/pull/14405)) -- Update the tilde version specifier warning to include more context ([#14335](https://github.com/astral-sh/uv/pull/14335)) -- Clarify behavior and hint on tool install when no executables are available ([#14423](https://github.com/astral-sh/uv/pull/14423)) - -### Bug fixes - -- Make project and interpreter lock acquisition non-fatal ([#14404](https://github.com/astral-sh/uv/pull/14404)) -- Includes `sys.prefix` in cached environment keys to avoid `--with` collisions across projects ([#14403](https://github.com/astral-sh/uv/pull/14403)) - -### Documentation - -- Add a migration guide from pip to uv projects ([#12382](https://github.com/astral-sh/uv/pull/12382)) - -## 0.7.18 - -### Python - -- Added arm64 Windows Python 3.11, 3.12, 3.13, and 3.14 - - These are not downloaded by default, since x86-64 Python has broader ecosystem support on Windows. -However, they can be requested with `cpython--windows-aarch64`. - -See the [python-build-standalone release](https://github.com/astral-sh/python-build-standalone/releases/tag/20250630) for more details. - -### Enhancements - -- Keep track of retries in `ManagedPythonDownload::fetch_with_retry` ([#14378](https://github.com/astral-sh/uv/pull/14378)) -- Reuse build (virtual) environments across resolution and installation ([#14338](https://github.com/astral-sh/uv/pull/14338)) -- Improve trace message for cached Python interpreter query ([#14328](https://github.com/astral-sh/uv/pull/14328)) -- Use parsed URLs for conflicting URL error message ([#14380](https://github.com/astral-sh/uv/pull/14380)) - -### Preview features - -- Ignore invalid build backend settings when not building ([#14372](https://github.com/astral-sh/uv/pull/14372)) - -### Bug fixes - -- Fix equals-star and tilde-equals with `python_version` and `python_full_version` ([#14271](https://github.com/astral-sh/uv/pull/14271)) -- Include the canonical path in the interpreter query cache key ([#14331](https://github.com/astral-sh/uv/pull/14331)) -- Only drop build directories on program exit ([#14304](https://github.com/astral-sh/uv/pull/14304)) -- Error instead of panic on conflict between global and subcommand flags ([#14368](https://github.com/astral-sh/uv/pull/14368)) -- Consistently normalize trailing slashes on URLs with no path segments ([#14349](https://github.com/astral-sh/uv/pull/14349)) - -### Documentation - -- Add instructions for publishing to JFrog's Artifactory ([#14253](https://github.com/astral-sh/uv/pull/14253)) -- Edits to the build backend documentation ([#14376](https://github.com/astral-sh/uv/pull/14376)) - -## 0.7.17 - -### Bug fixes - -- Apply build constraints when resolving `--with` dependencies ([#14340](https://github.com/astral-sh/uv/pull/14340)) -- Drop trailing slashes when converting index URL from URL ([#14346](https://github.com/astral-sh/uv/pull/14346)) -- Ignore `UV_PYTHON_CACHE_DIR` when empty ([#14336](https://github.com/astral-sh/uv/pull/14336)) -- Fix error message ordering for `pyvenv.cfg` version conflict ([#14329](https://github.com/astral-sh/uv/pull/14329)) - -## 0.7.16 - -### Python - -- Add Python 3.14.0b3 - -See the -[`python-build-standalone` release notes](https://github.com/astral-sh/python-build-standalone/releases/tag/20250626) -for more details. - -### Enhancements - -- Include path or URL when failing to convert in lockfile ([#14292](https://github.com/astral-sh/uv/pull/14292)) -- Warn when `~=` is used as a Python version specifier without a patch version ([#14008](https://github.com/astral-sh/uv/pull/14008)) - -### Preview features - -- Ensure preview default Python installs are upgradeable ([#14261](https://github.com/astral-sh/uv/pull/14261)) - -### Performance - -- Share workspace cache between lock and sync operations ([#14321](https://github.com/astral-sh/uv/pull/14321)) - -### Bug fixes - -- Allow local indexes to reference remote files ([#14294](https://github.com/astral-sh/uv/pull/14294)) -- Avoid rendering desugared prefix matches in error messages ([#14195](https://github.com/astral-sh/uv/pull/14195)) -- Avoid using path URL for workspace Git dependencies in `requirements.txt` ([#14288](https://github.com/astral-sh/uv/pull/14288)) -- Normalize index URLs to remove trailing slash ([#14245](https://github.com/astral-sh/uv/pull/14245)) -- Respect URL-encoded credentials in redirect location ([#14315](https://github.com/astral-sh/uv/pull/14315)) -- Lock the source tree when running setuptools, to protect concurrent builds ([#14174](https://github.com/astral-sh/uv/pull/14174)) - -### Documentation - -- Note that GCP Artifact Registry download URLs must have `/simple` component ([#14251](https://github.com/astral-sh/uv/pull/14251)) - -## 0.7.15 - -### Enhancements - -- Consistently use `Ordering::Relaxed` for standalone atomic use cases ([#14190](https://github.com/astral-sh/uv/pull/14190)) -- Warn on ambiguous relative paths for `--index` ([#14152](https://github.com/astral-sh/uv/pull/14152)) -- Skip GitHub fast path when rate-limited ([#13033](https://github.com/astral-sh/uv/pull/13033)) -- Preserve newlines in `schema.json` descriptions ([#13693](https://github.com/astral-sh/uv/pull/13693)) - -### Bug fixes - -- Add check for using minor version link when creating a venv on Windows ([#14252](https://github.com/astral-sh/uv/pull/14252)) -- Strip query parameters when parsing source URL ([#14224](https://github.com/astral-sh/uv/pull/14224)) - -### Documentation - -- Add a link to PyPI FAQ to clarify what per-project token is ([#14242](https://github.com/astral-sh/uv/pull/14242)) - -### Preview features - -- Allow symlinks in the build backend ([#14212](https://github.com/astral-sh/uv/pull/14212)) - -## 0.7.14 - -### Enhancements - -- Add XPU to `--torch-backend` ([#14172](https://github.com/astral-sh/uv/pull/14172)) -- Add ROCm backends to `--torch-backend` ([#14120](https://github.com/astral-sh/uv/pull/14120)) -- Remove preview label from `--torch-backend` ([#14119](https://github.com/astral-sh/uv/pull/14119)) -- Add `[tool.uv.dependency-groups].mygroup.requires-python` ([#13735](https://github.com/astral-sh/uv/pull/13735)) -- Add auto-detection for AMD GPUs ([#14176](https://github.com/astral-sh/uv/pull/14176)) -- Show retries for HTTP status code errors ([#13897](https://github.com/astral-sh/uv/pull/13897)) -- Support transparent Python patch version upgrades ([#13954](https://github.com/astral-sh/uv/pull/13954)) -- Warn on empty index directory ([#13940](https://github.com/astral-sh/uv/pull/13940)) -- Publish to DockerHub ([#14088](https://github.com/astral-sh/uv/pull/14088)) - -### Performance - -- Make cold resolves about 10% faster ([#14035](https://github.com/astral-sh/uv/pull/14035)) - -### Bug fixes - -- Don't use walrus operator in interpreter query script ([#14108](https://github.com/astral-sh/uv/pull/14108)) -- Fix handling of changes to `requires-python` ([#14076](https://github.com/astral-sh/uv/pull/14076)) -- Fix implied `platform_machine` marker for `win_amd64` platform tag ([#14041](https://github.com/astral-sh/uv/pull/14041)) -- Only update existing symlink directories on preview uninstall ([#14179](https://github.com/astral-sh/uv/pull/14179)) -- Serialize Python requests for tools as canonicalized strings ([#14109](https://github.com/astral-sh/uv/pull/14109)) -- Support netrc and same-origin credential propagation on index redirects ([#14126](https://github.com/astral-sh/uv/pull/14126)) -- Support reading `dependency-groups` from pyproject.tomls with no `[project]` ([#13742](https://github.com/astral-sh/uv/pull/13742)) -- Handle an existing shebang in `uv init --script` ([#14141](https://github.com/astral-sh/uv/pull/14141)) -- Prevent concurrent updates of the environment in `uv run` ([#14153](https://github.com/astral-sh/uv/pull/14153)) -- Filter managed Python distributions by platform before querying when included in request ([#13936](https://github.com/astral-sh/uv/pull/13936)) - -### Documentation - -- Replace cuda124 with cuda128 ([#14168](https://github.com/astral-sh/uv/pull/14168)) -- Document the way member sources shadow workspace sources ([#14136](https://github.com/astral-sh/uv/pull/14136)) -- Sync documented PyTorch integration index for CUDA and ROCm versions from PyTorch website ([#14100](https://github.com/astral-sh/uv/pull/14100)) - -## 0.7.13 - -### Python - -- Add Python 3.14.0b2 -- Add Python 3.13.5 -- Fix stability of `uuid.getnode` on 3.13 - -See the -[`python-build-standalone` release notes](https://github.com/astral-sh/python-build-standalone/releases/tag/20250612) -for more details. - -### Enhancements - -- Download versions in `uv python pin` if not found ([#13946](https://github.com/astral-sh/uv/pull/13946)) -- Use TTY detection to determine if SIGINT forwarding is enabled ([#13925](https://github.com/astral-sh/uv/pull/13925)) -- Avoid fetching an exact, cached Git commit, even if it isn't locked ([#13748](https://github.com/astral-sh/uv/pull/13748)) -- Add `zstd` and `deflate` to `Accept-Encoding` ([#13982](https://github.com/astral-sh/uv/pull/13982)) -- Build binaries for riscv64 ([#12688](https://github.com/astral-sh/uv/pull/12688)) - -### Bug fixes - -- Check if relative URL is valid directory before treating as index ([#13917](https://github.com/astral-sh/uv/pull/13917)) -- Ignore Python discovery errors during `uv python pin` ([#13944](https://github.com/astral-sh/uv/pull/13944)) -- Do not allow `uv add --group ... --script` ([#13997](https://github.com/astral-sh/uv/pull/13997)) - -### Preview changes - -- Build backend: Support namespace packages ([#13833](https://github.com/astral-sh/uv/pull/13833)) - -### Documentation - -- Add 3.14 to the supported platform reference ([#13990](https://github.com/astral-sh/uv/pull/13990)) -- Add an `llms.txt` to uv ([#13929](https://github.com/astral-sh/uv/pull/13929)) -- Add supported macOS version to the platform reference ([#13993](https://github.com/astral-sh/uv/pull/13993)) -- Update platform support reference to include Python implementation list ([#13991](https://github.com/astral-sh/uv/pull/13991)) -- Update pytorch.md ([#13899](https://github.com/astral-sh/uv/pull/13899)) -- Update the CLI help and reference to include references to the Python bin directory ([#13978](https://github.com/astral-sh/uv/pull/13978)) - -## 0.7.12 - -### Enhancements - -- Add `uv python pin --rm` to remove `.python-version` pins ([#13860](https://github.com/astral-sh/uv/pull/13860)) -- Don't hint at versions removed by `excluded-newer` ([#13884](https://github.com/astral-sh/uv/pull/13884)) -- Add hint to use `tool.uv.environments` on resolution error ([#13455](https://github.com/astral-sh/uv/pull/13455)) -- Add hint to use `tool.uv.required-environments` on resolution error ([#13575](https://github.com/astral-sh/uv/pull/13575)) -- Improve `python pin` error messages ([#13862](https://github.com/astral-sh/uv/pull/13862)) - -### Bug fixes - -- Lock environments during `uv sync`, `uv add` and `uv remove` to prevent race conditions ([#13869](https://github.com/astral-sh/uv/pull/13869)) -- Add `--no-editable` to `uv export` for `pylock.toml` ([#13852](https://github.com/astral-sh/uv/pull/13852)) - -### Documentation - -- List `.gitignore` in project init files ([#13855](https://github.com/astral-sh/uv/pull/13855)) -- Move the pip interface documentation into the concepts section ([#13841](https://github.com/astral-sh/uv/pull/13841)) -- Remove the configuration section in favor of concepts / reference ([#13842](https://github.com/astral-sh/uv/pull/13842)) -- Update Git and GitHub Actions docs to mention `gh auth login` ([#13850](https://github.com/astral-sh/uv/pull/13850)) - -### Preview - -- Fix directory glob traversal fallback preventing exclusion of all files ([#13882](https://github.com/astral-sh/uv/pull/13882)) - -## 0.7.11 - -### Python - -- Add Python 3.14.0b1 -- Add Python 3.13.4 -- Add Python 3.12.11 -- Add Python 3.11.13 -- Add Python 3.10.18 -- Add Python 3.9.23 - -### Enhancements - -- Add Pyodide support ([#12731](https://github.com/astral-sh/uv/pull/12731)) -- Better error message for version specifier with missing operator ([#13803](https://github.com/astral-sh/uv/pull/13803)) - -### Bug fixes - -- Downgrade `reqwest` and `hyper-util` to resolve connection reset errors over IPv6 ([#13835](https://github.com/astral-sh/uv/pull/13835)) -- Prefer `uv`'s binary's version when checking if it's up to date ([#13840](https://github.com/astral-sh/uv/pull/13840)) - -### Documentation - -- Use "terminal driver" instead of "shell" in `SIGINT` docs ([#13787](https://github.com/astral-sh/uv/pull/13787)) - -## 0.7.10 - -### Enhancements - -- Add `--show-extras` to `uv tool list` ([#13783](https://github.com/astral-sh/uv/pull/13783)) -- Add dynamically generated sysconfig replacement mappings ([#13441](https://github.com/astral-sh/uv/pull/13441)) -- Add data locations to install wheel logs ([#13797](https://github.com/astral-sh/uv/pull/13797)) - -### Bug fixes - -- Avoid redaction of placeholder `git` username when using SSH authentication ([#13799](https://github.com/astral-sh/uv/pull/13799)) -- Propagate credentials to files on devpi indexes ending in `/+simple` ([#13743](https://github.com/astral-sh/uv/pull/13743)) -- Restore retention of credentials for direct URLs in `uv export` ([#13809](https://github.com/astral-sh/uv/pull/13809)) - -## 0.7.9 - -### Python - -The changes reverted in [0.7.8](#078) have been restored. - -See the -[`python-build-standalone` release notes](https://github.com/astral-sh/python-build-standalone/releases/tag/20250529) -for more details. - -### Enhancements - -- Improve obfuscation of credentials in URLs ([#13560](https://github.com/astral-sh/uv/pull/13560)) -- Allow running non-default Python implementations via `uvx` ([#13583](https://github.com/astral-sh/uv/pull/13583)) -- Add `uvw` as alias for `uv` without console window on Windows ([#11786](https://github.com/astral-sh/uv/pull/11786)) -- Allow discovery of x86-64 managed Python builds on macOS ([#13722](https://github.com/astral-sh/uv/pull/13722)) -- Differentiate between implicit vs explicit architecture requests ([#13723](https://github.com/astral-sh/uv/pull/13723)) -- Implement ordering for Python architectures to prefer native installations ([#13709](https://github.com/astral-sh/uv/pull/13709)) -- Only show the first match per platform (and architecture) by default in `uv python list` ([#13721](https://github.com/astral-sh/uv/pull/13721)) -- Write the path of the parent environment to an `extends-environment` key in the `pyvenv.cfg` file of an ephemeral environment ([#13598](https://github.com/astral-sh/uv/pull/13598)) -- Improve the error message when libc cannot be found, e.g., when using the distroless containers ([#13549](https://github.com/astral-sh/uv/pull/13549)) - -### Performance - -- Avoid rendering info log level ([#13642](https://github.com/astral-sh/uv/pull/13642)) -- Improve performance of `uv-python` crate's manylinux submodule ([#11131](https://github.com/astral-sh/uv/pull/11131)) -- Optimize `Version` display ([#13643](https://github.com/astral-sh/uv/pull/13643)) -- Reduce number of reference-checks for `uv cache clean` ([#13669](https://github.com/astral-sh/uv/pull/13669)) - -### Bug fixes - -- Avoid reinstalling dependency group members with `--all-packages` ([#13678](https://github.com/astral-sh/uv/pull/13678)) -- Don't fail direct URL hash checking with dependency metadata ([#13736](https://github.com/astral-sh/uv/pull/13736)) -- Exit early on `self update` if global `--offline` is set ([#13663](https://github.com/astral-sh/uv/pull/13663)) -- Fix cases where the uv lock is incorrectly marked as out of date ([#13635](https://github.com/astral-sh/uv/pull/13635)) -- Include pre-release versions in `uv python install --reinstall` ([#13645](https://github.com/astral-sh/uv/pull/13645)) -- Set `LC_ALL=C` for git when checking git worktree ([#13637](https://github.com/astral-sh/uv/pull/13637)) -- Avoid rejecting Windows paths for remote Python download JSON targets ([#13625](https://github.com/astral-sh/uv/pull/13625)) - -### Preview - -- Add `uv add --bounds` to configure version constraints ([#12946](https://github.com/astral-sh/uv/pull/12946)) - -### Documentation - -- Add documentation about Python versions to Tools concept page ([#7673](https://github.com/astral-sh/uv/pull/7673)) -- Add example of enabling Dependabot ([#13692](https://github.com/astral-sh/uv/pull/13692)) -- Fix `exclude-newer` date format for persistent configuration files ([#13706](https://github.com/astral-sh/uv/pull/13706)) -- Quote versions variables in GitLab documentation ([#13679](https://github.com/astral-sh/uv/pull/13679)) -- Update Dependabot support status ([#13690](https://github.com/astral-sh/uv/pull/13690)) -- Explicitly specify to add a new repo entry to the repos list item in the `.pre-commit-config.yaml` ([#10243](https://github.com/astral-sh/uv/pull/10243)) -- Add integration with marimo guide ([#13691](https://github.com/astral-sh/uv/pull/13691)) -- Add pronunciation to README ([#5336](https://github.com/astral-sh/uv/pull/5336)) - -## 0.7.8 - -### Python - -We are reverting most of our Python changes from `uv 0.7.6` and `uv 0.7.7` due to -a miscompilation that makes the Python interpreter behave incorrectly, resulting -in spurious type-errors involving str. This issue seems to be isolated to -x86_64 Linux, and affected at least Python 3.12, 3.13, and 3.14. - -The following changes that were introduced in those versions of uv are temporarily -being reverted while we test and deploy a proper fix for the miscompilation: - -- Add Python 3.14 on musl -- free-threaded Python on musl -- Add Python 3.14.0a7 -- Statically link `libpython` into the interpreter on Linux for a significant performance boost - -See [the issue for details](https://github.com/astral-sh/uv/issues/13610). - -### Documentation - -- Remove misleading line in pin documentation ([#13611](https://github.com/astral-sh/uv/pull/13611)) - -## 0.7.7 - -### Python - -- Work around third-party packages that (incorrectly) assume the interpreter is dynamically linking libpython -- Allow the experimental JIT to be enabled at runtime on Python 3.13 and 3.14 on macOS on aarch64 aka Apple Silicon - -See the -[`python-build-standalone` release notes](https://github.com/astral-sh/python-build-standalone/releases/tag/20250521) -for more details. - -### Bug fixes - -- Make `uv version` lock and sync ([#13317](https://github.com/astral-sh/uv/pull/13317)) -- Fix references to `ldd` in diagnostics to correctly refer to `ld.so` ([#13552](https://github.com/astral-sh/uv/pull/13552)) - -### Documentation - -- Clarify adding SSH Git dependencies ([#13534](https://github.com/astral-sh/uv/pull/13534)) - -## 0.7.6 - -### Python - -- Add Python 3.14 on musl -- Add free-threaded Python on musl -- Add Python 3.14.0a7 -- Statically link `libpython` into the interpreter on Linux for a significant performance boost - -See the -[`python-build-standalone` release notes](https://github.com/astral-sh/python-build-standalone/releases/tag/20250517) -for more details. - -### Enhancements - -- Improve compatibility of `VIRTUAL_ENV_PROMPT` value ([#13501](https://github.com/astral-sh/uv/pull/13501)) -- Bump MSRV to 1.85 and Edition 2024 ([#13516](https://github.com/astral-sh/uv/pull/13516)) - -### Bug fixes - -- Respect default extras in uv remove ([#13380](https://github.com/astral-sh/uv/pull/13380)) - -### Documentation - -- Fix PowerShell code blocks ([#13511](https://github.com/astral-sh/uv/pull/13511)) - -## 0.7.5 - -### Bug fixes - -- Support case-sensitive module discovery in the build backend ([#13468](https://github.com/astral-sh/uv/pull/13468)) -- Bump Simple cache bucket to v16 ([#13498](https://github.com/astral-sh/uv/pull/13498)) -- Don't error when the script is too short for the buffer ([#13488](https://github.com/astral-sh/uv/pull/13488)) -- Add missing word in "script not supported" error ([#13483](https://github.com/astral-sh/uv/pull/13483)) - -## 0.7.4 - -### Enhancements - -- Add more context to external errors ([#13351](https://github.com/astral-sh/uv/pull/13351)) -- Align indentation of long arguments ([#13394](https://github.com/astral-sh/uv/pull/13394)) -- Preserve order of dependencies which are sorted naively ([#13334](https://github.com/astral-sh/uv/pull/13334)) -- Align progress bars by largest name length ([#13266](https://github.com/astral-sh/uv/pull/13266)) -- Reinstall local packages in `uv add` ([#13462](https://github.com/astral-sh/uv/pull/13462)) -- Rename `--raw-sources` to `--raw` ([#13348](https://github.com/astral-sh/uv/pull/13348)) -- Show 'Downgraded' when `self update` is used to install an older version ([#13340](https://github.com/astral-sh/uv/pull/13340)) -- Suggest `uv self update` if required uv version is newer ([#13305](https://github.com/astral-sh/uv/pull/13305)) -- Add 3.14 beta images to uv Docker images ([#13390](https://github.com/astral-sh/uv/pull/13390)) -- Add comma after "i.e." in Conda environment error ([#13423](https://github.com/astral-sh/uv/pull/13423)) -- Be more precise in unpinned packages warning ([#13426](https://github.com/astral-sh/uv/pull/13426)) -- Fix detection of sorted dependencies when include-group is used ([#13354](https://github.com/astral-sh/uv/pull/13354)) -- Fix display of HTTP responses in trace logs for retry of errors ([#13339](https://github.com/astral-sh/uv/pull/13339)) -- Log skip reasons during Python installation key interpreter match checks ([#13472](https://github.com/astral-sh/uv/pull/13472)) -- Redact credentials when displaying URLs ([#13333](https://github.com/astral-sh/uv/pull/13333)) - -### Bug fixes - -- Avoid erroring on `pylock.toml` dependency entries ([#13384](https://github.com/astral-sh/uv/pull/13384)) -- Avoid panics for cannot-be-a-base URLs ([#13406](https://github.com/astral-sh/uv/pull/13406)) -- Ensure cached realm credentials are applied if no password is found for index URL ([#13463](https://github.com/astral-sh/uv/pull/13463)) -- Fix `.tgz` parsing to respect true extension ([#13382](https://github.com/astral-sh/uv/pull/13382)) -- Fix double self-dependency ([#13366](https://github.com/astral-sh/uv/pull/13366)) -- Reject `pylock.toml` in `uv add -r` ([#13421](https://github.com/astral-sh/uv/pull/13421)) -- Retain dot-separated wheel tags during cache prune ([#13379](https://github.com/astral-sh/uv/pull/13379)) -- Retain trailing comments after PEP 723 metadata block ([#13460](https://github.com/astral-sh/uv/pull/13460)) - -### Documentation - -- Use "export" instead of "install" in `uv export` arguments ([#13430](https://github.com/astral-sh/uv/pull/13430)) -- Remove extra newline ([#13461](https://github.com/astral-sh/uv/pull/13461)) - -### Preview features - -- Build backend: Normalize glob paths ([#13465](https://github.com/astral-sh/uv/pull/13465)) - -## 0.7.3 - -### Enhancements - -- Add `--dry-run` support to `uv self update` ([#9829](https://github.com/astral-sh/uv/pull/9829)) -- Add `--show-with` to `uv tool list` to list packages included by `--with` ([#13264](https://github.com/astral-sh/uv/pull/13264)) -- De-duplicate fetched index URLs ([#13205](https://github.com/astral-sh/uv/pull/13205)) -- Support more zip compression formats: bzip2, lzma, xz, zstd ([#13285](https://github.com/astral-sh/uv/pull/13285)) -- Add support for downloading GraalPy ([#13172](https://github.com/astral-sh/uv/pull/13172)) -- Improve error message when a virtual environment Python symlink is broken ([#12168](https://github.com/astral-sh/uv/pull/12168)) -- Use `fs_err` for paths in symlinking errors ([#13303](https://github.com/astral-sh/uv/pull/13303)) -- Minify and embed managed Python JSON at compile time ([#12967](https://github.com/astral-sh/uv/pull/12967)) - -### Preview features - -- Build backend: Make preview default and add configuration docs ([#12804](https://github.com/astral-sh/uv/pull/12804)) -- Build backend: Allow escaping in globs ([#13313](https://github.com/astral-sh/uv/pull/13313)) -- Build backend: Make builds reproducible across operating systems ([#13171](https://github.com/astral-sh/uv/pull/13171)) - -### Configuration - -- Add `python-downloads-json-url` option for `uv.toml` to configure custom Python installations via JSON URL ([#12974](https://github.com/astral-sh/uv/pull/12974)) - -### Bug fixes - -- Check nested IO errors for retries ([#13260](https://github.com/astral-sh/uv/pull/13260)) -- Accept `musllinux_1_0` as a valid platform tag ([#13289](https://github.com/astral-sh/uv/pull/13289)) -- Fix discovery of pre-release managed Python versions in range requests ([#13330](https://github.com/astral-sh/uv/pull/13330)) -- Respect locked script preferences in `uv run --with` ([#13283](https://github.com/astral-sh/uv/pull/13283)) -- Retry streaming downloads on broken pipe errors ([#13281](https://github.com/astral-sh/uv/pull/13281)) -- Treat already-installed base environment packages as preferences in `uv run --with` ([#13284](https://github.com/astral-sh/uv/pull/13284)) -- Avoid enumerating sources in errors for path Python requests ([#13335](https://github.com/astral-sh/uv/pull/13335)) -- Avoid re-creating virtual environment with `--no-sync` ([#13287](https://github.com/astral-sh/uv/pull/13287)) - -### Documentation - -- Remove outdated description of index strategy ([#13326](https://github.com/astral-sh/uv/pull/13326)) -- Update "Viewing the version" docs ([#13241](https://github.com/astral-sh/uv/pull/13241)) - -## 0.7.2 - -### Enhancements - -- Improve trace log for retryable errors ([#13228](https://github.com/astral-sh/uv/pull/13228)) -- Use "error" instead of "warning" for self-update message ([#13229](https://github.com/astral-sh/uv/pull/13229)) -- Error when `uv version` is used with project-specific flags but no project is found ([#13203](https://github.com/astral-sh/uv/pull/13203)) - -### Bug fixes - -- Fix incorrect virtual environment invalidation for pre-release Python versions ([#13234](https://github.com/astral-sh/uv/pull/13234)) -- Fix patching of `clang` in managed Python sysconfig ([#13237](https://github.com/astral-sh/uv/pull/13237)) -- Respect `--project` in `uv version` ([#13230](https://github.com/astral-sh/uv/pull/13230)) - -## 0.7.1 - -### Enhancement - -- Add support for BLAKE2b-256 ([#13204](https://github.com/astral-sh/uv/pull/13204)) - -### Bugfix - -- Revert fix handling of authentication when encountering redirects ([#13215](https://github.com/astral-sh/uv/pull/13215)) - -## 0.7.0 - -This release contains various changes that improve correctness and user experience, but could break some workflows; many changes have been marked as breaking out of an abundance of caution. We expect most users to be able to upgrade without making changes. - -### Breaking changes - -- **Update `uv version` to display and update project versions ([#12349](https://github.com/astral-sh/uv/pull/12349))** - - Previously, `uv version` displayed uv's version. Now, `uv version` will display or update the project's version. This interface was [heavily requested](https://github.com/astral-sh/uv/issues/6298) and, after much consideration, we decided that transitioning the top-level command was the best option. - - Here's a brief example: - - ```console - $ uv init example - Initialized project `example` at `./example` - $ cd example - $ uv version - example 0.1.0 - $ uv version --bump major - example 0.1.0 => 1.0.0 - $ uv version --short - 1.0.0 - ``` - - If used outside of a project, uv will fallback to showing its own version still: - - ```console - $ uv version - warning: failed to read project: No `pyproject.toml` found in current directory or any parent directory - running `uv self version` for compatibility with old `uv version` command. - this fallback will be removed soon, pass `--preview` to make this an error. - - uv 0.7.0 (4433f41c9 2025-04-29) - ``` - - As described in the warning, `--preview` can be used to error instead: - - ```console - $ uv version --preview - error: No `pyproject.toml` found in current directory or any parent directory - ``` - - The previous functionality of `uv version` was moved to `uv self version`. -- **Avoid fallback to subsequent indexes on authentication failure ([#12805](https://github.com/astral-sh/uv/pull/12805))** - - When using the `first-index` strategy (the default), uv will stop searching indexes for a package once it is found on a single index. Previously, uv considered a package as "missing" from an index during authentication failures, such as an HTTP 401 or HTTP 403 (normally, missing packages are represented by an HTTP 404). This behavior was motivated by unusual responses from some package indexes, but reduces the safety of uv's index strategy when authentication fails. Now, uv will consider an authentication failure as a stop-point when searching for a package across indexes. The `index.ignore-error-codes` option can be used to recover the existing behavior, e.g.: - - ```toml - [[tool.uv.index]] - name = "pytorch" - url = "https://download.pytorch.org/whl/cpu" - ignore-error-codes = [401, 403] - ``` - - Since PyTorch's indexes always return a HTTP 403 for missing packages, uv special-cases indexes on the `pytorch.org` domain to ignore that error code by default. -- **Require the command in `uvx ` to be available in the Python environment ([#11603](https://github.com/astral-sh/uv/pull/11603))** - - Previously, `uvx` would attempt to execute a command even if it was not provided by a Python package. For example, if we presume `foo` is an empty Python package which provides no command, `uvx foo` would invoke the `foo` command on the `PATH` (if present). Now, uv will error early if the `foo` executable is not provided by the requested Python package. This check is not enforced when `--from` is used, so patterns like `uvx --from foo bash -c "..."` are still valid. uv also still allows `uvx foo` where the `foo` executable is provided by a dependency of `foo` instead of `foo` itself, as this is fairly common for packages which depend on a dedicated package for their command-line interface. -- **Use index URL instead of package URL for keyring credential lookups ([#12651](https://github.com/astral-sh/uv/pull/12651))** - - When determining credentials for querying a package URL, uv previously sent the full URL to the `keyring` command. However, some keyring plugins expect to receive the *index URL* (which is usually a parent of the package URL). Now, uv requests credentials for the index URL instead. This behavior matches `pip`. -- **Remove `--version` from subcommands ([#13108](https://github.com/astral-sh/uv/pull/13108))** - - Previously, uv allowed the `--version` flag on arbitrary subcommands, e.g., `uv run --version`. However, the `--version` flag is useful for other operations since uv is a package manager. Consequently, we've removed the `--version` flag from subcommands — it is only available as `uv --version`. -- **Omit Python 3.7 downloads from managed versions ([#13022](https://github.com/astral-sh/uv/pull/13022))** - - Python 3.7 is EOL and not formally supported by uv; however, Python 3.7 was previously available for download on a subset of platforms. -- **Reject non-PEP 751 TOML files in install, compile, and export commands ([#13120](https://github.com/astral-sh/uv/pull/13120), [#13119](https://github.com/astral-sh/uv/pull/13119))** - - Previously, uv treated arbitrary `.toml` files passed to commands (e.g., `uv pip install -r foo.toml` or `uv pip compile -o foo.toml`) as `requirements.txt`-formatted files. Now, uv will error instead. If using PEP 751 lockfiles, use the standardized format for custom names instead, e.g., `pylock.foo.toml`. -- **Ignore arbitrary Python requests in version files ([#12909](https://github.com/astral-sh/uv/pull/12909))** - - uv allows arbitrary strings to be used for Python version requests, in which they are treated as an executable name to search for in the `PATH`. However, using this form of request in `.python-version` files is non-standard and conflicts with `pyenv-virtualenv` which writes environment names to `.python-version` files. In this release, uv will now ignore requests that are arbitrary strings when found in `.python-version` files. -- **Error on unknown dependency object specifiers ([12811](https://github.com/astral-sh/uv/pull/12811))** - - The `[dependency-groups]` entries can include "object specifiers", e.g. `set-phasers-to = ...` in: - - ```toml - [dependency-groups] - foo = ["pyparsing"] - bar = [{set-phasers-to = "stun"}] - ``` - - However, the only current spec-compliant object specifier is `include-group`. Previously, uv would ignore unknown object specifiers. Now, uv will error. -- **Make `--frozen` and `--no-sources` conflicting options ([#12671](https://github.com/astral-sh/uv/pull/12671))** - - Using `--no-sources` always requires a new resolution and `--frozen` will always fail when used with it. Now, this conflict is encoded in the CLI options for clarity. -- **Treat empty `UV_PYTHON_INSTALL_DIR` and `UV_TOOL_DIR` as unset ([#12907](https://github.com/astral-sh/uv/pull/12907), [#12905](https://github.com/astral-sh/uv/pull/12905))** - - Previously, these variables were treated as set to the current working directory when set to an empty string. Now, uv will ignore these variables when empty. This matches uv's behavior for other environment variables which configure directories. - -### Enhancements - -- Disallow mixing requirements across PyTorch indexes ([#13179](https://github.com/astral-sh/uv/pull/13179)) -- Add optional managed Python archive download cache ([#12175](https://github.com/astral-sh/uv/pull/12175)) -- Add `poetry-core` as a `uv init` build backend option ([#12781](https://github.com/astral-sh/uv/pull/12781)) -- Show tag hints when failing to find a compatible wheel in `pylock.toml` ([#13136](https://github.com/astral-sh/uv/pull/13136)) -- Report Python versions in `pyvenv.cfg` version mismatch ([#13027](https://github.com/astral-sh/uv/pull/13027)) - -### Bug fixes - -- Avoid erroring on omitted wheel-only packages in `pylock.toml` ([#13132](https://github.com/astral-sh/uv/pull/13132)) -- Fix display name for `uvx --version` ([#13109](https://github.com/astral-sh/uv/pull/13109)) -- Restore handling of authentication when encountering redirects ([#13050](https://github.com/astral-sh/uv/pull/13050)) -- Respect build options (`--no-binary` et al) in `pylock.toml` ([#13134](https://github.com/astral-sh/uv/pull/13134)) -- Use `upload-time` rather than `upload_time` in `uv.lock` ([#13176](https://github.com/astral-sh/uv/pull/13176)) - -### Documentation - -- Changed `fish` completions append `>>` to overwrite `>` ([#13130](https://github.com/astral-sh/uv/pull/13130)) -- Add `pylock.toml` mentions where relevant ([#13115](https://github.com/astral-sh/uv/pull/13115)) -- Add ROCm example to the PyTorch guide ([#13200](https://github.com/astral-sh/uv/pull/13200)) -- Upgrade PyTorch guide to CUDA 12.8 and PyTorch 2.7 ([#13199](https://github.com/astral-sh/uv/pull/13199)) +See [changelogs/0.7.x](./changelogs/0.7.x.md) ## 0.6.x diff --git a/changelogs/0.6.x.md b/changelogs/0.6.x.md index 5055315ec..02f6acaeb 100644 --- a/changelogs/0.6.x.md +++ b/changelogs/0.6.x.md @@ -1,3 +1,5 @@ +# Changelog 0.6.x + ## 0.6.0 There have been 31 releases and 1135 pull requests since diff --git a/changelogs/0.7.x.md b/changelogs/0.7.x.md new file mode 100644 index 000000000..a934c266b --- /dev/null +++ b/changelogs/0.7.x.md @@ -0,0 +1,995 @@ +# Changelog 0.7.x + +## 0.7.0 + +This release contains various changes that improve correctness and user experience, but could break +some workflows; many changes have been marked as breaking out of an abundance of caution. We expect +most users to be able to upgrade without making changes. + +### Breaking changes + +- **Update `uv version` to display and update project versions + ([#12349](https://github.com/astral-sh/uv/pull/12349))** + + Previously, `uv version` displayed uv's version. Now, `uv version` will display or update the + project's version. This interface was + [heavily requested](https://github.com/astral-sh/uv/issues/6298) and, after much consideration, we + decided that transitioning the top-level command was the best option. + + Here's a brief example: + + ```console + $ uv init example + Initialized project `example` at `./example` + $ cd example + $ uv version + example 0.1.0 + $ uv version --bump major + example 0.1.0 => 1.0.0 + $ uv version --short + 1.0.0 + ``` + + If used outside of a project, uv will fallback to showing its own version still: + + ```console + $ uv version + warning: failed to read project: No `pyproject.toml` found in current directory or any parent directory + running `uv self version` for compatibility with old `uv version` command. + this fallback will be removed soon, pass `--preview` to make this an error. + + uv 0.7.0 (4433f41c9 2025-04-29) + ``` + + As described in the warning, `--preview` can be used to error instead: + + ```console + $ uv version --preview + error: No `pyproject.toml` found in current directory or any parent directory + ``` + + The previous functionality of `uv version` was moved to `uv self version`. + +- **Avoid fallback to subsequent indexes on authentication failure + ([#12805](https://github.com/astral-sh/uv/pull/12805))** + + When using the `first-index` strategy (the default), uv will stop searching indexes for a package + once it is found on a single index. Previously, uv considered a package as "missing" from an index + during authentication failures, such as an HTTP 401 or HTTP 403 (normally, missing packages are + represented by an HTTP 404). This behavior was motivated by unusual responses from some package + indexes, but reduces the safety of uv's index strategy when authentication fails. Now, uv will + consider an authentication failure as a stop-point when searching for a package across indexes. + The `index.ignore-error-codes` option can be used to recover the existing behavior, e.g.: + + ```toml + [[tool.uv.index]] + name = "pytorch" + url = "https://download.pytorch.org/whl/cpu" + ignore-error-codes = [401, 403] + ``` + + Since PyTorch's indexes always return a HTTP 403 for missing packages, uv special-cases indexes on + the `pytorch.org` domain to ignore that error code by default. + +- **Require the command in `uvx ` to be available in the Python environment + ([#11603](https://github.com/astral-sh/uv/pull/11603))** + + Previously, `uvx` would attempt to execute a command even if it was not provided by a Python + package. For example, if we presume `foo` is an empty Python package which provides no command, + `uvx foo` would invoke the `foo` command on the `PATH` (if present). Now, uv will error early if + the `foo` executable is not provided by the requested Python package. This check is not enforced + when `--from` is used, so patterns like `uvx --from foo bash -c "..."` are still valid. uv also + still allows `uvx foo` where the `foo` executable is provided by a dependency of `foo` instead of + `foo` itself, as this is fairly common for packages which depend on a dedicated package for their + command-line interface. + +- **Use index URL instead of package URL for keyring credential lookups + ([#12651](https://github.com/astral-sh/uv/pull/12651))** + + When determining credentials for querying a package URL, uv previously sent the full URL to the + `keyring` command. However, some keyring plugins expect to receive the _index URL_ (which is + usually a parent of the package URL). Now, uv requests credentials for the index URL instead. This + behavior matches `pip`. + +- **Remove `--version` from subcommands ([#13108](https://github.com/astral-sh/uv/pull/13108))** + + Previously, uv allowed the `--version` flag on arbitrary subcommands, e.g., `uv run --version`. + However, the `--version` flag is useful for other operations since uv is a package manager. + Consequently, we've removed the `--version` flag from subcommands — it is only available as + `uv --version`. + +- **Omit Python 3.7 downloads from managed versions + ([#13022](https://github.com/astral-sh/uv/pull/13022))** + + Python 3.7 is EOL and not formally supported by uv; however, Python 3.7 was previously available + for download on a subset of platforms. + +- **Reject non-PEP 751 TOML files in install, compile, and export commands + ([#13120](https://github.com/astral-sh/uv/pull/13120), + [#13119](https://github.com/astral-sh/uv/pull/13119))** + + Previously, uv treated arbitrary `.toml` files passed to commands (e.g., + `uv pip install -r foo.toml` or `uv pip compile -o foo.toml`) as `requirements.txt`-formatted + files. Now, uv will error instead. If using PEP 751 lockfiles, use the standardized format for + custom names instead, e.g., `pylock.foo.toml`. + +- **Ignore arbitrary Python requests in version files + ([#12909](https://github.com/astral-sh/uv/pull/12909))** + + uv allows arbitrary strings to be used for Python version requests, in which they are treated as + an executable name to search for in the `PATH`. However, using this form of request in + `.python-version` files is non-standard and conflicts with `pyenv-virtualenv` which writes + environment names to `.python-version` files. In this release, uv will now ignore requests that + are arbitrary strings when found in `.python-version` files. + +- **Error on unknown dependency object specifiers + ([12811](https://github.com/astral-sh/uv/pull/12811))** + + The `[dependency-groups]` entries can include "object specifiers", e.g. `set-phasers-to = ...` in: + + ```toml + [dependency-groups] + foo = ["pyparsing"] + bar = [{set-phasers-to = "stun"}] + ``` + + However, the only current spec-compliant object specifier is `include-group`. Previously, uv would + ignore unknown object specifiers. Now, uv will error. + +- **Make `--frozen` and `--no-sources` conflicting options + ([#12671](https://github.com/astral-sh/uv/pull/12671))** + + Using `--no-sources` always requires a new resolution and `--frozen` will always fail when used + with it. Now, this conflict is encoded in the CLI options for clarity. + +- **Treat empty `UV_PYTHON_INSTALL_DIR` and `UV_TOOL_DIR` as unset + ([#12907](https://github.com/astral-sh/uv/pull/12907), + [#12905](https://github.com/astral-sh/uv/pull/12905))** + + Previously, these variables were treated as set to the current working directory when set to an + empty string. Now, uv will ignore these variables when empty. This matches uv's behavior for other + environment variables which configure directories. + +### Enhancements + +- Disallow mixing requirements across PyTorch indexes + ([#13179](https://github.com/astral-sh/uv/pull/13179)) +- Add optional managed Python archive download cache + ([#12175](https://github.com/astral-sh/uv/pull/12175)) +- Add `poetry-core` as a `uv init` build backend option + ([#12781](https://github.com/astral-sh/uv/pull/12781)) +- Show tag hints when failing to find a compatible wheel in `pylock.toml` + ([#13136](https://github.com/astral-sh/uv/pull/13136)) +- Report Python versions in `pyvenv.cfg` version mismatch + ([#13027](https://github.com/astral-sh/uv/pull/13027)) + +### Bug fixes + +- Avoid erroring on omitted wheel-only packages in `pylock.toml` + ([#13132](https://github.com/astral-sh/uv/pull/13132)) +- Fix display name for `uvx --version` ([#13109](https://github.com/astral-sh/uv/pull/13109)) +- Restore handling of authentication when encountering redirects + ([#13050](https://github.com/astral-sh/uv/pull/13050)) +- Respect build options (`--no-binary` et al) in `pylock.toml` + ([#13134](https://github.com/astral-sh/uv/pull/13134)) +- Use `upload-time` rather than `upload_time` in `uv.lock` + ([#13176](https://github.com/astral-sh/uv/pull/13176)) + +### Documentation + +- Changed `fish` completions append `>>` to overwrite `>` + ([#13130](https://github.com/astral-sh/uv/pull/13130)) +- Add `pylock.toml` mentions where relevant ([#13115](https://github.com/astral-sh/uv/pull/13115)) +- Add ROCm example to the PyTorch guide ([#13200](https://github.com/astral-sh/uv/pull/13200)) +- Upgrade PyTorch guide to CUDA 12.8 and PyTorch 2.7 + ([#13199](https://github.com/astral-sh/uv/pull/13199)) + +## 0.7.1 + +### Enhancement + +- Add support for BLAKE2b-256 ([#13204](https://github.com/astral-sh/uv/pull/13204)) + +### Bugfix + +- Revert fix handling of authentication when encountering redirects + ([#13215](https://github.com/astral-sh/uv/pull/13215)) + +## 0.7.2 + +### Enhancements + +- Improve trace log for retryable errors ([#13228](https://github.com/astral-sh/uv/pull/13228)) +- Use "error" instead of "warning" for self-update message + ([#13229](https://github.com/astral-sh/uv/pull/13229)) +- Error when `uv version` is used with project-specific flags but no project is found + ([#13203](https://github.com/astral-sh/uv/pull/13203)) + +### Bug fixes + +- Fix incorrect virtual environment invalidation for pre-release Python versions + ([#13234](https://github.com/astral-sh/uv/pull/13234)) +- Fix patching of `clang` in managed Python sysconfig + ([#13237](https://github.com/astral-sh/uv/pull/13237)) +- Respect `--project` in `uv version` ([#13230](https://github.com/astral-sh/uv/pull/13230)) + +## 0.7.3 + +### Enhancements + +- Add `--dry-run` support to `uv self update` ([#9829](https://github.com/astral-sh/uv/pull/9829)) +- Add `--show-with` to `uv tool list` to list packages included by `--with` + ([#13264](https://github.com/astral-sh/uv/pull/13264)) +- De-duplicate fetched index URLs ([#13205](https://github.com/astral-sh/uv/pull/13205)) +- Support more zip compression formats: bzip2, lzma, xz, zstd + ([#13285](https://github.com/astral-sh/uv/pull/13285)) +- Add support for downloading GraalPy ([#13172](https://github.com/astral-sh/uv/pull/13172)) +- Improve error message when a virtual environment Python symlink is broken + ([#12168](https://github.com/astral-sh/uv/pull/12168)) +- Use `fs_err` for paths in symlinking errors ([#13303](https://github.com/astral-sh/uv/pull/13303)) +- Minify and embed managed Python JSON at compile time + ([#12967](https://github.com/astral-sh/uv/pull/12967)) + +### Preview features + +- Build backend: Make preview default and add configuration docs + ([#12804](https://github.com/astral-sh/uv/pull/12804)) +- Build backend: Allow escaping in globs ([#13313](https://github.com/astral-sh/uv/pull/13313)) +- Build backend: Make builds reproducible across operating systems + ([#13171](https://github.com/astral-sh/uv/pull/13171)) + +### Configuration + +- Add `python-downloads-json-url` option for `uv.toml` to configure custom Python installations via + JSON URL ([#12974](https://github.com/astral-sh/uv/pull/12974)) + +### Bug fixes + +- Check nested IO errors for retries ([#13260](https://github.com/astral-sh/uv/pull/13260)) +- Accept `musllinux_1_0` as a valid platform tag + ([#13289](https://github.com/astral-sh/uv/pull/13289)) +- Fix discovery of pre-release managed Python versions in range requests + ([#13330](https://github.com/astral-sh/uv/pull/13330)) +- Respect locked script preferences in `uv run --with` + ([#13283](https://github.com/astral-sh/uv/pull/13283)) +- Retry streaming downloads on broken pipe errors + ([#13281](https://github.com/astral-sh/uv/pull/13281)) +- Treat already-installed base environment packages as preferences in `uv run --with` + ([#13284](https://github.com/astral-sh/uv/pull/13284)) +- Avoid enumerating sources in errors for path Python requests + ([#13335](https://github.com/astral-sh/uv/pull/13335)) +- Avoid re-creating virtual environment with `--no-sync` + ([#13287](https://github.com/astral-sh/uv/pull/13287)) + +### Documentation + +- Remove outdated description of index strategy + ([#13326](https://github.com/astral-sh/uv/pull/13326)) +- Update "Viewing the version" docs ([#13241](https://github.com/astral-sh/uv/pull/13241)) + +## 0.7.4 + +### Enhancements + +- Add more context to external errors ([#13351](https://github.com/astral-sh/uv/pull/13351)) +- Align indentation of long arguments ([#13394](https://github.com/astral-sh/uv/pull/13394)) +- Preserve order of dependencies which are sorted naively + ([#13334](https://github.com/astral-sh/uv/pull/13334)) +- Align progress bars by largest name length ([#13266](https://github.com/astral-sh/uv/pull/13266)) +- Reinstall local packages in `uv add` ([#13462](https://github.com/astral-sh/uv/pull/13462)) +- Rename `--raw-sources` to `--raw` ([#13348](https://github.com/astral-sh/uv/pull/13348)) +- Show 'Downgraded' when `self update` is used to install an older version + ([#13340](https://github.com/astral-sh/uv/pull/13340)) +- Suggest `uv self update` if required uv version is newer + ([#13305](https://github.com/astral-sh/uv/pull/13305)) +- Add 3.14 beta images to uv Docker images ([#13390](https://github.com/astral-sh/uv/pull/13390)) +- Add comma after "i.e." in Conda environment error + ([#13423](https://github.com/astral-sh/uv/pull/13423)) +- Be more precise in unpinned packages warning + ([#13426](https://github.com/astral-sh/uv/pull/13426)) +- Fix detection of sorted dependencies when include-group is used + ([#13354](https://github.com/astral-sh/uv/pull/13354)) +- Fix display of HTTP responses in trace logs for retry of errors + ([#13339](https://github.com/astral-sh/uv/pull/13339)) +- Log skip reasons during Python installation key interpreter match checks + ([#13472](https://github.com/astral-sh/uv/pull/13472)) +- Redact credentials when displaying URLs ([#13333](https://github.com/astral-sh/uv/pull/13333)) + +### Bug fixes + +- Avoid erroring on `pylock.toml` dependency entries + ([#13384](https://github.com/astral-sh/uv/pull/13384)) +- Avoid panics for cannot-be-a-base URLs ([#13406](https://github.com/astral-sh/uv/pull/13406)) +- Ensure cached realm credentials are applied if no password is found for index URL + ([#13463](https://github.com/astral-sh/uv/pull/13463)) +- Fix `.tgz` parsing to respect true extension + ([#13382](https://github.com/astral-sh/uv/pull/13382)) +- Fix double self-dependency ([#13366](https://github.com/astral-sh/uv/pull/13366)) +- Reject `pylock.toml` in `uv add -r` ([#13421](https://github.com/astral-sh/uv/pull/13421)) +- Retain dot-separated wheel tags during cache prune + ([#13379](https://github.com/astral-sh/uv/pull/13379)) +- Retain trailing comments after PEP 723 metadata block + ([#13460](https://github.com/astral-sh/uv/pull/13460)) + +### Documentation + +- Use "export" instead of "install" in `uv export` arguments + ([#13430](https://github.com/astral-sh/uv/pull/13430)) +- Remove extra newline ([#13461](https://github.com/astral-sh/uv/pull/13461)) + +### Preview features + +- Build backend: Normalize glob paths ([#13465](https://github.com/astral-sh/uv/pull/13465)) + +## 0.7.5 + +### Bug fixes + +- Support case-sensitive module discovery in the build backend + ([#13468](https://github.com/astral-sh/uv/pull/13468)) +- Bump Simple cache bucket to v16 ([#13498](https://github.com/astral-sh/uv/pull/13498)) +- Don't error when the script is too short for the buffer + ([#13488](https://github.com/astral-sh/uv/pull/13488)) +- Add missing word in "script not supported" error + ([#13483](https://github.com/astral-sh/uv/pull/13483)) + +## 0.7.6 + +### Python + +- Add Python 3.14 on musl +- Add free-threaded Python on musl +- Add Python 3.14.0a7 +- Statically link `libpython` into the interpreter on Linux for a significant performance boost + +See the +[`python-build-standalone` release notes](https://github.com/astral-sh/python-build-standalone/releases/tag/20250517) +for more details. + +### Enhancements + +- Improve compatibility of `VIRTUAL_ENV_PROMPT` value + ([#13501](https://github.com/astral-sh/uv/pull/13501)) +- Bump MSRV to 1.85 and Edition 2024 ([#13516](https://github.com/astral-sh/uv/pull/13516)) + +### Bug fixes + +- Respect default extras in uv remove ([#13380](https://github.com/astral-sh/uv/pull/13380)) + +### Documentation + +- Fix PowerShell code blocks ([#13511](https://github.com/astral-sh/uv/pull/13511)) + +## 0.7.7 + +### Python + +- Work around third-party packages that (incorrectly) assume the interpreter is dynamically linking + libpython +- Allow the experimental JIT to be enabled at runtime on Python 3.13 and 3.14 on macOS on aarch64 + aka Apple Silicon + +See the +[`python-build-standalone` release notes](https://github.com/astral-sh/python-build-standalone/releases/tag/20250521) +for more details. + +### Bug fixes + +- Make `uv version` lock and sync ([#13317](https://github.com/astral-sh/uv/pull/13317)) +- Fix references to `ldd` in diagnostics to correctly refer to `ld.so` + ([#13552](https://github.com/astral-sh/uv/pull/13552)) + +### Documentation + +- Clarify adding SSH Git dependencies ([#13534](https://github.com/astral-sh/uv/pull/13534)) + +## 0.7.8 + +### Python + +We are reverting most of our Python changes from `uv 0.7.6` and `uv 0.7.7` due to a miscompilation +that makes the Python interpreter behave incorrectly, resulting in spurious type-errors involving +str. This issue seems to be isolated to x86_64 Linux, and affected at least Python 3.12, 3.13, and +3.14. + +The following changes that were introduced in those versions of uv are temporarily being reverted +while we test and deploy a proper fix for the miscompilation: + +- Add Python 3.14 on musl +- free-threaded Python on musl +- Add Python 3.14.0a7 +- Statically link `libpython` into the interpreter on Linux for a significant performance boost + +See [the issue for details](https://github.com/astral-sh/uv/issues/13610). + +### Documentation + +- Remove misleading line in pin documentation ([#13611](https://github.com/astral-sh/uv/pull/13611)) + +## 0.7.9 + +### Python + +The changes reverted in [0.7.8](#078) have been restored. + +See the +[`python-build-standalone` release notes](https://github.com/astral-sh/python-build-standalone/releases/tag/20250529) +for more details. + +### Enhancements + +- Improve obfuscation of credentials in URLs ([#13560](https://github.com/astral-sh/uv/pull/13560)) +- Allow running non-default Python implementations via `uvx` + ([#13583](https://github.com/astral-sh/uv/pull/13583)) +- Add `uvw` as alias for `uv` without console window on Windows + ([#11786](https://github.com/astral-sh/uv/pull/11786)) +- Allow discovery of x86-64 managed Python builds on macOS + ([#13722](https://github.com/astral-sh/uv/pull/13722)) +- Differentiate between implicit vs explicit architecture requests + ([#13723](https://github.com/astral-sh/uv/pull/13723)) +- Implement ordering for Python architectures to prefer native installations + ([#13709](https://github.com/astral-sh/uv/pull/13709)) +- Only show the first match per platform (and architecture) by default in `uv python list` + ([#13721](https://github.com/astral-sh/uv/pull/13721)) +- Write the path of the parent environment to an `extends-environment` key in the `pyvenv.cfg` file + of an ephemeral environment ([#13598](https://github.com/astral-sh/uv/pull/13598)) +- Improve the error message when libc cannot be found, e.g., when using the distroless containers + ([#13549](https://github.com/astral-sh/uv/pull/13549)) + +### Performance + +- Avoid rendering info log level ([#13642](https://github.com/astral-sh/uv/pull/13642)) +- Improve performance of `uv-python` crate's manylinux submodule + ([#11131](https://github.com/astral-sh/uv/pull/11131)) +- Optimize `Version` display ([#13643](https://github.com/astral-sh/uv/pull/13643)) +- Reduce number of reference-checks for `uv cache clean` + ([#13669](https://github.com/astral-sh/uv/pull/13669)) + +### Bug fixes + +- Avoid reinstalling dependency group members with `--all-packages` + ([#13678](https://github.com/astral-sh/uv/pull/13678)) +- Don't fail direct URL hash checking with dependency metadata + ([#13736](https://github.com/astral-sh/uv/pull/13736)) +- Exit early on `self update` if global `--offline` is set + ([#13663](https://github.com/astral-sh/uv/pull/13663)) +- Fix cases where the uv lock is incorrectly marked as out of date + ([#13635](https://github.com/astral-sh/uv/pull/13635)) +- Include pre-release versions in `uv python install --reinstall` + ([#13645](https://github.com/astral-sh/uv/pull/13645)) +- Set `LC_ALL=C` for git when checking git worktree + ([#13637](https://github.com/astral-sh/uv/pull/13637)) +- Avoid rejecting Windows paths for remote Python download JSON targets + ([#13625](https://github.com/astral-sh/uv/pull/13625)) + +### Preview + +- Add `uv add --bounds` to configure version constraints + ([#12946](https://github.com/astral-sh/uv/pull/12946)) + +### Documentation + +- Add documentation about Python versions to Tools concept page + ([#7673](https://github.com/astral-sh/uv/pull/7673)) +- Add example of enabling Dependabot ([#13692](https://github.com/astral-sh/uv/pull/13692)) +- Fix `exclude-newer` date format for persistent configuration files + ([#13706](https://github.com/astral-sh/uv/pull/13706)) +- Quote versions variables in GitLab documentation + ([#13679](https://github.com/astral-sh/uv/pull/13679)) +- Update Dependabot support status ([#13690](https://github.com/astral-sh/uv/pull/13690)) +- Explicitly specify to add a new repo entry to the repos list item in the `.pre-commit-config.yaml` + ([#10243](https://github.com/astral-sh/uv/pull/10243)) +- Add integration with marimo guide ([#13691](https://github.com/astral-sh/uv/pull/13691)) +- Add pronunciation to README ([#5336](https://github.com/astral-sh/uv/pull/5336)) + +## 0.7.10 + +### Enhancements + +- Add `--show-extras` to `uv tool list` ([#13783](https://github.com/astral-sh/uv/pull/13783)) +- Add dynamically generated sysconfig replacement mappings + ([#13441](https://github.com/astral-sh/uv/pull/13441)) +- Add data locations to install wheel logs ([#13797](https://github.com/astral-sh/uv/pull/13797)) + +### Bug fixes + +- Avoid redaction of placeholder `git` username when using SSH authentication + ([#13799](https://github.com/astral-sh/uv/pull/13799)) +- Propagate credentials to files on devpi indexes ending in `/+simple` + ([#13743](https://github.com/astral-sh/uv/pull/13743)) +- Restore retention of credentials for direct URLs in `uv export` + ([#13809](https://github.com/astral-sh/uv/pull/13809)) + +## 0.7.11 + +### Python + +- Add Python 3.14.0b1 +- Add Python 3.13.4 +- Add Python 3.12.11 +- Add Python 3.11.13 +- Add Python 3.10.18 +- Add Python 3.9.23 + +### Enhancements + +- Add Pyodide support ([#12731](https://github.com/astral-sh/uv/pull/12731)) +- Better error message for version specifier with missing operator + ([#13803](https://github.com/astral-sh/uv/pull/13803)) + +### Bug fixes + +- Downgrade `reqwest` and `hyper-util` to resolve connection reset errors over IPv6 + ([#13835](https://github.com/astral-sh/uv/pull/13835)) +- Prefer `uv`'s binary's version when checking if it's up to date + ([#13840](https://github.com/astral-sh/uv/pull/13840)) + +### Documentation + +- Use "terminal driver" instead of "shell" in `SIGINT` docs + ([#13787](https://github.com/astral-sh/uv/pull/13787)) + +## 0.7.12 + +### Enhancements + +- Add `uv python pin --rm` to remove `.python-version` pins + ([#13860](https://github.com/astral-sh/uv/pull/13860)) +- Don't hint at versions removed by `excluded-newer` + ([#13884](https://github.com/astral-sh/uv/pull/13884)) +- Add hint to use `tool.uv.environments` on resolution error + ([#13455](https://github.com/astral-sh/uv/pull/13455)) +- Add hint to use `tool.uv.required-environments` on resolution error + ([#13575](https://github.com/astral-sh/uv/pull/13575)) +- Improve `python pin` error messages ([#13862](https://github.com/astral-sh/uv/pull/13862)) + +### Bug fixes + +- Lock environments during `uv sync`, `uv add` and `uv remove` to prevent race conditions + ([#13869](https://github.com/astral-sh/uv/pull/13869)) +- Add `--no-editable` to `uv export` for `pylock.toml` + ([#13852](https://github.com/astral-sh/uv/pull/13852)) + +### Documentation + +- List `.gitignore` in project init files ([#13855](https://github.com/astral-sh/uv/pull/13855)) +- Move the pip interface documentation into the concepts section + ([#13841](https://github.com/astral-sh/uv/pull/13841)) +- Remove the configuration section in favor of concepts / reference + ([#13842](https://github.com/astral-sh/uv/pull/13842)) +- Update Git and GitHub Actions docs to mention `gh auth login` + ([#13850](https://github.com/astral-sh/uv/pull/13850)) + +### Preview + +- Fix directory glob traversal fallback preventing exclusion of all files + ([#13882](https://github.com/astral-sh/uv/pull/13882)) + +## 0.7.13 + +### Python + +- Add Python 3.14.0b2 +- Add Python 3.13.5 +- Fix stability of `uuid.getnode` on 3.13 + +See the +[`python-build-standalone` release notes](https://github.com/astral-sh/python-build-standalone/releases/tag/20250612) +for more details. + +### Enhancements + +- Download versions in `uv python pin` if not found + ([#13946](https://github.com/astral-sh/uv/pull/13946)) +- Use TTY detection to determine if SIGINT forwarding is enabled + ([#13925](https://github.com/astral-sh/uv/pull/13925)) +- Avoid fetching an exact, cached Git commit, even if it isn't locked + ([#13748](https://github.com/astral-sh/uv/pull/13748)) +- Add `zstd` and `deflate` to `Accept-Encoding` + ([#13982](https://github.com/astral-sh/uv/pull/13982)) +- Build binaries for riscv64 ([#12688](https://github.com/astral-sh/uv/pull/12688)) + +### Bug fixes + +- Check if relative URL is valid directory before treating as index + ([#13917](https://github.com/astral-sh/uv/pull/13917)) +- Ignore Python discovery errors during `uv python pin` + ([#13944](https://github.com/astral-sh/uv/pull/13944)) +- Do not allow `uv add --group ... --script` ([#13997](https://github.com/astral-sh/uv/pull/13997)) + +### Preview changes + +- Build backend: Support namespace packages ([#13833](https://github.com/astral-sh/uv/pull/13833)) + +### Documentation + +- Add 3.14 to the supported platform reference + ([#13990](https://github.com/astral-sh/uv/pull/13990)) +- Add an `llms.txt` to uv ([#13929](https://github.com/astral-sh/uv/pull/13929)) +- Add supported macOS version to the platform reference + ([#13993](https://github.com/astral-sh/uv/pull/13993)) +- Update platform support reference to include Python implementation list + ([#13991](https://github.com/astral-sh/uv/pull/13991)) +- Update pytorch.md ([#13899](https://github.com/astral-sh/uv/pull/13899)) +- Update the CLI help and reference to include references to the Python bin directory + ([#13978](https://github.com/astral-sh/uv/pull/13978)) + +## 0.7.14 + +### Enhancements + +- Add XPU to `--torch-backend` ([#14172](https://github.com/astral-sh/uv/pull/14172)) +- Add ROCm backends to `--torch-backend` ([#14120](https://github.com/astral-sh/uv/pull/14120)) +- Remove preview label from `--torch-backend` ([#14119](https://github.com/astral-sh/uv/pull/14119)) +- Add `[tool.uv.dependency-groups].mygroup.requires-python` + ([#13735](https://github.com/astral-sh/uv/pull/13735)) +- Add auto-detection for AMD GPUs ([#14176](https://github.com/astral-sh/uv/pull/14176)) +- Show retries for HTTP status code errors ([#13897](https://github.com/astral-sh/uv/pull/13897)) +- Support transparent Python patch version upgrades + ([#13954](https://github.com/astral-sh/uv/pull/13954)) +- Warn on empty index directory ([#13940](https://github.com/astral-sh/uv/pull/13940)) +- Publish to DockerHub ([#14088](https://github.com/astral-sh/uv/pull/14088)) + +### Performance + +- Make cold resolves about 10% faster ([#14035](https://github.com/astral-sh/uv/pull/14035)) + +### Bug fixes + +- Don't use walrus operator in interpreter query script + ([#14108](https://github.com/astral-sh/uv/pull/14108)) +- Fix handling of changes to `requires-python` + ([#14076](https://github.com/astral-sh/uv/pull/14076)) +- Fix implied `platform_machine` marker for `win_amd64` platform tag + ([#14041](https://github.com/astral-sh/uv/pull/14041)) +- Only update existing symlink directories on preview uninstall + ([#14179](https://github.com/astral-sh/uv/pull/14179)) +- Serialize Python requests for tools as canonicalized strings + ([#14109](https://github.com/astral-sh/uv/pull/14109)) +- Support netrc and same-origin credential propagation on index redirects + ([#14126](https://github.com/astral-sh/uv/pull/14126)) +- Support reading `dependency-groups` from pyproject.tomls with no `[project]` + ([#13742](https://github.com/astral-sh/uv/pull/13742)) +- Handle an existing shebang in `uv init --script` + ([#14141](https://github.com/astral-sh/uv/pull/14141)) +- Prevent concurrent updates of the environment in `uv run` + ([#14153](https://github.com/astral-sh/uv/pull/14153)) +- Filter managed Python distributions by platform before querying when included in request + ([#13936](https://github.com/astral-sh/uv/pull/13936)) + +### Documentation + +- Replace cuda124 with cuda128 ([#14168](https://github.com/astral-sh/uv/pull/14168)) +- Document the way member sources shadow workspace sources + ([#14136](https://github.com/astral-sh/uv/pull/14136)) +- Sync documented PyTorch integration index for CUDA and ROCm versions from PyTorch website + ([#14100](https://github.com/astral-sh/uv/pull/14100)) + +## 0.7.15 + +### Enhancements + +- Consistently use `Ordering::Relaxed` for standalone atomic use cases + ([#14190](https://github.com/astral-sh/uv/pull/14190)) +- Warn on ambiguous relative paths for `--index` + ([#14152](https://github.com/astral-sh/uv/pull/14152)) +- Skip GitHub fast path when rate-limited ([#13033](https://github.com/astral-sh/uv/pull/13033)) +- Preserve newlines in `schema.json` descriptions + ([#13693](https://github.com/astral-sh/uv/pull/13693)) + +### Bug fixes + +- Add check for using minor version link when creating a venv on Windows + ([#14252](https://github.com/astral-sh/uv/pull/14252)) +- Strip query parameters when parsing source URL + ([#14224](https://github.com/astral-sh/uv/pull/14224)) + +### Documentation + +- Add a link to PyPI FAQ to clarify what per-project token is + ([#14242](https://github.com/astral-sh/uv/pull/14242)) + +### Preview features + +- Allow symlinks in the build backend ([#14212](https://github.com/astral-sh/uv/pull/14212)) + +## 0.7.16 + +### Python + +- Add Python 3.14.0b3 + +See the +[`python-build-standalone` release notes](https://github.com/astral-sh/python-build-standalone/releases/tag/20250626) +for more details. + +### Enhancements + +- Include path or URL when failing to convert in lockfile + ([#14292](https://github.com/astral-sh/uv/pull/14292)) +- Warn when `~=` is used as a Python version specifier without a patch version + ([#14008](https://github.com/astral-sh/uv/pull/14008)) + +### Preview features + +- Ensure preview default Python installs are upgradeable + ([#14261](https://github.com/astral-sh/uv/pull/14261)) + +### Performance + +- Share workspace cache between lock and sync operations + ([#14321](https://github.com/astral-sh/uv/pull/14321)) + +### Bug fixes + +- Allow local indexes to reference remote files + ([#14294](https://github.com/astral-sh/uv/pull/14294)) +- Avoid rendering desugared prefix matches in error messages + ([#14195](https://github.com/astral-sh/uv/pull/14195)) +- Avoid using path URL for workspace Git dependencies in `requirements.txt` + ([#14288](https://github.com/astral-sh/uv/pull/14288)) +- Normalize index URLs to remove trailing slash + ([#14245](https://github.com/astral-sh/uv/pull/14245)) +- Respect URL-encoded credentials in redirect location + ([#14315](https://github.com/astral-sh/uv/pull/14315)) +- Lock the source tree when running setuptools, to protect concurrent builds + ([#14174](https://github.com/astral-sh/uv/pull/14174)) + +### Documentation + +- Note that GCP Artifact Registry download URLs must have `/simple` component + ([#14251](https://github.com/astral-sh/uv/pull/14251)) + +## 0.7.17 + +### Bug fixes + +- Apply build constraints when resolving `--with` dependencies + ([#14340](https://github.com/astral-sh/uv/pull/14340)) +- Drop trailing slashes when converting index URL from URL + ([#14346](https://github.com/astral-sh/uv/pull/14346)) +- Ignore `UV_PYTHON_CACHE_DIR` when empty ([#14336](https://github.com/astral-sh/uv/pull/14336)) +- Fix error message ordering for `pyvenv.cfg` version conflict + ([#14329](https://github.com/astral-sh/uv/pull/14329)) + +## 0.7.18 + +### Python + +- Added arm64 Windows Python 3.11, 3.12, 3.13, and 3.14 These are not downloaded by default, since + x86-64 Python has broader ecosystem support on Windows. However, they can be requested with + `cpython--windows-aarch64`. + +See the +[python-build-standalone release](https://github.com/astral-sh/python-build-standalone/releases/tag/20250630) +for more details. + +### Enhancements + +- Keep track of retries in `ManagedPythonDownload::fetch_with_retry` + ([#14378](https://github.com/astral-sh/uv/pull/14378)) +- Reuse build (virtual) environments across resolution and installation + ([#14338](https://github.com/astral-sh/uv/pull/14338)) +- Improve trace message for cached Python interpreter query + ([#14328](https://github.com/astral-sh/uv/pull/14328)) +- Use parsed URLs for conflicting URL error message + ([#14380](https://github.com/astral-sh/uv/pull/14380)) + +### Preview features + +- Ignore invalid build backend settings when not building + ([#14372](https://github.com/astral-sh/uv/pull/14372)) + +### Bug fixes + +- Fix equals-star and tilde-equals with `python_version` and `python_full_version` + ([#14271](https://github.com/astral-sh/uv/pull/14271)) +- Include the canonical path in the interpreter query cache key + ([#14331](https://github.com/astral-sh/uv/pull/14331)) +- Only drop build directories on program exit ([#14304](https://github.com/astral-sh/uv/pull/14304)) +- Error instead of panic on conflict between global and subcommand flags + ([#14368](https://github.com/astral-sh/uv/pull/14368)) +- Consistently normalize trailing slashes on URLs with no path segments + ([#14349](https://github.com/astral-sh/uv/pull/14349)) + +### Documentation + +- Add instructions for publishing to JFrog's Artifactory + ([#14253](https://github.com/astral-sh/uv/pull/14253)) +- Edits to the build backend documentation ([#14376](https://github.com/astral-sh/uv/pull/14376)) + +## 0.7.19 + +The **[uv build backend](https://docs.astral.sh/uv/concepts/build-backend/) is now stable**, and +considered ready for production use. + +The uv build backend is a great choice for pure Python projects. It has reasonable defaults, with +the goal of requiring zero configuration for most users, but provides flexible configuration to +accommodate most Python project structures. It integrates tightly with uv, to improve messaging and +user experience. It validates project metadata and structures, preventing common mistakes. And, +finally, it's very fast — `uv sync` on a new project (from `uv init`) is 10-30x faster than with +other build backends. + +To use uv as a build backend in an existing project, add `uv_build` to the `[build-system]` section +in your `pyproject.toml`: + +```toml +[build-system] +requires = ["uv_build>=0.7.19,<0.8.0"] +build-backend = "uv_build" +``` + +In a future release, it will replace `hatchling` as the default in `uv init`. As before, uv will +remain compatible with all standards-compliant build backends. + +### Python + +- Add PGO distributions of Python for aarch64 Linux, which are more optimized for better performance + +See the +[python-build-standalone release](https://github.com/astral-sh/python-build-standalone/releases/tag/20250702) +for more details. + +### Enhancements + +- Ignore Python patch version for `--universal` pip compile + ([#14405](https://github.com/astral-sh/uv/pull/14405)) +- Update the tilde version specifier warning to include more context + ([#14335](https://github.com/astral-sh/uv/pull/14335)) +- Clarify behavior and hint on tool install when no executables are available + ([#14423](https://github.com/astral-sh/uv/pull/14423)) + +### Bug fixes + +- Make project and interpreter lock acquisition non-fatal + ([#14404](https://github.com/astral-sh/uv/pull/14404)) +- Includes `sys.prefix` in cached environment keys to avoid `--with` collisions across projects + ([#14403](https://github.com/astral-sh/uv/pull/14403)) + +### Documentation + +- Add a migration guide from pip to uv projects + ([#12382](https://github.com/astral-sh/uv/pull/12382)) + +## 0.7.20 + +### Python + +- Add Python 3.14.0b4 +- Add zstd support to Python 3.14 on Unix (it already was available on Windows) +- Add PyPy 7.3.20 (for Python 3.11.13) + +See the [PyPy](https://pypy.org/posts/2025/07/pypy-v7320-release.html) and +[`python-build-standalone`](https://github.com/astral-sh/python-build-standalone/releases/tag/20250708) +release notes for more details. + +### Enhancements + +- Add `--workspace` flag to `uv add` ([#14496](https://github.com/astral-sh/uv/pull/14496)) +- Add auto-detection for Intel GPUs ([#14386](https://github.com/astral-sh/uv/pull/14386)) +- Drop trailing arguments when writing shebangs + ([#14519](https://github.com/astral-sh/uv/pull/14519)) +- Add debug message when skipping Python downloads + ([#14509](https://github.com/astral-sh/uv/pull/14509)) +- Add support for declaring multiple modules in namespace packages + ([#14460](https://github.com/astral-sh/uv/pull/14460)) + +### Bug fixes + +- Revert normalization of trailing slashes on index URLs + ([#14511](https://github.com/astral-sh/uv/pull/14511)) +- Fix forced resolution with all extras in `uv version` + ([#14434](https://github.com/astral-sh/uv/pull/14434)) +- Fix handling of pre-releases in preferences ([#14498](https://github.com/astral-sh/uv/pull/14498)) +- Remove transparent variants in `uv-extract` to enable retries + ([#14450](https://github.com/astral-sh/uv/pull/14450)) + +### Rust API + +- Add method to get packages involved in a `NoSolutionError` + ([#14457](https://github.com/astral-sh/uv/pull/14457)) +- Make `ErrorTree` for `NoSolutionError` public + ([#14444](https://github.com/astral-sh/uv/pull/14444)) + +### Documentation + +- Finish incomplete sentence in pip migration guide + ([#14432](https://github.com/astral-sh/uv/pull/14432)) +- Remove `cache-dependency-glob` examples for `setup-uv` + ([#14493](https://github.com/astral-sh/uv/pull/14493)) +- Remove `uv pip sync` suggestion with `pyproject.toml` + ([#14510](https://github.com/astral-sh/uv/pull/14510)) +- Update documentation for GitHub to use `setup-uv@v6` + ([#14490](https://github.com/astral-sh/uv/pull/14490)) + +## 0.7.21 + +### Python + +- Restore the SQLite `fts4`, `fts5`, `rtree`, and `geopoly` extensions on macOS and Linux + +See the +[`python-build-standalone` release notes](https://github.com/astral-sh/python-build-standalone/releases/tag/20250712) +for more details. + +### Enhancements + +- Add `--python-platform` to `uv sync` ([#14320](https://github.com/astral-sh/uv/pull/14320)) +- Support pre-releases in `uv version --bump` ([#13578](https://github.com/astral-sh/uv/pull/13578)) +- Add `-w` shorthand for `--with` ([#14530](https://github.com/astral-sh/uv/pull/14530)) +- Add an exception handler on Windows to display information on crash + ([#14582](https://github.com/astral-sh/uv/pull/14582)) +- Add hint when Python downloads are disabled ([#14522](https://github.com/astral-sh/uv/pull/14522)) +- Add `UV_HTTP_RETRIES` to customize retry counts + ([#14544](https://github.com/astral-sh/uv/pull/14544)) +- Follow leaf symlinks matched by globs in `cache-key` + ([#13438](https://github.com/astral-sh/uv/pull/13438)) +- Support parent path components (`..`) in globs in `cache-key` + ([#13469](https://github.com/astral-sh/uv/pull/13469)) +- Improve `cache-key` performance ([#13469](https://github.com/astral-sh/uv/pull/13469)) + +### Preview features + +- Add `uv sync --output-format json` ([#13689](https://github.com/astral-sh/uv/pull/13689)) + +### Bug fixes + +- Do not re-resolve with a new Python version in `uv tool` if it is incompatible with `--python` + ([#14606](https://github.com/astral-sh/uv/pull/14606)) + +### Documentation + +- Document how to nest dependency groups with `include-group` + ([#14539](https://github.com/astral-sh/uv/pull/14539)) +- Fix repeated word in Pyodide doc ([#14554](https://github.com/astral-sh/uv/pull/14554)) +- Update CONTRIBUTING.md with instructions to format Markdown files via Docker + ([#14246](https://github.com/astral-sh/uv/pull/14246)) +- Fix version number for `setup-python` ([#14533](https://github.com/astral-sh/uv/pull/14533)) + +## 0.7.22 + +### Python + +- Upgrade GraalPy to 24.2.2 + +See the [GraalPy release notes](https://github.com/oracle/graalpython/releases/tag/graal-24.2.2) for +more details. + +### Configuration + +- Add `UV_COMPILE_BYTECODE_TIMEOUT` environment variable + ([#14369](https://github.com/astral-sh/uv/pull/14369)) +- Allow users to override index `cache-control` headers + ([#14620](https://github.com/astral-sh/uv/pull/14620)) +- Add `UV_LIBC` to override libc selection in multi-libc environment + ([#14646](https://github.com/astral-sh/uv/pull/14646)) + +### Bug fixes + +- Fix `--all-arches` when paired with `--only-downloads` + ([#14629](https://github.com/astral-sh/uv/pull/14629)) +- Skip Windows Python interpreters that return a broken MSIX package code + ([#14636](https://github.com/astral-sh/uv/pull/14636)) +- Warn on invalid `uv.toml` when provided via direct path + ([#14653](https://github.com/astral-sh/uv/pull/14653)) +- Improve async signal safety in Windows exception handler + ([#14619](https://github.com/astral-sh/uv/pull/14619)) + +### Documentation + +- Mention the `revision` in the lockfile versioning doc + ([#14634](https://github.com/astral-sh/uv/pull/14634)) +- Move "Conflicting dependencies" to the "Resolution" page + ([#14633](https://github.com/astral-sh/uv/pull/14633)) +- Rename "Dependency specifiers" section to exclude PEP 508 reference + ([#14631](https://github.com/astral-sh/uv/pull/14631)) +- Suggest `uv cache clean` prior to `--reinstall` + ([#14659](https://github.com/astral-sh/uv/pull/14659)) + +### Preview features + +- Make preview Python registration on Windows non-fatal + ([#14614](https://github.com/astral-sh/uv/pull/14614)) +- Update preview installation of Python executables to be non-fatal + ([#14612](https://github.com/astral-sh/uv/pull/14612)) +- Add `uv python update-shell` ([#14627](https://github.com/astral-sh/uv/pull/14627)) From 02cc49296b2f415654ba86d138b929346d35636f Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 22 Jul 2025 14:11:15 -0500 Subject: [PATCH 322/349] Avoid reading files in the environment bin that are not entrypoints (#14830) Closes https://github.com/astral-sh/uv/issues/14829 I tested this against the given Dockerfile. --- crates/uv/src/commands/project/run.rs | 38 +++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index 44d0dc474..a1476c63c 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -1754,12 +1754,46 @@ fn copy_entrypoint( previous_executable: &Path, python_executable: &Path, ) -> Result<(), CopyEntrypointError> { - use std::io::Write; + use std::io::{Seek, Write}; use std::os::unix::fs::PermissionsExt; use fs_err::os::unix::fs::OpenOptionsExt; - let contents = fs_err::read_to_string(source)?; + let mut file = fs_err::File::open(source)?; + let mut buffer = [0u8; 2]; + if file.read_exact(&mut buffer).is_err() { + // File is too small to have a shebang + trace!( + "Skipping copy of entrypoint `{}`: file is too small to contain a shebang", + source.user_display() + ); + return Ok(()); + } + + // Check if it starts with `#!` to avoid reading binary files and such into memory + if &buffer != b"#!" { + trace!( + "Skipping copy of entrypoint `{}`: does not start with #!", + source.user_display() + ); + return Ok(()); + } + + let mut contents = String::new(); + file.seek(std::io::SeekFrom::Start(0))?; + match file.read_to_string(&mut contents) { + Ok(_) => {} + Err(err) if err.kind() == std::io::ErrorKind::InvalidData => { + // If the file is not valid UTF-8, we skip it in case it was a binary file with `#!` at + // the start (which seems pretty niche, but being defensive here seems safe) + trace!( + "Skipping copy of entrypoint `{}`: is not valid UTF-8", + source.user_display() + ); + return Ok(()); + } + Err(err) => return Err(err.into()), + } let Some(contents) = contents // Check for a relative path or relocatable shebang From 34cda1be44be7a15da9e1549cd8820be71340f95 Mon Sep 17 00:00:00 2001 From: Nils Koch Date: Tue, 22 Jul 2025 20:25:33 +0100 Subject: [PATCH 323/349] expose `tls_built_in_root_certs` from reqwest client (#14816) ## Summary We are using UV as a library and need to set `tls_built_in_root_certs` on the reqwest client. This PR exposes this property in the `BaseClientBuilder` and in the `RegistryClientBuilder`. The default is set to `false`, so this does not change any behaviour unless you explicitly opt into it. ## Test Plan --- crates/uv-client/src/base_client.rs | 10 +++++++++- crates/uv-client/src/registry_client.rs | 8 ++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/crates/uv-client/src/base_client.rs b/crates/uv-client/src/base_client.rs index d901f57e7..e4945f5ee 100644 --- a/crates/uv-client/src/base_client.rs +++ b/crates/uv-client/src/base_client.rs @@ -67,6 +67,7 @@ pub struct BaseClientBuilder<'a> { keyring: KeyringProviderType, allow_insecure_host: Vec, native_tls: bool, + built_in_root_certs: bool, retries: u32, pub connectivity: Connectivity, markers: Option<&'a MarkerEnvironment>, @@ -127,6 +128,7 @@ impl BaseClientBuilder<'_> { keyring: KeyringProviderType::default(), allow_insecure_host: vec![], native_tls: false, + built_in_root_certs: false, connectivity: Connectivity::Online, retries: DEFAULT_RETRIES, markers: None, @@ -192,6 +194,12 @@ impl<'a> BaseClientBuilder<'a> { self } + #[must_use] + pub fn built_in_root_certs(mut self, built_in_root_certs: bool) -> Self { + self.built_in_root_certs = built_in_root_certs; + self + } + #[must_use] pub fn markers(mut self, markers: &'a MarkerEnvironment) -> Self { self.markers = Some(markers); @@ -388,7 +396,7 @@ impl<'a> BaseClientBuilder<'a> { .user_agent(user_agent) .pool_max_idle_per_host(20) .read_timeout(timeout) - .tls_built_in_root_certs(false) + .tls_built_in_root_certs(self.built_in_root_certs) .redirect(redirect_policy.reqwest_policy()); // If necessary, accept invalid certificates. diff --git a/crates/uv-client/src/registry_client.rs b/crates/uv-client/src/registry_client.rs index 1d12c5adf..c21aa3b31 100644 --- a/crates/uv-client/src/registry_client.rs +++ b/crates/uv-client/src/registry_client.rs @@ -126,6 +126,14 @@ impl<'a> RegistryClientBuilder<'a> { self } + #[must_use] + pub fn built_in_root_certs(mut self, built_in_root_certs: bool) -> Self { + self.base_client_builder = self + .base_client_builder + .built_in_root_certs(built_in_root_certs); + self + } + #[must_use] pub fn cache(mut self, cache: Cache) -> Self { self.cache = cache; From 21fadbcc1347bdc655a4be3d6105453b9f7d64e4 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 22 Jul 2025 14:39:53 -0500 Subject: [PATCH 324/349] Bump version to 0.8.2 (#14832) --- CHANGELOG.md | 24 ++++++++++++++++++++++++ Cargo.lock | 6 +++--- crates/uv-build/Cargo.toml | 2 +- crates/uv-build/pyproject.toml | 2 +- crates/uv-version/Cargo.toml | 2 +- crates/uv/Cargo.toml | 2 +- docs/concepts/build-backend.md | 2 +- 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/pre-commit.md | 10 +++++----- pyproject.toml | 2 +- 13 files changed, 48 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16b551e80..6a9e0af94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,30 @@ +## 0.8.2 + +### Enhancements + +- Add derivation chains for dependency errors ([#14824](https://github.com/astral-sh/uv/pull/14824)) + +### Configuration + +- Add `UV_INIT_BUILD_BACKEND` ([#14821](https://github.com/astral-sh/uv/pull/14821)) + +### Bug fixes + +- Avoid reading files in the environment bin that are not entrypoints ([#14830](https://github.com/astral-sh/uv/pull/14830)) +- Avoid removing empty directories when constructing virtual environments ([#14822](https://github.com/astral-sh/uv/pull/14822)) +- Preserve index URL priority order when writing to pyproject.toml ([#14831](https://github.com/astral-sh/uv/pull/14831)) + +### Rust API + +- Expose `tls_built_in_root_certs` for client ([#14816](https://github.com/astral-sh/uv/pull/14816)) + +### Documentation + +- Archive the 0.7.x changelog ([#14819](https://github.com/astral-sh/uv/pull/14819)) + ## 0.8.1 ### Enhancements diff --git a/Cargo.lock b/Cargo.lock index b38eafa97..e8da02e2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4645,7 +4645,7 @@ dependencies = [ [[package]] name = "uv" -version = "0.8.1" +version = "0.8.2" dependencies = [ "anstream", "anyhow", @@ -4811,7 +4811,7 @@ dependencies = [ [[package]] name = "uv-build" -version = "0.8.1" +version = "0.8.2" dependencies = [ "anyhow", "uv-build-backend", @@ -6005,7 +6005,7 @@ dependencies = [ [[package]] name = "uv-version" -version = "0.8.1" +version = "0.8.2" [[package]] name = "uv-virtualenv" diff --git a/crates/uv-build/Cargo.toml b/crates/uv-build/Cargo.toml index d1da52fc7..063939748 100644 --- a/crates/uv-build/Cargo.toml +++ b/crates/uv-build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-build" -version = "0.8.1" +version = "0.8.2" edition.workspace = true rust-version.workspace = true homepage.workspace = true diff --git a/crates/uv-build/pyproject.toml b/crates/uv-build/pyproject.toml index 17f99f538..fcf06ed05 100644 --- a/crates/uv-build/pyproject.toml +++ b/crates/uv-build/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uv-build" -version = "0.8.1" +version = "0.8.2" description = "The uv build backend" authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }] requires-python = ">=3.8" diff --git a/crates/uv-version/Cargo.toml b/crates/uv-version/Cargo.toml index db8381053..3afc69a98 100644 --- a/crates/uv-version/Cargo.toml +++ b/crates/uv-version/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-version" -version = "0.8.1" +version = "0.8.2" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml index 699f98c50..272b5ae87 100644 --- a/crates/uv/Cargo.toml +++ b/crates/uv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv" -version = "0.8.1" +version = "0.8.2" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } diff --git a/docs/concepts/build-backend.md b/docs/concepts/build-backend.md index cdc37ea40..ce1af212d 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.8.1,<0.9.0"] +requires = ["uv_build>=0.8.2,<0.9.0"] build-backend = "uv_build" ``` diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index b6d2e2efc..3a961c4c1 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.8.1/install.sh | sh + $ curl -LsSf https://astral.sh/uv/0.8.2/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.8.1/install.ps1 | iex" + PS> powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/0.8.2/install.ps1 | iex" ``` !!! tip diff --git a/docs/guides/integration/aws-lambda.md b/docs/guides/integration/aws-lambda.md index c0b82ad12..4046b009e 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.8.1 AS uv +FROM ghcr.io/astral-sh/uv:0.8.2 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.8.1 AS uv +FROM ghcr.io/astral-sh/uv:0.8.2 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 b707bc7cd..b17ee0f1e 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.8.1` +- `ghcr.io/astral-sh/uv:{major}.{minor}.{patch}`, e.g., `ghcr.io/astral-sh/uv:0.8.2` - `ghcr.io/astral-sh/uv:{major}.{minor}`, e.g., `ghcr.io/astral-sh/uv:0.8` (the latest patch version) @@ -75,7 +75,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.8.1-alpine`. +`ghcr.io/astral-sh/uv:{major}.{minor}-{base}`, e.g., `ghcr.io/astral-sh/uv:0.8.2-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. @@ -116,7 +116,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.8.1 /uv /uvx /bin/ +COPY --from=ghcr.io/astral-sh/uv:0.8.2 /uv /uvx /bin/ ``` !!! tip @@ -134,7 +134,7 @@ COPY --from=ghcr.io/astral-sh/uv:0.8.1 /uv /uvx /bin/ Or, with the installer: ```dockerfile -ADD https://astral.sh/uv/0.8.1/install.sh /uv-installer.sh +ADD https://astral.sh/uv/0.8.2/install.sh /uv-installer.sh ``` ### Installing a project @@ -560,5 +560,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.8.1`, or (even better) the specific image digest, + version tag, e.g., `ghcr.io/astral-sh/uv:0.8.2`, 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 512085b02..932c47033 100644 --- a/docs/guides/integration/github.md +++ b/docs/guides/integration/github.md @@ -47,7 +47,7 @@ jobs: uses: astral-sh/setup-uv@v6 with: # Install a specific version of uv. - version: "0.8.1" + version: "0.8.2" ``` ## Setting up Python diff --git a/docs/guides/integration/pre-commit.md b/docs/guides/integration/pre-commit.md index b2a2db826..2e83b4822 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.8.1 + rev: 0.8.2 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.8.1 + rev: 0.8.2 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.8.1 + rev: 0.8.2 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.8.1 + rev: 0.8.2 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.8.1 + rev: 0.8.2 hooks: # Compile requirements - id: pip-compile diff --git a/pyproject.toml b/pyproject.toml index 492efb7e3..d18374587 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "uv" -version = "0.8.1" +version = "0.8.2" 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" From 788b70f0fe9565b6d3c674b68afeeaedef6ae9ba Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 23 Jul 2025 07:11:17 -0500 Subject: [PATCH 325/349] Move the "Cargo" install method further down in docs (#14842) Closes https://github.com/astral-sh/uv/issues/14835 --- docs/getting-started/installation.md | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 3a961c4c1..47791cec2 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -88,15 +88,6 @@ $ pip install uv [contributing setup guide](https://github.com/astral-sh/uv/blob/main/CONTRIBUTING.md#setup) for details on building uv from source. -### Cargo - -uv is available via Cargo, but must be built from Git rather than [crates.io](https://crates.io) due -to its dependency on unpublished crates. - -```console -$ cargo install --git https://github.com/astral-sh/uv uv -``` - ### Homebrew uv is available in the core Homebrew packages. @@ -136,6 +127,19 @@ uv release artifacts can be downloaded directly from Each release page includes binaries for all supported platforms as well as instructions for using the standalone installer via `github.com` instead of `astral.sh`. +### Cargo + +uv is available via Cargo, but must be built from Git rather than [crates.io](https://crates.io) due +to its dependency on unpublished crates. + +```console +$ cargo install --git https://github.com/astral-sh/uv uv +``` + +!!! note + + This method builds uv from source, which requires a compatible Rust toolchain. + ## Upgrading uv When uv is installed via the standalone installer, it can update itself on-demand: From 310a9d342650dfaaf3d62e06554ba1bc49b1c5d7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 23 Jul 2025 11:01:09 -0500 Subject: [PATCH 326/349] Sync latest Python releases (#14847) Automated update for Python releases. Co-authored-by: zanieb <2586601+zanieb@users.noreply.github.com> --- .../uv-dev/src/generate_sysconfig_mappings.rs | 4 +- crates/uv-python/download-metadata.json | 1560 +++++++++++++---- .../src/sysconfig/generated_mappings.rs | 2 +- crates/uv/tests/it/python_install.rs | 10 +- 4 files changed, 1204 insertions(+), 372 deletions(-) diff --git a/crates/uv-dev/src/generate_sysconfig_mappings.rs b/crates/uv-dev/src/generate_sysconfig_mappings.rs index 8357ee7fb..da238bc80 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/20250712/cpython-unix/targets.yml"; +const TARGETS_YML_URL: &str = "https://raw.githubusercontent.com/astral-sh/python-build-standalone/refs/tags/20250723/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/download-metadata.json b/crates/uv-python/download-metadata.json index 540a3c8a0..f37c33ca1 100644 --- a/crates/uv-python/download-metadata.json +++ b/crates/uv-python/download-metadata.json @@ -1,4 +1,836 @@ { + "cpython-3.14.0rc1-darwin-aarch64-none": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "dcfb420af1aa99d91db6e5d318abff5779abe3e2f743da0da4eca921910caeaf", + "variant": null + }, + "cpython-3.14.0rc1-darwin-x86_64-none": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "f6bfca47afe6d591997313c129e09ffdc97684ed3b7cac5c9770c1fb78eb8369", + "variant": null + }, + "cpython-3.14.0rc1-linux-aarch64-gnu": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "c542388de7bc9ce2a53443c5006b39a7cde247c1c2d28389669e78b84fcd58f4", + "variant": null + }, + "cpython-3.14.0rc1-linux-armv7-gnueabi": { + "name": "cpython", + "arch": { + "family": "armv7", + "variant": null + }, + "os": "linux", + "libc": "gnueabi", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "06258716bcef808e182487474dafb51bb1c9667e50a5aa451faa2b1609ec9957", + "variant": null + }, + "cpython-3.14.0rc1-linux-armv7-gnueabihf": { + "name": "cpython", + "arch": { + "family": "armv7", + "variant": null + }, + "os": "linux", + "libc": "gnueabihf", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "d48109bd2fcd7804c7cc069da7db8148fd15f24620d51f7b07e2de5d6ccead24", + "variant": null + }, + "cpython-3.14.0rc1-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": { + "family": "powerpc64le", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "a33b5432bcde4005e5167494ef3e43595b9523b3d2494455c0d2952dd0ae7119", + "variant": null + }, + "cpython-3.14.0rc1-linux-riscv64-gnu": { + "name": "cpython", + "arch": { + "family": "riscv64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "8fbd656cf30fd518633bc237c0aa8d581c4b0d9cab747ed188fd85b11de901fe", + "variant": null + }, + "cpython-3.14.0rc1-linux-s390x-gnu": { + "name": "cpython", + "arch": { + "family": "s390x", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "698ce991f9c5c2321c16f07dd904dcfe3163cfb76b8a7a4856e11c05f3c270ec", + "variant": null + }, + "cpython-3.14.0rc1-linux-x86_64-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "15e34735d42d4d10db22107db14adf11d366c69aeeda0a52c52b42006c1828e5", + "variant": null + }, + "cpython-3.14.0rc1-linux-x86_64-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "f3f5c5b31cb8f14991d2eb491c1a08965d38c09d4cb703d63710b72144e34249", + "variant": null + }, + "cpython-3.14.0rc1-linux-x86_64_v2-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v2" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "bb0bb41e882c8f9440e48f855018f7586651f6c37ca258c28389283e2250d6a1", + "variant": null + }, + "cpython-3.14.0rc1-linux-x86_64_v2-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v2" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "40446b1864d5f137fefe5f219ad604cefb5a41b0a616eb780570cb8a10e222cd", + "variant": null + }, + "cpython-3.14.0rc1-linux-x86_64_v3-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v3" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "670326630740431e1de02da7a809a777392e5bc3b1ff676d41b6033a32e903a4", + "variant": null + }, + "cpython-3.14.0rc1-linux-x86_64_v3-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v3" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "0c26378f91141dcdce6fee7cd2402d28c01bd43e85181224684e5210103af380", + "variant": null + }, + "cpython-3.14.0rc1-linux-x86_64_v4-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v4" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "52dda8bcb05432b3b43b283b27e03cba7af03f1c60dba61c32b8d7a820cacfc9", + "variant": null + }, + "cpython-3.14.0rc1-linux-x86_64_v4-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v4" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "8faf38625bd246373d986a3aa6f2a5ff907960a0c2d5705d72f3d1c0bcf99077", + "variant": null + }, + "cpython-3.14.0rc1-windows-aarch64-none": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "windows", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "05adf846ecb53013a75d17c3a767b91dcaab1fcc980c633c0418a23d805c9d1e", + "variant": null + }, + "cpython-3.14.0rc1-windows-i686-none": { + "name": "cpython", + "arch": { + "family": "i686", + "variant": null + }, + "os": "windows", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "26d82b4d1c8bf076d992bf34862610d8939f6d060cc7865cda8c355481059fb4", + "variant": null + }, + "cpython-3.14.0rc1-windows-x86_64-none": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "windows", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "d0cbe346bada69096985b34a29b9b468713581dfe26ce40a411766a27c426060", + "variant": null + }, + "cpython-3.14.0rc1+freethreaded-darwin-aarch64-none": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "0bed2d0aee3eec6ae47fb5aa18e83c63d31621276ec73956cff8074870e2b8cd", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-darwin-x86_64-none": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "04447e12c93b9299a28bfba10d94fb02bd18ff858097fa95474c5c6673d826d1", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-linux-aarch64-gnu": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-aarch64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "b71f5c60fdffb4f375d53a3d16a45520b76af52448f55414243b6b20606447ac", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-linux-armv7-gnueabi": { + "name": "cpython", + "arch": { + "family": "armv7", + "variant": null + }, + "os": "linux", + "libc": "gnueabi", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-armv7-unknown-linux-gnueabi-freethreaded%2Blto-full.tar.zst", + "sha256": "2a485fb0ffc5a847cc7abf2cd47ae65e086de3c831c77a2a1a721da064d48c02", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-linux-armv7-gnueabihf": { + "name": "cpython", + "arch": { + "family": "armv7", + "variant": null + }, + "os": "linux", + "libc": "gnueabihf", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-armv7-unknown-linux-gnueabihf-freethreaded%2Blto-full.tar.zst", + "sha256": "1afc8301ffb66c085740311661c691e2d0f5a6096a0663dfc8821763d1c6f7b3", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": { + "family": "powerpc64le", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-ppc64le-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "982f0f8451cd90986b43683b119f157fe4eb69f763be18a65503b4869266798a", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-linux-riscv64-gnu": { + "name": "cpython", + "arch": { + "family": "riscv64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-riscv64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "55ab4d2dfa1e01b2f7a69bcae6fad3f77bc7f9ec2556a31c0885b16d446e90a6", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-linux-s390x-gnu": { + "name": "cpython", + "arch": { + "family": "s390x", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-s390x-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "d870eb1d41bdf97a9739c387b6d8ca948f66f9ea9a77e5a3591c3b88551bdcfd", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-linux-x86_64-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "ab51a2a4162b9ec329fdcd2e15ac0d59a458d8db133c03a07ad86a8890a302ae", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-linux-x86_64-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "fd0db9f4d311868dab4759cf1a204551f75d7f990560cbe5bf809b133fe84295", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-linux-x86_64_v2-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v2" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v2-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "5046d68526bd4d40f893e700f59e5f1720cf23fd8ec50c8eec805f0abb4018bd", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-linux-x86_64_v2-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v2" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v2-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "66289891081ec622d07e1774b54a11b4e45217af978f75bfba79d3e59cb9032c", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-linux-x86_64_v3-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v3" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v3-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "b209b1640fdccedef35e6f5e6a914615ec7e9277376669ee78025d84e37c766d", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-linux-x86_64_v3-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v3" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v3-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "dd61157b5f96b0c70327a96b9232c5db50eea504a074f1da985143ae68ad2a9a", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-linux-x86_64_v4-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v4" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v4-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "9885d7a4a9752b6d616f68e1c5347adcd453329af8810d8cbe05397d9cf5514f", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-linux-x86_64_v4-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v4" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v4-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "32941293e3eaa171f010608d944a5f5abf0d304477b7cca7699b81f110eeec58", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-windows-aarch64-none": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "windows", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-aarch64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "2f2ec7b783a135680153905780a91d07cc8e47768139cfb285db1703eed98a8e", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-windows-i686-none": { + "name": "cpython", + "arch": { + "family": "i686", + "variant": null + }, + "os": "windows", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-i686-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "a847fa2357c31db9a9585803dbaa72056d9cd11728e7a91cc2a0286eb5845d8d", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-windows-x86_64-none": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "windows", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "3cf7290af19454ed0e30649ccd0c68d67911b22428e008af08766723efd212b2", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "a92dd263a47aeb14790f1a8c559f5865d7abbed067484768a480d0427c72bbc0", + "variant": "debug" + }, + "cpython-3.14.0rc1+debug-linux-armv7-gnueabi": { + "name": "cpython", + "arch": { + "family": "armv7", + "variant": null + }, + "os": "linux", + "libc": "gnueabi", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "5a00fd3bc886ac893491337f9379a575ba164fd442815ca43ce5a11cce05483e", + "variant": "debug" + }, + "cpython-3.14.0rc1+debug-linux-armv7-gnueabihf": { + "name": "cpython", + "arch": { + "family": "armv7", + "variant": null + }, + "os": "linux", + "libc": "gnueabihf", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "65af1c0362b63bfe891a78f8f7716d23cef235e0918b080377d7b1a21b84982f", + "variant": "debug" + }, + "cpython-3.14.0rc1+debug-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": { + "family": "powerpc64le", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "c2ff82a274b03cded473ce77a9dcd39d503da56ad2561a06d09354ddb53e2b01", + "variant": "debug" + }, + "cpython-3.14.0rc1+debug-linux-riscv64-gnu": { + "name": "cpython", + "arch": { + "family": "riscv64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "76a1b3af532dbb89d404b2c5bcb38d6d123d28a2aaf0393a620c5eb1140e95d6", + "variant": "debug" + }, + "cpython-3.14.0rc1+debug-linux-s390x-gnu": { + "name": "cpython", + "arch": { + "family": "s390x", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "38eb350ec7b9752a8cc506bebe5d44b19ff000ed5756ab97590253014b0cef69", + "variant": "debug" + }, + "cpython-3.14.0rc1+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "3b3799653a00f6b57206800fcfb317267c94604daa3797d47f0a9b5e112a229a", + "variant": "debug" + }, + "cpython-3.14.0rc1+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "1be9b3fce91cfed297c65975f981bd41b0a3273e85843af0721386684153e1cb", + "variant": "debug" + }, + "cpython-3.14.0rc1+debug-linux-x86_64_v2-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v2" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "6e2a35a7f4eda1cafc5f262168c6d0718fe122f5f4be5ce1084531341efc7cde", + "variant": "debug" + }, + "cpython-3.14.0rc1+debug-linux-x86_64_v2-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v2" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "c1f051b4c2bca6edccd13c3141f2efffc9e48ec53d5c21ad21acc3defd508e5d", + "variant": "debug" + }, + "cpython-3.14.0rc1+debug-linux-x86_64_v3-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v3" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "ce9c32fcb9f5492c83a07d821352fbc04fb196df58dce5deb236b605368c1658", + "variant": "debug" + }, + "cpython-3.14.0rc1+debug-linux-x86_64_v3-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v3" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "24e8a566d1dcc1f81bdffe1c07591e87730448ace085fc5a776e8769c146c206", + "variant": "debug" + }, + "cpython-3.14.0rc1+debug-linux-x86_64_v4-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v4" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "0a956747b1f816c9d116f9dd78e3944a676fc3a1540d51799c18580b9813573c", + "variant": "debug" + }, + "cpython-3.14.0rc1+debug-linux-x86_64_v4-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v4" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "6bde1af0b5ee94ebc403219d835d39d081056ae541f4207e96e9d9107e580a5a", + "variant": "debug" + }, "cpython-3.14.0b4-darwin-aarch64-none": { "name": "cpython", "arch": { @@ -6395,8 +7227,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "08d840adc7dd1724bd7c25141a0207f8343808749fa67e608d8007b46429c196", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "a2c25baa13d271b730744d7e8684d7db54a003dfeeb5ae3b0ae86af7d81d44ae", "variant": null }, "cpython-3.13.5-darwin-x86_64-none": { @@ -6411,8 +7243,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "5277dc381e94abde80989841f3015df2aba33894893c4a31d63400887bdefd2d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "c198610998092c42650600ef250be388ba27ab307ade4d8684c27af9a0b5569a", "variant": null }, "cpython-3.13.5-linux-aarch64-gnu": { @@ -6427,8 +7259,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "82d8a025b43c9127d47490a7070aa5d8bfede2d1deb5161c0f4c2355396f9e5d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "65412f4d99966ab9e0218211c1408497b5ecd2a40e1bd12183a37937c25cce4e", "variant": null }, "cpython-3.13.5-linux-armv7-gnueabi": { @@ -6443,8 +7275,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "6aa50bf3245364091a7e5ca6b88166f960c2268586c33e295069645815f16195", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "c64b911fcd21846c0dda87411cb42cda51ed915d9e38e080c2c62b493f209268", "variant": null }, "cpython-3.13.5-linux-armv7-gnueabihf": { @@ -6459,8 +7291,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "5f776b18951b9a0507e64e890796113a16b18adb93a01d4f84c922e2564dab43", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "878ced3ad23a128f356ddef90a8f5e7f5d822983c3f9e0e0a981fe846632a848", "variant": null }, "cpython-3.13.5-linux-powerpc64le-gnu": { @@ -6475,8 +7307,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "b74b79e5a65c84ed732071fd7b445a51b86c03ef18643b87c0fe5c96242e629b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "2d90eb0947dfec36960456fe4e9bf631b7943c412b8c57319c7273bd7ce9fcd4", "variant": null }, "cpython-3.13.5-linux-riscv64-gnu": { @@ -6491,8 +7323,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "652416183693219b1f0f1f2a8d2a595f75f8c94e8c7b8b25ecd312ec1fdbb36e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "353dbbf89f555ef19a6ef0f0557f63286544f32bdf739a9b9aaa66f70c22473b", "variant": null }, "cpython-3.13.5-linux-s390x-gnu": { @@ -6507,8 +7339,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "29a7140db0cbd1426f450cd419a8b5892a4a72d7ef74c1760940dd656f8eaded", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "60b2cadd89ee8bee3aaaa0d51cb8156f6c5f95a9024620e49ec251fc46085019", "variant": null }, "cpython-3.13.5-linux-x86_64-gnu": { @@ -6523,8 +7355,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "e42827755c227d3ea31b0c887230db1cd411e8bddf84f16341a989de2d352c51", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "f7ac16748be2674ec14df532a3e48d0c6f215017b537f14ca3feb837dbe86292", "variant": null }, "cpython-3.13.5-linux-x86_64-musl": { @@ -6539,8 +7371,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "a652ff101318b7bd7a06181df679e2e76d592ebe70dbc4ca5db97b572889d93f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "8ba1a81e371263b668667ca66234c0422dfff212005a5fb0ced0dbc5011be942", "variant": null }, "cpython-3.13.5-linux-x86_64_v2-gnu": { @@ -6555,8 +7387,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "dd945e6178236e2eee27b9de8e6d0b2ef9c6f905185a177676d608e42d81bebb", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "463bd4ec8b202fc9acddb99764ac83c8fb9583e58acd77e2a879867b640462b6", "variant": null }, "cpython-3.13.5-linux-x86_64_v2-musl": { @@ -6571,8 +7403,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "af86120b3c3c48afdd512a798c1df2e01e7404875d5b54fc7bbde23f8b004265", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "a37f222e6f37bf32f3fa15fff2daf672c8d56ce29f490531e154351a6c6a8d61", "variant": null }, "cpython-3.13.5-linux-x86_64_v3-gnu": { @@ -6587,8 +7419,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "c13783eae63223bced84ec976be9ad87d5b2ab3d9ba80c4f678520a4763410ba", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "c35117917f126db072f128e5bf3f4eccc86c490444297357ae2f36723ec6d7d6", "variant": null }, "cpython-3.13.5-linux-x86_64_v3-musl": { @@ -6603,8 +7435,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "5e7433fd471a8d2a5dfa9b062b3c1af108eef5958e74d123de963c5d018b3086", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "94c0f64ae8cfa67692d0f954d2550a0f55e6c7426d2b86e06ae13b332b41e69c", "variant": null }, "cpython-3.13.5-linux-x86_64_v4-gnu": { @@ -6619,8 +7451,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "14a4301952bf11ddf023e27ff5810963bf5a165946009f72c18bdd53f22450c0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "ded4706563e9bc75549c0e80d009ced6e5c936bdfbd3be0102b7bdd5ecd2c88b", "variant": null }, "cpython-3.13.5-linux-x86_64_v4-musl": { @@ -6635,8 +7467,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "583b793e600a9d55b941092de2f4f7426acaac7e7430ed9a36586f7a1754a8ea", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "2460b452e49bd70c8d11673f7e92a0014ea34dcf6d69ca1d4e3808a505c04d99", "variant": null }, "cpython-3.13.5-windows-aarch64-none": { @@ -6651,8 +7483,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "0e95119f5d018ec18bcf9ee57c91e13c9ffda2a5da5fa14f578498f8ec6e4ac0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "73a16356d1f21ad15467f14f5c53a6ab9ff717f8f9dc62cf865098d893338c95", "variant": null }, "cpython-3.13.5-windows-i686-none": { @@ -6667,8 +7499,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "a877e912a7fc298e2b8ee349ed86bee00ac551232faebf258b790e334208f9d2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "09996bce9b9bd0cfb109e4208c6fb437ccb2860605218e20bb4efff531fe7092", "variant": null }, "cpython-3.13.5-windows-x86_64-none": { @@ -6683,8 +7515,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "bf9d014f24aa15f2ae37814e748773e395cbec111e368a91cdbcb4372bdff7c5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "4d88fdda4a59e0d6d45953c37c2fcb4e114dd5a2d41cb5b24b75b42ab8328ff8", "variant": null }, "cpython-3.13.5+freethreaded-darwin-aarch64-none": { @@ -6699,8 +7531,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "61862be1c897fff1d5ec772be045d1af44846ffd4a6186247cc11e5e9ae3d247", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "e6866eaa226227ff64ac0b200d6cda2c1efca3ef25c9950ee11725d22d73fff7", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-darwin-x86_64-none": { @@ -6715,8 +7547,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "a51777a7a3d4b4860dd761dbcce85a8e9589031293a2f91f4a6a3679c3d0f5a8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "606bcff8b3eadaa4d328b700ad2c3e4d44924b24eb833a85b23d7e425735fc1b", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-aarch64-gnu": { @@ -6731,8 +7563,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-aarch64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "e907a33d468de5f3936e73a0e6281a40307207acf62d59a34a1ef5a703816810", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-aarch64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "66c221e2f846e75a9191534a4228063047178ddbf808be5df25dbac6d823598a", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-armv7-gnueabi": { @@ -6747,8 +7579,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-armv7-unknown-linux-gnueabi-freethreaded%2Blto-full.tar.zst", - "sha256": "fa495608f0bb7debc53a5d7e9bd10a328e7f087bba5b14203512902ead9e6142", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-armv7-unknown-linux-gnueabi-freethreaded%2Blto-full.tar.zst", + "sha256": "a428797732b9906063a3714568fa280a6b01cd58e2899497fc283d8b079dcd51", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-armv7-gnueabihf": { @@ -6763,8 +7595,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-armv7-unknown-linux-gnueabihf-freethreaded%2Blto-full.tar.zst", - "sha256": "5316526a325b72a7e6a75f5c0ba8f2f4d1cbab8c8f0516f76055f7a178666f21", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-armv7-unknown-linux-gnueabihf-freethreaded%2Blto-full.tar.zst", + "sha256": "145c8b9c87805da541917fe7316e5b838f2951c6b1433ca36477c385e481ab39", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-powerpc64le-gnu": { @@ -6779,8 +7611,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-ppc64le-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "23770a0b9e176b8ca1bbbecd86029d4c9961fa8b88d0b0d584b14f0ad7a5dccc", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-ppc64le-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "f80e45db9796339a1253cd5361c64c6d59dabc49d00aefad4f42bc7b8975f9d2", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-riscv64-gnu": { @@ -6795,8 +7627,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-riscv64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "0f111d4619843451a0edd13e145fc3b1ea44aecf8d7a92184dcd4a9ed0a063c4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-riscv64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "681ec3b7068207bb2c1bf8af77772378eed685649e4befab17040680ea6defba", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-s390x-gnu": { @@ -6811,8 +7643,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-s390x-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "0a6df4acd93d29b0d94aa92fa46482f10bbcfe1b1e608e26909f608691c7f512", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-s390x-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "66cfa0cf73a7467525b7c193b7045ef88b2ca73ce4c91a58751a3fd67bca44bf", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64-gnu": { @@ -6827,8 +7659,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "2c49314909be249c90071a54168f80d4cbf27ecbec7d464f8743d84427c5b7b1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "f79ae7403bcfb36a1e7123707a4513e084f84a9e85a0e24186936a6e95e4f661", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64-musl": { @@ -6843,8 +7675,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "e27a15c987d616763619413b2d7122d1f4ba66a66c564c2ab4a22fb1f95c826d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "39add8abe6ead062124343124c07191378f895341c2d547fe0c3d94852c34406", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v2-gnu": { @@ -6859,8 +7691,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v2-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "6882afc2e308561b8c1a23187c0439116434aae8573fd6e6dbdce60e3af79db5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v2-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "740237f978b14cb9bac60fdc2c3c39f979a928379d0caddc3ae4189722eba2ca", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v2-musl": { @@ -6875,8 +7707,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v2-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "a8ef0d7a50a2616b2a1f8a5d7a3b52fa69085e6a75a6f7d3f318f7c132abfe16", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v2-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "1713de73b300a928ae3117da5356f558523c38427276dd7d35ca9a192ba1bc13", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v3-gnu": { @@ -6891,8 +7723,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v3-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "ab2e44c83245d18226f1fce26b09218de866048ecb515b50b8174ba75c182b4e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v3-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "0fcf05fdf3a3ac77ea081ec37a0a9cb893cf66608d8ef70e8eec60108bc77c49", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v3-musl": { @@ -6907,8 +7739,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v3-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "bad372bd5e38ff42064907b95273736137485ffdc6ff1d90b2e49f8df2829abb", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v3-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "9a726185b079d8690ee179278d43d9b435e1e5f169da67e58cb8aa8a306edd98", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v4-gnu": { @@ -6923,8 +7755,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v4-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "d12f4ecb61ae7ced3723173aa0a5ddaea395e098bfede57497426c65b5776b82", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v4-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "8443b7bebfdf9e6f344f781e43531775c7ff1140f5e3b5818c58a4aed6b03cb0", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v4-musl": { @@ -6939,8 +7771,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v4-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "734233279cbab1f882f6e6b7d1a403695379aaba7473ba865b9741b860833076", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v4-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "931d4149f9d2efc35d41836d46e9ba686b3b934003ee4d23cee0a54c1ebd995f", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-windows-aarch64-none": { @@ -6955,8 +7787,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-aarch64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", - "sha256": "51d116a7f93654d602d7e503e3c7132ae4f10e5a8e8fbe7e2ceb9e550f11051a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-aarch64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "cc39b7f33e78c737071935d551660d795ac6a2e27101b61b527174b853f8119a", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-windows-i686-none": { @@ -6971,8 +7803,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-i686-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", - "sha256": "d4461149a95fd6d9c97d01afb42561c4b687d08526c84e8ff9658d26514450eb", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-i686-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "6dc326d094e9c1f35c5c30fcdf2f4d7e549ab958992969d556fbd8168b7e4ee3", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-windows-x86_64-none": { @@ -6987,8 +7819,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", - "sha256": "eb704f14608176fc8d8d8d08ca5b7e7de14c982b12cd447727bf79b1d2b72ac7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "6881d375ea3936f9d47fa6c36a7f8d3b53eac36e5dc78da110f646d8aaef86d0", "variant": "freethreaded" }, "cpython-3.13.5+debug-linux-aarch64-gnu": { @@ -7003,8 +7835,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "defdf6ddc233f8e97cc26afaa341651791c6085a59e02a1ab14cf8a981cdc7bf", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "9ae282cc9d80eb75f6d58b4bb4037bd8de335788cb6366fb696c43f977c24c94", "variant": "debug" }, "cpython-3.13.5+debug-linux-armv7-gnueabi": { @@ -7019,8 +7851,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "69308c195ebc63543efa8f09fabb4a6fa2fc575019bd1afbc36c66858d2122c4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "c940b146382d5c79803ff0e831ff20f318253bb024cc04cdba2123635c776c72", "variant": "debug" }, "cpython-3.13.5+debug-linux-armv7-gnueabihf": { @@ -7035,8 +7867,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "ad3c911764e60a94c073c57361dc44ed1e04885652cabb1d1f3a1d11d466650d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "59268ab76c7c3b65e070d59e543a013e038aceb0f4ab5d9b16732059fe782cde", "variant": "debug" }, "cpython-3.13.5+debug-linux-powerpc64le-gnu": { @@ -7051,8 +7883,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "bd91893c42edc3b23ee45df6fff77250dab8f94646bbdf2087c0a209231f210d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "1a9a559eac62017fc9bc9c4abc97cd1d82972b3059e4a0f6794905f166d321da", "variant": "debug" }, "cpython-3.13.5+debug-linux-riscv64-gnu": { @@ -7067,8 +7899,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "7f3e649685358af0d78c8d7dcc4d357d5674e24aeaecbcc309ce83d5694821ce", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "7a8cdabe8ffa1e5e1ae07b9191ba791c121d6b537e20b7f3f449d6236d742e0f", "variant": "debug" }, "cpython-3.13.5+debug-linux-s390x-gnu": { @@ -7083,8 +7915,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "fc013b0375c357286bf6886c0160c9a7fca774869c8a5896114ac1bf338f0b2e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "18533cad293870a71779aa4c83e749607751e5e2d46d98bc037e76ec017902f7", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64-gnu": { @@ -7099,8 +7931,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "3502c7c36500fa1a84096f0e9c04dc036f3dbbae117d6b86d05b0a71a65e53cb", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "1021807e24dc396d94bd47e5bfc8d5a1d8aa28d8dc745c54dbefcaccf0c23d9a", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64-musl": { @@ -7115,8 +7947,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "b42647c29dca10e55ceeaa10b6425f4ff851721376b4b9de82ce10c21da2b5f2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "fd92b19896c8908b16e0702e1a1065f149a5e79e50f28bbe20ddda0881cf232d", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v2-gnu": { @@ -7131,8 +7963,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "5dee021b1e82ddeacae72fdee5ba6d2727faf1b39b8d4b9361a7961e5321c347", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "1054910a08a2b2c3dac81892ef5dfcac7755752f6795106b2c4a7cdcdc3ddf88", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v2-musl": { @@ -7147,8 +7979,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "623e2fedb44f5c8c123371a9e82771792d1a64ea11cb963259947679c1bb7027", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "16e251d275125bd4c1bd7bed1360f2f86baf006ee829ede9009da57c19769a4c", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v3-gnu": { @@ -7163,8 +7995,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "f24df9f31d052c4e9cabec7a897d78ceccf9fb90a6edaa6f4f128e49d5f27162", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "b86c5a8007f87755bb192886b4056dda084725c7bf31760e36f7a9ac9dbdaf40", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v3-musl": { @@ -7179,8 +8011,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "2821ef432b962ab4968e339f8d55a790eb64e266ccba674837589d58fb40f0d0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "10ec92d51f2ff26cc39e4da62ccb5faf68c1951b97f488a8c31da20ba9cc3881", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v4-gnu": { @@ -7195,8 +8027,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "8f9f953c202e0f6b5f7e7abff2b34beaff7a627d1f7ff8cdfe4d29f4fc12f067", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "7fe09bac747997c0f33d9ee6960049ae313c106311db9793aa9272c7bdf264f6", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v4-musl": { @@ -7211,8 +8043,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "5c0740e8df7d69b4e2ead4f11db97e3d884e77377d84cbf6fba58077043388fb", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "2b23d9848fe198f394a819bd4582a48639fe0a44604f093f427a921c9beab9a4", "variant": "debug" }, "cpython-3.13.4-darwin-aarch64-none": { @@ -11435,8 +12267,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "0a5748a455ebd0ef0419bffa0b239c1596ea021937fa4c9eb3b8893cf7b46d48", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "ba814d8d611898aee3c6daed53a535a93345a6b4984ebe62f9aa38646255a5bf", "variant": null }, "cpython-3.12.11-darwin-x86_64-none": { @@ -11451,8 +12283,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "1154b0be69bdd8c144272cee596181f096577d535bff1548f8df49e0d7d9c721", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "de7786cb936a0ae87ee8b89906ba7a5e02b2880d8665f7ba5d3ecff5f5e14d1f", "variant": null }, "cpython-3.12.11-linux-aarch64-gnu": { @@ -11467,8 +12299,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "73a22b9fa275682f326393df8f8afe82c302330e760bf9b4667378a3a98613ba", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "32b17b62fd621481bb4eab0ed5549cbad43d88780e29fc2c0a222a1722470c4b", "variant": null }, "cpython-3.12.11-linux-armv7-gnueabi": { @@ -11483,8 +12315,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "6a60953cc821d673bf67724d05a430576d0921a60cfceeca11af5a758bd3ae71", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "918e87c9c0ea3ca144893b6a6fcec887dba86bf571f2eb2bb488067fb4816a4b", "variant": null }, "cpython-3.12.11-linux-armv7-gnueabihf": { @@ -11499,8 +12331,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "1f8b03c8bf51f36f659961364f9d78a093af84305bbe416f95b5ecb64a11314d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "a76c795b2562c520f94930f7076d690d0b144e625f881bffc1137ae01a1c2bc5", "variant": null }, "cpython-3.12.11-linux-powerpc64le-gnu": { @@ -11515,8 +12347,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "10164c4c0e7f9a29024677226bc5f7c0b8b2b6ac5109a0d51a0fb7963f4bec48", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "79db549e29944c9077b9698646904519a5845037b507a77e018433fbe12f8a00", "variant": null }, "cpython-3.12.11-linux-riscv64-gnu": { @@ -11531,8 +12363,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "f47a3ad7d96ba16b8b38f68f69296e0dca1e910b8ff9b89dd9e9309fab9aa379", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "994e418ad826eea792e3662a546e6402450f1d4bc39509d3412ea41d37f237c3", "variant": null }, "cpython-3.12.11-linux-s390x-gnu": { @@ -11547,8 +12379,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "0714bccd13e1bfd7cce812255f4ba960b9ac5eb0a8b876daef7f8796dbd79c7a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "a55c2175aa3ee55040087f68398a43ff538db03655b4dc6f07ecdcd8b7c96ae1", "variant": null }, "cpython-3.12.11-linux-x86_64-gnu": { @@ -11563,8 +12395,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "e42c16fe50fda85dad3f5042b6d507476ea8e88c0f039018fef0680038d87c17", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "7263ed87e9d2d4c52b01f1ad8cdca315cf859c0848b8ffdcb70de205fa8b0336", "variant": null }, "cpython-3.12.11-linux-x86_64-musl": { @@ -11579,8 +12411,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "3676e47a82e674878b986a6ba05d5e2829cb8061bfda3c72258c232ad2a5c9f1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "8fcc688fac6c3e3ec1692e57587ecd790c16b33fd8e636d9fdef610dd8e7afef", "variant": null }, "cpython-3.12.11-linux-x86_64_v2-gnu": { @@ -11595,8 +12427,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "ddf0c26a2df22156672e7476fda10845056d13d4b5223de6ba054d25bfcd9d3c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "e1a6912009f7b2dcd62155b8765d028b02d9e47532c23981e9c5e61308c09671", "variant": null }, "cpython-3.12.11-linux-x86_64_v2-musl": { @@ -11611,8 +12443,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "2be8e228b2a698b66f9d96819bcc6f31ac5bdc773f6ec6dbd917ab351d665da2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "bc9dabbc25cb9103579ceea526a58b3b5be430ec80f6b1541d6247f351d0edea", "variant": null }, "cpython-3.12.11-linux-x86_64_v3-gnu": { @@ -11627,8 +12459,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "820174fbb713495a1beecd087cc651d2d4f1d10b1bb2e308c61aecec006fea0a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "be451752526dbd91d8e124c476efab447edc8f8f6aa0ce865e0bf5003853b349", "variant": null }, "cpython-3.12.11-linux-x86_64_v3-musl": { @@ -11643,8 +12475,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "5cfc247d6ee2303c98fecddfbdf6ddd2e0d44c59a033cb47a3eb6ab4bd236933", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "87cb97a3888ad1fcf782e1d491853576b9fd246775a9d6d3ad040bbd24d29152", "variant": null }, "cpython-3.12.11-linux-x86_64_v4-gnu": { @@ -11659,8 +12491,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "01519be2a0930f86a43ac93f25fb0f44b3dbf8077ecd23c98c5b3011150ef16a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "9ede735f710d6da06232c335a5e591f01ee6a79729dae911244c64318d1c9a30", "variant": null }, "cpython-3.12.11-linux-x86_64_v4-musl": { @@ -11675,8 +12507,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "93a9714ef88ece8575707e1841369b753f9d320c42832efffda8df8dfcbd9ca7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "e7a3f44a111cabe50cd9d173473accfb3bd8b7b68e37a7bb41a5e57e215d7ef1", "variant": null }, "cpython-3.12.11-windows-aarch64-none": { @@ -11691,8 +12523,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "512ae77ca0afe3a81d990c975548f052b9cde78187190eb5457b3b9cdad37a9c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "cafc536b9dfb36367710f4f2e75f52346c0de4d649ad279e61a222281ed0d459", "variant": null }, "cpython-3.12.11-windows-i686-none": { @@ -11707,8 +12539,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "c815e6eadc40013227269d4999d5aef856c4967e175beedadef60e429275be57", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "d5f7cb5ebbaa71638613949ce3119e9aeff9035248112fb14902cc1829e04f06", "variant": null }, "cpython-3.12.11-windows-x86_64-none": { @@ -11723,8 +12555,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "49911a479230f9a0ad33fc6742229128249f695502360dab3f5fd9096585e9a5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "8d8305b7d95f40a14fa9c582ef1f0e081d29694201ec66ef9002af075ffbb0e6", "variant": null }, "cpython-3.12.11+debug-linux-aarch64-gnu": { @@ -11739,8 +12571,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "aed96d0c279ff78619991fadf2ef85539d9ca208f2204ea252d3197b82092e37", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "4c30dd6eeb5c63d5ccd642f45ad1132a9757582f4ed472ad4cd7caecef1a6128", "variant": "debug" }, "cpython-3.12.11+debug-linux-armv7-gnueabi": { @@ -11755,8 +12587,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "360e6b2b9bf34d8fb086c43f3b0ce95e7918a458b491c6d85bf2624ab7e75ae3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "6aa4c4154d2023f8258850c6b1315d5903514bc477c16719d89edc3a69b7b41d", "variant": "debug" }, "cpython-3.12.11+debug-linux-armv7-gnueabihf": { @@ -11771,8 +12603,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "fffb9b6c2e81b03aa8a1d8932a351da172cd6069bbdc192f020c8862d262eab5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "b41625b1d18ed924e18cf43045090d8aef6d2c0811ff85768c1a7d11eac84f72", "variant": "debug" }, "cpython-3.12.11+debug-linux-powerpc64le-gnu": { @@ -11787,8 +12619,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "a8bed95f73ccd6451cad69163ef7097bfc17eda984d2932a93e2dda639f06ff2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "b00a755d459c322755e4d206d4b9359ebb41095cef719fe05ac37579e2ed8344", "variant": "debug" }, "cpython-3.12.11+debug-linux-riscv64-gnu": { @@ -11803,8 +12635,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "395d73e73ff0d0085ddb83f15d51375c655756e28b0e44c0266eb49f8d2b2f27", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "99e1da79b392536d38620cf19a8178cd25c92c13bcd18b172dc30cc6f73cc26b", "variant": "debug" }, "cpython-3.12.11+debug-linux-s390x-gnu": { @@ -11819,8 +12651,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "097dc82abc3805b8e1721e67869fd4ae6419fb9089d7289aec4dd61b9c834db4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "3cd187df02af10d2211fe33e89356409731e56e7ac41d9e7c93e315202b03b81", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64-gnu": { @@ -11835,8 +12667,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "d11f20d2adaa582ac3e3ab6f56a3c1f4e468e1aa4712d6fe76dd2776fdb28330", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "32c383d05e6772ef0a75ab44097478f40a51b495e9bd51c7774e15c8fc0def8b", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64-musl": { @@ -11851,8 +12683,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "a4cfaa4c7915c35ecf4a15a3f25cdda68b1e2de06280cfe98680b4eed3e11ac1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "4f6b7ab1e484ba71104a7de60bfd4a1102f990efa4be48d8860083d58bde5886", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v2-gnu": { @@ -11867,8 +12699,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "e040fa65666bd109534c8ed4c70d198954a28e87dffbab1b138a55c8c98c4db5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "9a05dd9756faf4391e4a47c04f70b805883fe50601bacc137ef59a60eb3cf35a", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v2-musl": { @@ -11883,8 +12715,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "89504b7f5fba85aa2644be63aa9377e69e56f6c6f4c57a96e0a6050e95e2b8d8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "8bee1171679235172c5d186da62547410b9ae15d6dd7c9629a8e1b753c6d3c08", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v3-gnu": { @@ -11899,8 +12731,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "5eb9cb98d4528045f1e03373373ddb783fbbf6646e3d0e683fb563e5f1d198e6", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "ab69e71a76ab3f2b6f360b63d3c7166586f88c0a03480e883a4b3b42909212e5", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v3-musl": { @@ -11915,8 +12747,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "0d463ebb5c0886e019c54e07963965ee53c52d01e42b3ca8a994e8599c2d7242", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "509bec14d1db604782693f40288af233d0dfd0aff2f6076d5ffe6f00d282a895", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v4-gnu": { @@ -11931,8 +12763,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "94924bb8ca1f03bf06c87554be2ea50ff8db47f2a3b02c5ff3b27d5a502d5fe4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "63bb17486f667a7ddd952ce34a59272405869ebb7f60f429c28b6ff5041f377e", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v4-musl": { @@ -11947,8 +12779,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "47d315cae2b1cd67155cd072410e4a6c0f428e78f09bb5da9ff7eb08480c05c4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "ba5cd4c5029d79dec11be5e563a255f2fd588965df1f4a38caa0d6a2ca5f687a", "variant": "debug" }, "cpython-3.12.10-darwin-aarch64-none": { @@ -15995,8 +16827,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "cb07230fc0946bab64762b2a97cca278c32c0fa4b1cf5c5c3eb848f08757498a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "239bc3ed7a9ebf8666f017d13096e2652d790e0ccda4fcca1e4115cbcea4882d", "variant": null }, "cpython-3.11.13-darwin-x86_64-none": { @@ -16011,8 +16843,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "1eec204b5dffad8a430c2380fd14895fad2b47406f6d69e07f00b954ffdb8064", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "aac708a735a0a7eb36b619b9ce3c8133396a26166f1b68d6ccb67514cc59b006", "variant": null }, "cpython-3.11.13-linux-aarch64-gnu": { @@ -16027,8 +16859,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "c5155a27d8e8df696eff8c39b1b37e5330f12a764fdf79b5f52ea2deb98a73a0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "71caba13c43177b4b14b9c7fabad807904f70578710357c5d885704f29ac932a", "variant": null }, "cpython-3.11.13-linux-armv7-gnueabi": { @@ -16043,8 +16875,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "680ecfd9fc09d62dbe68cfb201e567086e3df9a27d061d9bcde78fad4f7f4d94", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "72f8df85241dbe9e322a3123131fc5979770eb5cf88489a5034ee3212d8861e0", "variant": null }, "cpython-3.11.13-linux-armv7-gnueabihf": { @@ -16059,8 +16891,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "af2508bfab6c90a28d7e271e9c1cede875769556f3537fc7b0e3b6dd1f1c92b7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "34e0c80b5115fcb2a57945ee363542a4996f2c2ec9e1bf56eb4127141750b981", "variant": null }, "cpython-3.11.13-linux-powerpc64le-gnu": { @@ -16075,8 +16907,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "c83b749e3908140dec9ffadbf6b3f98bacaf4ca2230ead6adbd8a0923eebf362", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "6bd63e27ec7f1f3f228e49065474bde3806630bf1ad2a02e38818e607b103caf", "variant": null }, "cpython-3.11.13-linux-riscv64-gnu": { @@ -16091,8 +16923,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "7f0dfc489925e04ba015f170f4f30309330fae711d28bc4ed11ff13b9c3d9443", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "bf766b50fb533b12dcb9b9dbacfdd2694af26d7c545e024e895b94935b3a92c8", "variant": null }, "cpython-3.11.13-linux-s390x-gnu": { @@ -16107,8 +16939,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "603e7bad4e81cee7d4c1c9ca3cb5573036fb1d226a9a9634ca0763120740d8ff", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "5ef05a729a49219e2faec8e44db0c9b32955203e65103671e60be09b5eadd620", "variant": null }, "cpython-3.11.13-linux-x86_64-gnu": { @@ -16123,8 +16955,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "e50197b0784baaf2d47c8c8773daa4600b2809330829565e9f31e6cfbc657eae", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "a461b33c40b95e68f04bd3dcf954a7b7dad3e440ebadcfd7065539aa5647ce54", "variant": null }, "cpython-3.11.13-linux-x86_64-musl": { @@ -16139,8 +16971,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "a233b0492531f187ac33ecfd466debf21537a8b3ae90d799758808d74af09162", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "e20deee73c1ae1b8c276823184174256a347373c8a577bf263515d9454796621", "variant": null }, "cpython-3.11.13-linux-x86_64_v2-gnu": { @@ -16155,8 +16987,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "5f970ce2eecd824c367132c4fd8d066a0af3d079e46acf972e672588a578b246", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "c4eaf46061a93f88b849e406ccc26d00df1177b9688df0eab941913de6745036", "variant": null }, "cpython-3.11.13-linux-x86_64_v2-musl": { @@ -16171,8 +17003,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "a2df9657ecbecce2a50f8bb27cb8755d54c478195d49558de1c9c56f5de84033", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "a3023630c38ab2c8432ae0f48e2de01702dc2caafce597d9fd12ad932722ee57", "variant": null }, "cpython-3.11.13-linux-x86_64_v3-gnu": { @@ -16187,8 +17019,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "c30fd4073a10ac6ee0b8719d106bb6195ca73b7f85340aac6e33069869ae4ee8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "c1a1443013209af83b706105df40caf7a282b3b6b026defd1265c02562c42cc1", "variant": null }, "cpython-3.11.13-linux-x86_64_v3-musl": { @@ -16203,8 +17035,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "cd15f24848c848b058a41dd0b05c4e5beca692d2c60c962fcb912fffc690afef", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "3dda1c0b040f10045665970e3ec18bd83d51f6caefbc6a8a793deb7f3af9637e", "variant": null }, "cpython-3.11.13-linux-x86_64_v4-gnu": { @@ -16219,8 +17051,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "8c390cae0b2d163f18117cae43bcbe430e58146d97e0c39b4afe72842e55f5fc", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "a8f289ff49acf6eb75b423942bb9cc80a4b4c5962ba4a2267f790f726efbf8ed", "variant": null }, "cpython-3.11.13-linux-x86_64_v4-musl": { @@ -16235,8 +17067,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "f2ac3addbdf3c08ccf2320bdbed20213b45acd3399d44a990046f09dd883824e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "c12b4ac4dce7ab593ea987e5d6a152f8a5e46b5b1e8cab3eb5a451cda598839d", "variant": null }, "cpython-3.11.13-windows-aarch64-none": { @@ -16251,8 +17083,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "84058f18798534e76f6b9d15b96c41116aad0055e01c6e3ab2ab02db24826b9a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "47cb946dd4fd9693532450a95780c1d88b4f4052b3b9f73438cab01fadb7d3d7", "variant": null }, "cpython-3.11.13-windows-i686-none": { @@ -16267,8 +17099,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "8044a253950315481784b9f4764e1025b0d4a7a2760b7a82df849f4667113f80", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "28086a4458e9466b87ae6181253d000531901a70262fc4311492da4fda4fabae", "variant": null }, "cpython-3.11.13-windows-x86_64-none": { @@ -16283,8 +17115,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "43a574437fb7e11c439e13d84dd094fa25c741d32f9245c5ffc0e5f9523aafa9", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "3c90c0a26a969206b2cd04b1462230c23eb37ac2df70aaf1524656735c98b011", "variant": null }, "cpython-3.11.13+debug-linux-aarch64-gnu": { @@ -16299,8 +17131,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "b6ca253ced82c9575935a32d327d29dcffa9cb15963b9331c621ac91aa151933", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "a631af6864ba338a7f7e13ea36fa7b7bc3654bb3f82d0bb8a817731f1b711bce", "variant": "debug" }, "cpython-3.11.13+debug-linux-armv7-gnueabi": { @@ -16315,8 +17147,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "3e02d8ff6b63bb83a9b4cbf428d75c90d06f79df211fa176d291f3864c1e77df", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "38ac19a8924588aaadf4e3f145f05aee6eb628c8d67dd9219d23cd0d82735c9c", "variant": "debug" }, "cpython-3.11.13+debug-linux-armv7-gnueabihf": { @@ -16331,8 +17163,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "c7f9429f877d9e78a1b7e71c83b2beea38a727f239899ed325b3648e4e4cc1bf", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "ec4924d0e03dc2f262f801f33d6b998930e039c08279aad269c0fa9852364617", "variant": "debug" }, "cpython-3.11.13+debug-linux-powerpc64le-gnu": { @@ -16347,8 +17179,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "1f47dd100661489bf86befae148ce290009b91a7b62994f087136916ba4cfe4f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "a8d7973e1a8462b3dd6ab34375e3b40a0147dd859276a89fc5fe70e9b9db1c77", "variant": "debug" }, "cpython-3.11.13+debug-linux-riscv64-gnu": { @@ -16363,8 +17195,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "47c5cae609e683e59bf6aff225c06216305b939374476a4cf796d65888a00436", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "4ff68c6fa8397bba4737d41ebbb3fffbe9b4c5cf5d4f4a8651a1ec4174d4eab9", "variant": "debug" }, "cpython-3.11.13+debug-linux-s390x-gnu": { @@ -16379,8 +17211,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "7c16d22e0eeddfec0275f413ccca73c62ba55736230e889e5e78213e456bae1c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "23c3bca377d88efb3c6a19e75fd2f9efe2acecf77c4b6e018bbfbc0d8ebc625d", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64-gnu": { @@ -16395,8 +17227,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "22b0309a7232568c054790a23979f490143c2a65f5b4638b52ebfa2e02ad7b20", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "b185c5c4a55bf281e7c369ecf35c9d1ef23066b94014f42e261c437d0ddad700", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64-musl": { @@ -16411,8 +17243,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "6a3c83db95e39a68ace7515787be03e77993f023bb0c908eaed4cf79480f24d4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "4ae15443464de9337094ad34ddfc489fa97bd9892fa21be5e4e11a474e3f9228", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v2-gnu": { @@ -16427,8 +17259,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "0d7a5be35f70db94f151656a912fd66e0c001c515969007906b3f97c3fe46364", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "91fdd9bafc0c517193fd0c8870d6284a14cae691cda082259a9b23c0f6ba3f64", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v2-musl": { @@ -16443,8 +17275,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "7c4ae94fe3f488027f1a97f304ef4dbe2d83f4b97381b5d6dd5552ce01065027", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "2abd6833b156ba3a9d79ad9db89e408fdcaf8a6c72089be75d0a5515a8efd14a", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v3-gnu": { @@ -16459,8 +17291,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "5fec7d7868079bd9107c190a3187d3bffe8e3a0214d09f8ce7fbe02788f6030d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "c8262776bdf50a3b61bb6be0054faf76b0d31424d6bc155e5f20b15836db9ea8", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v3-musl": { @@ -16475,8 +17307,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "ac5f52aca1051354e336448634b8e544476198d1f8db73f0bcd6dff64267cf9e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "27f28503ee10498e9ee49c650e61e71e559e988c5b8e6186513acb1d04316cf7", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v4-gnu": { @@ -16491,8 +17323,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "467cee90b4081db0ddfef98e213bf9b69355068c2899853c7cf38bea44661fd5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "9f4d1dc4bc2ea43450ee83a840845db0e228419e2d7c4f59a0b7a14a96cf627e", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v4-musl": { @@ -16507,8 +17339,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "1ac6812cca22b1d3c70b932d5f6f6da0bc693a532e78132661f856bafcd40e2b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "41a40666e3156dba814a6f0870597e8435f931aed4f0a3108b9aeb4a8b372a5f", "variant": "debug" }, "cpython-3.11.12-darwin-aarch64-none": { @@ -20299,8 +21131,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "73939b9c93d50163cd0f1af8b3ce751c941a3a8d6eba9c08edcc9235dc5888c7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "6fead66d93847f6fd02094632150f0062400b7020c0e4554c16f4c1dee5e4617", "variant": null }, "cpython-3.10.18-darwin-x86_64-none": { @@ -20315,8 +21147,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "1ba1523d81d042a516068b98ded99d3490d3f4bb6c214fc468b62dadde88e5ac", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "2ccc64c57c3152ede369b9bd9be8ff5b259168b079a40502373a16519961e2f7", "variant": null }, "cpython-3.10.18-linux-aarch64-gnu": { @@ -20331,8 +21163,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "54c490a7f22ac03171334e5265081ca90d75ca0525b154b001f0ee96ad961c18", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "f2df9f0cfdaa36197d5ec73aa36c3b74fab9d6aee8c12e374e02b04789109fbd", "variant": null }, "cpython-3.10.18-linux-armv7-gnueabi": { @@ -20347,8 +21179,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "56ca1369651cb56221053676d206aa675ee91ddad5de71cb8de7e357f213ff59", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "3de8a3b560649f7ac9e5a4190a160d96da0f9aa683c534a8cd66c7a9f19998f5", "variant": null }, "cpython-3.10.18-linux-armv7-gnueabihf": { @@ -20363,8 +21195,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "eacff45758c90b3cdd4456a31b1217d665e122df8b5a0b8b238efcc59b8d8867", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "19ab244920eaf6aeb09aada92b636f61ed7375c1f0ab37ad9a636452ba181622", "variant": null }, "cpython-3.10.18-linux-powerpc64le-gnu": { @@ -20379,8 +21211,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "6e4180591050ec321a76ac278f9eab9c80017136293ce965229f3cbea3a1a855", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "2392c3770400b52919a24c64fc3cce27cbaf5d8b2b74c9eb51d6780dfcde8459", "variant": null }, "cpython-3.10.18-linux-riscv64-gnu": { @@ -20395,8 +21227,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "ef176d45d3199989df3563e8a578fb00084190fa139ecc752debdee7d9acc77d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "7ba47af9ca3c179588adf5aa1aece546424405ee54ae40363f776ddf22026035", "variant": null }, "cpython-3.10.18-linux-s390x-gnu": { @@ -20411,8 +21243,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "f744cbebf0cc0236fd234aa99ae799105ed2edb0a01cf3fe9991d6dd85bd157c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "3e56e6371803a2ce77d3320c6c34c246bd864b576d613b05bca5232f46380905", "variant": null }, "cpython-3.10.18-linux-x86_64-gnu": { @@ -20427,8 +21259,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "ba282bc7e494c38c7f5483437fd1108e1d55f0b24effb3eb5b28e03966667d7c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "0cab2a8a9f4f9c53a6dfed3a1448848267a8db9eac6369aa507dc6f914c7689c", "variant": null }, "cpython-3.10.18-linux-x86_64-musl": { @@ -20443,8 +21275,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "0502186e5ccc85134a2c7d11913198eb5319477da1702deb5d4b89c3f692b166", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "b440dad1091e4f95a61a4e76e4bb50be82766f134134934ef617bcba06163337", "variant": null }, "cpython-3.10.18-linux-x86_64_v2-gnu": { @@ -20459,8 +21291,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "ddd7ff4a13131c29011dd508d2f398c95977dc5c055be891835a3aa12df7acfa", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "ed1e119b5280ea06aee109b68974c1eabb662db02f8b9c8ce7f3c9c3d59effc6", "variant": null }, "cpython-3.10.18-linux-x86_64_v2-musl": { @@ -20475,8 +21307,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "feb3d0c6ddfa959948321d6ac3de32d5cde32fe50135862c65165c9415cafedf", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "07ec0c058f4cec9838c1a9327b77f9b3a253fa6c20ffccd2e319da4d59e03cc4", "variant": null }, "cpython-3.10.18-linux-x86_64_v3-gnu": { @@ -20491,8 +21323,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "69c634bf5c979ca3d6fac7e5a34613915e55fc6671bfb0dee7470f3960a649ee", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "b712e37fd4f50243afae019f0c325568129550b66598addb6dd9685d36b4625f", "variant": null }, "cpython-3.10.18-linux-x86_64_v3-musl": { @@ -20507,8 +21339,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "dbe2e101bb60277ef0f9354b7f0b1aaa85b07dec3a12ca72ae133baa080deeca", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "0b3dbed6e201d8b8dd65298aa7fcac549bb5be1cd4f50f14777b2394333b9ce5", "variant": null }, "cpython-3.10.18-linux-x86_64_v4-gnu": { @@ -20523,8 +21355,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "a6b2530a580061eb9d08168ac5e8808b8df1d2e7b8dd683c424b59cc9124a3a2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "2d64f72ebf1c398917bcf12ef5c725265ff88f16fd5fa8256ae7224d486d072c", "variant": null }, "cpython-3.10.18-linux-x86_64_v4-musl": { @@ -20539,8 +21371,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "3a2abc86a8e740d4e7dddcd697781630d9d9e6ce538095b43a4789a531f8239b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "a46d2ffa01fc06eb9da1297483159dc67f97d4d9b78f9f16ee55ef8b7189c4fa", "variant": null }, "cpython-3.10.18-windows-i686-none": { @@ -20555,8 +21387,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "1326fb50a7f39ff80b338a95c47acbeda30f484ee28ff168c3e395320345ee01", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "fc141a84044e9fd832642b90726ec7ec267a7cd7f4d4acef83a24fc1df6f33bf", "variant": null }, "cpython-3.10.18-windows-x86_64-none": { @@ -20571,8 +21403,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "0dec10054eefa76d4e47e8f53d9993e51a6d76252d9f8e5162b1b9805e6ffc20", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "84141f5372c52b22e6742990fea6a4584e613a6cf54c34b03327f729843676ea", "variant": null }, "cpython-3.10.18+debug-linux-aarch64-gnu": { @@ -20587,8 +21419,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "ed4d68544efef0d7c158c4464d8e3b4407a02e2ea014e76dfa65fddfd49384af", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "1ee0d7c8b3d79a543494ba2e62695118883018a0fa13dc0d9d6e9a6b0416ad58", "variant": "debug" }, "cpython-3.10.18+debug-linux-armv7-gnueabi": { @@ -20603,8 +21435,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "39fdc60b2645262ef658ebbf5edfaffd655524855d3aa35bfb05a149a271e4f5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "7a3ff754622092cdf814bdcfadbc7e7b0b42d02d30570b550b9938e36c11ab18", "variant": "debug" }, "cpython-3.10.18+debug-linux-armv7-gnueabihf": { @@ -20619,8 +21451,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "cf0c02ab4b46c9b6a0854e5bd9da9b322d8d91ae5803190b798ff15cb25ab153", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "0330cad328fe3770f137ae116def7a9e489d95a353a75704b0d7d05a4f310759", "variant": "debug" }, "cpython-3.10.18+debug-linux-powerpc64le-gnu": { @@ -20635,8 +21467,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "e9f346d7fa001e85cea92cf027b924c2095d54f7db297287b2df550f04e6c304", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "2df2b944d23f272a9f1275082e1556687d6f234eafaa01b9e288c34cc871aec2", "variant": "debug" }, "cpython-3.10.18+debug-linux-riscv64-gnu": { @@ -20651,8 +21483,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "c11eba8055c7bb643f55694fb1828d8d13e4ade2cb3ec60d8d9bb38fbf7500d8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "7e464ddca8d3c70e11b48c79c998b626a7682df8841abb55a906086f4286a3c1", "variant": "debug" }, "cpython-3.10.18+debug-linux-s390x-gnu": { @@ -20667,8 +21499,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "c7b407062dc86e011c2e3d8f5f0e1db8d8eac3124e4d0b597f561d7f7b2a8723", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "4c735b4fd557464c0eddfae430672e6b80706484d57fbe9db8975eeb7268bd57", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64-gnu": { @@ -20683,8 +21515,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "1ba2a0159629d92207966cbf2038774afd0f78cc59e94efb8a86e88a32563bdd", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "42eb98ea98120e90c73da704f25bcb60bf920a0bd533d2ede80e0d39708e2867", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64-musl": { @@ -20699,8 +21531,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "ebee02e3380e50e394962697dc4d4c845f60ac356da88f671be563ef0dafaa9b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "9afaa3f3920e6366f41eb3b564fb29647b25db8c9362474d98e14da27febd06f", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v2-gnu": { @@ -20715,8 +21547,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "4de984931af2c4a2b18139ff123843671c5037900524065c2fef26ff3d1a5771", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "dd828155d7a63203dde4a45863d1154ee97a8b7df9cc1d2356e89bb2f00ce995", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v2-musl": { @@ -20731,8 +21563,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "fd97d5565e0fb98ad78db65f107789e287f84c53f4d9f3ccb37fdd5f3849288b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "af251c36898898b76a4593b83e3f1ebc2b3c5b6c5a11d1d5cfb99de6fb2a29ac", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v3-gnu": { @@ -20747,8 +21579,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "ea450da681ab3fdef0da5181d90ebff7331ce1f7f827bb3b56657badc4127fad", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "945c6cae2687238a2c48d964ecb199411f10af1f7235df483edc8bee62307112", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v3-musl": { @@ -20763,8 +21595,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "ff9fe8b880460ce9529db369e2becca20a7e6a042df2deba2277e35c5cdcd35a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "17162586ce68af3912e1c57ac398498e7e3c343520b0457fa9e93e8ced1da630", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v4-gnu": { @@ -20779,8 +21611,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "c1a1d9661cf1d45096478fefd1e70ff6d0cbc419194cf094414d24fa336f5116", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "a79f755a80f18e567362b1079a8707a291965ac6b6e71d7d96a84cf618b468f1", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v4-musl": { @@ -20795,8 +21627,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "2bf809a85ffc45a37b32d5107f1a3ee8a6d12f07bb5fd3ad26ba16501418a8a7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "d1567db39cf0b3a131ced96c323657d1a2c3d13a40e2518c827f3ed00176a93e", "variant": "debug" }, "cpython-3.10.17-darwin-aarch64-none": { @@ -25739,8 +26571,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "3ab0d1885fee62dadc1123f0b23814e51b6abe5dcf6182a0c9af6cfc69764741", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "8947d8995f8062b0e732adbfb99a101b1f6c52bba8a5dd871feb3135f6be9056", "variant": null }, "cpython-3.9.23-darwin-x86_64-none": { @@ -25755,8 +26587,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "0fbb8bcc5d203b83ba1e63f9b8b1debe9162c22dd0f7481543f310b298255d6a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "2ef17a66acaa84106e97bb5c3767203e57d3a89734bee2aa0115a38ec539f418", "variant": null }, "cpython-3.9.23-linux-aarch64-gnu": { @@ -25771,8 +26603,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "da2e4a73d7318241031d87da2acb7da99070f94d715b8c9f8c973a5d586b20a6", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "647ccfa3315e0d30c4f66e2a7336bae40cfa48c1cdbcae9889805f8ea4a0d2bd", "variant": null }, "cpython-3.9.23-linux-armv7-gnueabi": { @@ -25787,8 +26619,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "41599a37d0f6fa48b44183d15a7c98a299839b83fa28774ff3f01d28500da9a6", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "3e5ba485c18a256c3d7f9916b1549a4d12c4f9f0b0df981d72d4dc637706f89b", "variant": null }, "cpython-3.9.23-linux-armv7-gnueabihf": { @@ -25803,8 +26635,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "2263daa7d9cda3e53449091dc86aa7931409721031bad1a1a160b214777c5cd6", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "ee2a398cc0fe5ec94cad0546ff7e1388052b712de081aaec255a83cde4ec5634", "variant": null }, "cpython-3.9.23-linux-powerpc64le-gnu": { @@ -25819,8 +26651,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "fc068ac5cf5e4effc74e2b63e34c2618e5a838737a19ca8f7f17cc2f10e44f26", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "0ce497e9cc4481d060106afea1d413860b3d5cdcbbca42027f3d8cccf5e3f676", "variant": null }, "cpython-3.9.23-linux-riscv64-gnu": { @@ -25835,8 +26667,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "5475f1106abed1b1163fa7964f8f8e834cbdafc26ddb9ab79cc5c10fb8110457", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "5728b6bf3053fda65d5eee7c070de52ac3af9d0001cd1ebebb62677f2ef2d683", "variant": null }, "cpython-3.9.23-linux-s390x-gnu": { @@ -25851,8 +26683,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "2d571c79b0722488b4980badb163ebd83e48b02b5a125239c67239df8dd37476", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "b1d383ca066cada90c4a4961604183aebf4bd98052c019b714b5d38f463d83bc", "variant": null }, "cpython-3.9.23-linux-x86_64-gnu": { @@ -25867,8 +26699,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "7932256affbd8fe7e055fb54715dae47e4557919bfe84bb8f33260a7a792633a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "d7b9149e48776712fdcb314e0509d81277ba3d068fcf247d0ee8ad255896be42", "variant": null }, "cpython-3.9.23-linux-x86_64-musl": { @@ -25883,8 +26715,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "64c4bb8c76b50f264a6900f3391156efd0c39ad75447f1b561aa0b150069e361", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "b1c8a6eda45429ffaeac05781079c9c30c4063e35e3c8e54b047ebbe74b6eb15", "variant": null }, "cpython-3.9.23-linux-x86_64_v2-gnu": { @@ -25899,8 +26731,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "c2bdab1548c60ed0bda4c69bea6dd17569c1d681065ed5ec5395175ed165f47a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "7772eb5f4c2b9765ca0972fada1a3f740b1c70c003172bc96b29c63d6b2428d5", "variant": null }, "cpython-3.9.23-linux-x86_64_v2-musl": { @@ -25915,8 +26747,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "61b59f2c19575acd088e1d63ca95e810e8e2b1af20f37d7acebf90f864c22ca4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "578be71cf430d079f6b75179fa323db2f01e5778eb332995c8d96ec0fe067a08", "variant": null }, "cpython-3.9.23-linux-x86_64_v3-gnu": { @@ -25931,8 +26763,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "f791037703a7370783c853bb406034532599ff561dfbf5bc67d44323d131b3c3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "acd3b61d78ef6cff586adf2a1059a0dafcdcf9074ef647e9ff4a99cc3b90fae9", "variant": null }, "cpython-3.9.23-linux-x86_64_v3-musl": { @@ -25947,8 +26779,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "88c3ad43158942c232039752e4d269cd89e282795e4c7f863f76f3e307b852f4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "52d64841dcd93a7d10bbb4684627f00bf591a9a55e61fe75c5a659c28c0a49be", "variant": null }, "cpython-3.9.23-linux-x86_64_v4-gnu": { @@ -25963,8 +26795,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "0a71dcb46a9ff949f7672f65090d210ee79d80846f10629e3f234eb7f5fe58e8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "9c508afcc09f802e83d83cee6e1e7899546c8de5368ec34e9e816ff2555a0560", "variant": null }, "cpython-3.9.23-linux-x86_64_v4-musl": { @@ -25979,8 +26811,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "cd574a9a36a729aa964e1c52bb3084a36350d905c4d16427d85dd3f80e1b3dcd", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "b85bfb0221b60a7b62bae10d8dffd92b1c2eb762017331e49be5141409ef9d8b", "variant": null }, "cpython-3.9.23-windows-i686-none": { @@ -25995,8 +26827,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "f5b6a6185ed80463160cbd95e520d8d741873736d816ac314d3e08d61f4df222", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "a0dfc3efd785f93c9d9cddc0f6cf28cae0f2c3da9d96f84ad680ae689be447bc", "variant": null }, "cpython-3.9.23-windows-x86_64-none": { @@ -26011,8 +26843,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "a8f80f8da7901fba2b271cdc5351a79b3d12fd95ee50cc4fe78410dc693eb150", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "0bcf12b809db5252e0f1f6f79d6752f658111c36224d2f8a3fc3202520923ddd", "variant": null }, "cpython-3.9.23+debug-linux-aarch64-gnu": { @@ -26027,8 +26859,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "c00ba3d83356c187e39c9d6b1541733299a675663690dc1b49c62a152d2db191", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "e6055d1ad101abced96cd1271cedf0887da9103d62a5bd6f6cdcd1aaccb45b77", "variant": "debug" }, "cpython-3.9.23+debug-linux-armv7-gnueabi": { @@ -26043,8 +26875,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "eb4875c6220036fd1b40af4d885823057122d61fc60f0b2c364065259adad0cc", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "b1d8fa8e4bdda0bfa4f0946a0912e2d113169330a0c151624fd33bbb6298d2cf", "variant": "debug" }, "cpython-3.9.23+debug-linux-armv7-gnueabihf": { @@ -26059,8 +26891,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "eca68cac8c0880f08de5c1bcae91ff0bd7fe64e5788a433fc182a5e037af671c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "f13a8960904b06c42217f8b907d0e671fc7205d57c3fbf14306080c42c2b4ff9", "variant": "debug" }, "cpython-3.9.23+debug-linux-powerpc64le-gnu": { @@ -26075,8 +26907,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "5ffc8d84b6098cfa5e2e3aaedcc3e130809d5caa1958d5155995ed3df15d8cc7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "fc8baa7e99a0bab0fcd5b1de0e3884b6324d259b5354da268242cdbce4c77040", "variant": "debug" }, "cpython-3.9.23+debug-linux-riscv64-gnu": { @@ -26091,8 +26923,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "d7f38d5539d7a0b15ce6071ba3290ce1a4ac2da3bd490d023b4d7b36c6c33c89", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "ebb2f0d68293e8f39202c805483bab2271226e2785ea37b0d776aadc5d90bb8e", "variant": "debug" }, "cpython-3.9.23+debug-linux-s390x-gnu": { @@ -26107,8 +26939,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "14250195a8c4c42fa9b22e7ca70ac5be3fe5e0ca81239c0672043eddeb6bb96e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "0bf3a1313c1aa3461c4fb81d26a44ae1fb1b72b42d2c24a0d8af28cc31292c8e", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64-gnu": { @@ -26123,8 +26955,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "846ad94f04ca8413762e6cfaee752156bbaa75f3ec030bcc235453f708e3577c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "b5ebc8b3cb05b8a3c94130775cec49b277386ce6ce0983101bb80793c02d62fe", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64-musl": { @@ -26139,8 +26971,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "4ef30683e0dd6a08a6ef591ab37a218baa42a7352f5c3951131538ab0ef83865", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "e8ad1356859bdf010359aa2daaf749fc863000b945f33adf17604389db031410", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v2-gnu": { @@ -26155,8 +26987,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "8964daf898c112bc5caa9499e8d1ba4c0d82911b4c3e07044c7f5abf489b97c6", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "cd245adb4a349c1a8e11150b2013ff0adc3922e557f3680234cbfe239b18c3af", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v2-musl": { @@ -26171,8 +27003,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "868f2f3e994992a1b68eb051fa2678a2e57bbbe1fcfc9f48461b0d2d87c5b6a8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "9852c5fea7168fccc6cd4c7505e531ba8e57ca835d7e8fc9148bf9982cf3e92b", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v3-gnu": { @@ -26187,8 +27019,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "1616c6f535b6edf4160ee97b9beca8146f9cd77a4de8c240a0a3f095a09795e9", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "d569d700200799f9f102a25405dd25ad6f06a0fc073226fb7e08d328fa320759", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v3-musl": { @@ -26203,8 +27035,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "1f9d7987734042d04badc60686f5503eb373ea8b7b7f3ade6a58a37f7d808265", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "5e5718ceb023f92530d4d77349105e04c19e3de3dccf6463c79f43de8d060fc2", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v4-gnu": { @@ -26219,8 +27051,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "4b8f925b20b6b74c1eb48fa869ee79cde20745fb93c83776e5c71924448e7e53", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "84e2177a19f711ae96ddf9d76efd14699bc080e09d3570e1bea03ed248d7bbd3", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v4-musl": { @@ -26235,8 +27067,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "ecab1905698e5dd4a11c46a1dc6be49cf0e37f70b81191adbb7dad6e453906cb", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "539cd3927ba002c80096b5d5744cdc566850ef579bfd6281dbc99eda699956d2", "variant": "debug" }, "cpython-3.9.22-darwin-aarch64-none": { diff --git a/crates/uv-python/src/sysconfig/generated_mappings.rs b/crates/uv-python/src/sysconfig/generated_mappings.rs index 646501b07..58cde0f6d 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)] diff --git a/crates/uv/tests/it/python_install.rs b/crates/uv/tests/it/python_install.rs index 19b3a5c7f..41b046026 100644 --- a/crates/uv/tests/it/python_install.rs +++ b/crates/uv/tests/it/python_install.rs @@ -2055,8 +2055,8 @@ fn python_install_314() { ----- stdout ----- ----- stderr ----- - Installed Python 3.14.0b4 in [TIME] - + cpython-3.14.0b4-[PLATFORM] (python3.14) + Installed Python 3.14.0rc1 in [TIME] + + cpython-3.14.0rc1-[PLATFORM] (python3.14) "); // Install a specific pre-release @@ -2087,7 +2087,7 @@ fn python_install_314() { success: true exit_code: 0 ----- stdout ----- - [TEMP_DIR]/managed/cpython-3.14.0b4-[PLATFORM]/[INSTALL-BIN]/[PYTHON] + [TEMP_DIR]/managed/cpython-3.14.0rc1-[PLATFORM]/[INSTALL-BIN]/[PYTHON] ----- stderr ----- "); @@ -2097,7 +2097,7 @@ fn python_install_314() { success: true exit_code: 0 ----- stdout ----- - [TEMP_DIR]/managed/cpython-3.14.0b4-[PLATFORM]/[INSTALL-BIN]/[PYTHON] + [TEMP_DIR]/managed/cpython-3.14.0rc1-[PLATFORM]/[INSTALL-BIN]/[PYTHON] ----- stderr ----- "); @@ -2106,7 +2106,7 @@ fn python_install_314() { success: true exit_code: 0 ----- stdout ----- - [TEMP_DIR]/managed/cpython-3.14.0b4-[PLATFORM]/[INSTALL-BIN]/[PYTHON] + [TEMP_DIR]/managed/cpython-3.14.0rc1-[PLATFORM]/[INSTALL-BIN]/[PYTHON] ----- stderr ----- "); From f7ac6875c3436c6ccaffe210b52fc7d68119e303 Mon Sep 17 00:00:00 2001 From: konsti Date: Wed, 23 Jul 2025 18:52:39 +0200 Subject: [PATCH 327/349] Improve concurrency safety of Python downloads into cache (#14846) --- crates/uv-python/src/downloads.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/uv-python/src/downloads.rs b/crates/uv-python/src/downloads.rs index ad516d096..05bf17cd1 100644 --- a/crates/uv-python/src/downloads.rs +++ b/crates/uv-python/src/downloads.rs @@ -988,7 +988,11 @@ impl ManagedPythonDownload { archive_writer.flush().await?; } // Move the completed file into place, invalidating the `File` instance. - fs_err::rename(&temp_file, target_cache_file)?; + match rename_with_retry(&temp_file, target_cache_file).await { + Ok(()) => {} + Err(_) if target_cache_file.is_file() => {} + Err(err) => return Err(err.into()), + } Ok(()) } From 09549c2e71b6a486ba342a4563b3a19b0e0d39e0 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Wed, 23 Jul 2025 16:00:56 -0400 Subject: [PATCH 328/349] Use `cache_index_credentials` in `uv venv` (#14854) --- crates/uv/src/commands/venv.rs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/crates/uv/src/commands/venv.rs b/crates/uv/src/commands/venv.rs index 9d3b87fe1..cd44924ab 100644 --- a/crates/uv/src/commands/venv.rs +++ b/crates/uv/src/commands/venv.rs @@ -1,7 +1,6 @@ use std::fmt::Write; use std::path::{Path, PathBuf}; use std::str::FromStr; -use std::sync::Arc; use std::vec; use anyhow::Result; @@ -224,15 +223,7 @@ pub(crate) async fn venv( let interpreter = venv.interpreter(); // Add all authenticated sources to the cache. - for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials() { - let credentials = Arc::new(credentials); - uv_auth::store_credentials(index.raw_url(), credentials.clone()); - if let Some(root_url) = index.root_url() { - uv_auth::store_credentials(&root_url, credentials.clone()); - } - } - } + index_locations.cache_index_credentials(); // Instantiate a client. let client = RegistryClientBuilder::try_from(client_builder)? From 4dd039208661f9aefc1500079e7f2598c435da37 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Wed, 23 Jul 2025 16:01:10 -0400 Subject: [PATCH 329/349] Avoid writing redacted credentials to tool receipt (#14855) ## Summary Right now, we write index URLs to the tool receipt with redacted credentials (i.e., a username, and `****` in lieu of a password). This is always wrong and unusable. Instead, this PR drops them entirely. Part of https://github.com/astral-sh/uv/issues/14806. --- crates/uv-distribution-types/src/index_url.rs | 9 ++- crates/uv-redacted/src/lib.rs | 19 +++++ crates/uv/tests/it/tool_install.rs | 80 +++++++++++++++++++ 3 files changed, 107 insertions(+), 1 deletion(-) diff --git a/crates/uv-distribution-types/src/index_url.rs b/crates/uv-distribution-types/src/index_url.rs index 6baca1c1f..bc0fcf59c 100644 --- a/crates/uv-distribution-types/src/index_url.rs +++ b/crates/uv-distribution-types/src/index_url.rs @@ -203,7 +203,11 @@ impl serde::ser::Serialize for IndexUrl { where S: serde::ser::Serializer, { - self.to_string().serialize(serializer) + match self { + Self::Pypi(url) => url.without_credentials().serialize(serializer), + Self::Url(url) => url.without_credentials().serialize(serializer), + Self::Path(url) => url.without_credentials().serialize(serializer), + } } } @@ -404,6 +408,9 @@ impl<'a> IndexLocations { } else { let mut indexes = vec![]; + // TODO(charlie): By only yielding the first default URL, we'll drop credentials if, + // e.g., an authenticated default URL is provided in a configuration file, but an + // unauthenticated default URL is present in the receipt. let mut seen = FxHashSet::default(); let mut default = false; for index in { diff --git a/crates/uv-redacted/src/lib.rs b/crates/uv-redacted/src/lib.rs index 5c9a8e278..a0534c46d 100644 --- a/crates/uv-redacted/src/lib.rs +++ b/crates/uv-redacted/src/lib.rs @@ -1,5 +1,6 @@ use ref_cast::RefCast; use serde::{Deserialize, Serialize}; +use std::borrow::Cow; use std::fmt::{Debug, Display}; use std::ops::{Deref, DerefMut}; use std::str::FromStr; @@ -98,6 +99,24 @@ impl DisplaySafeUrl { let _ = self.0.set_password(None); } + /// Returns the URL with any credentials removed. + pub fn without_credentials(&self) -> Cow<'_, Url> { + if self.0.password().is_none() && self.0.username() == "" { + return Cow::Borrowed(&self.0); + } + + // For URLs that use the `git` convention (i.e., `ssh://git@github.com/...`), avoid dropping the + // username. + if is_ssh_git_username(&self.0) { + return Cow::Borrowed(&self.0); + } + + let mut url = self.0.clone(); + let _ = url.set_username(""); + let _ = url.set_password(None); + Cow::Owned(url) + } + /// Returns [`Display`] implementation that doesn't mask credentials. #[inline] pub fn displayable_with_credentials(&self) -> impl Display { diff --git a/crates/uv/tests/it/tool_install.rs b/crates/uv/tests/it/tool_install.rs index 6a2d38db8..00cc17c04 100644 --- a/crates/uv/tests/it/tool_install.rs +++ b/crates/uv/tests/it/tool_install.rs @@ -3629,3 +3629,83 @@ fn tool_install_mismatched_name() { error: Package name (`black`) provided with `--from` does not match install request (`flask`) "###); } + +/// When installing from an authenticated index, the credentials should be omitted from the receipt. +#[test] +fn tool_install_credentials() { + let context = TestContext::new("3.12") + .with_exclude_newer("2025-01-18T00:00:00Z") + .with_filtered_counts() + .with_filtered_exe_suffix(); + let tool_dir = context.temp_dir.child("tools"); + let bin_dir = context.temp_dir.child("bin"); + + // Install `executable-application` + uv_snapshot!(context.filters(), context.tool_install() + .arg("executable-application") + .arg("--index") + .arg("https://public:heron@pypi-proxy.fly.dev/basic-auth/simple") + .env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str()) + .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()) + .env(EnvVars::PATH, bin_dir.as_os_str()), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved [N] packages in [TIME] + Prepared [N] packages in [TIME] + Installed [N] packages in [TIME] + + executable-application==0.3.0 + Installed 1 executable: app + "###); + + tool_dir + .child("executable-application") + .assert(predicate::path::is_dir()); + tool_dir + .child("executable-application") + .child("uv-receipt.toml") + .assert(predicate::path::exists()); + + let executable = bin_dir.child(format!("app{}", std::env::consts::EXE_SUFFIX)); + assert!(executable.exists()); + + // On Windows, we can't snapshot an executable file. + #[cfg(not(windows))] + insta::with_settings!({ + filters => context.filters(), + }, { + // Should run black in the virtual environment + assert_snapshot!(fs_err::read_to_string(executable).unwrap(), @r###" + #![TEMP_DIR]/tools/executable-application/bin/python + # -*- coding: utf-8 -*- + import sys + from executable_application import main + if __name__ == "__main__": + if sys.argv[0].endswith("-script.pyw"): + sys.argv[0] = sys.argv[0][:-11] + elif sys.argv[0].endswith(".exe"): + sys.argv[0] = sys.argv[0][:-4] + sys.exit(main()) + "###); + + }); + + insta::with_settings!({ + filters => context.filters(), + }, { + // We should have a tool receipt + assert_snapshot!(fs_err::read_to_string(tool_dir.join("executable-application").join("uv-receipt.toml")).unwrap(), @r#" + [tool] + requirements = [{ name = "executable-application" }] + entrypoints = [ + { name = "app", install-path = "[TEMP_DIR]/bin/app" }, + ] + + [tool.options] + index = [{ url = "https://pypi-proxy.fly.dev/basic-auth/simple", explicit = false, default = false, format = "simple", authenticate = "auto" }] + exclude-newer = "2025-01-18T00:00:00Z" + "#); + }); +} From faa12f50ceea52c37f1f59e35ff704898b9f24b3 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Wed, 23 Jul 2025 17:23:51 -0400 Subject: [PATCH 330/349] Respect credentials from all defined indexes (#14858) ## Summary The core problem here is that `allowed_indexes` only includes at most one "default" index. This is problematic for tool upgrades, since the index in the receipt will be marked as default, but credentials will be omitted; if credentials are then defined in a `uv.toml`, we'll never look at those, since that will _also_ be marked as default, and we only look at the first default. Instead, we should consider all defined indexes in priority order. Closes https://github.com/astral-sh/uv/issues/14806. --- crates/uv-distribution-types/src/index_url.rs | 26 +++- crates/uv/tests/it/tool_install.rs | 123 ++++++++++++++++++ 2 files changed, 146 insertions(+), 3 deletions(-) diff --git a/crates/uv-distribution-types/src/index_url.rs b/crates/uv-distribution-types/src/index_url.rs index bc0fcf59c..a75f1ea1b 100644 --- a/crates/uv-distribution-types/src/index_url.rs +++ b/crates/uv-distribution-types/src/index_url.rs @@ -400,8 +400,8 @@ impl<'a> IndexLocations { /// /// This includes explicit indexes, implicit indexes, flat indexes, and the default index. /// - /// The indexes will be returned in the order in which they were defined, such that the - /// last-defined index is the last item in the vector. + /// The indexes will be returned in the reverse of the order in which they were defined, such + /// that the last-defined index is the first item in the vector. pub fn allowed_indexes(&'a self) -> Vec<&'a Index> { if self.no_index { self.flat_index.iter().rev().collect() @@ -436,9 +436,29 @@ impl<'a> IndexLocations { } } + /// Return a vector containing all known [`Index`] entries. + /// + /// This includes explicit indexes, implicit indexes, flat indexes, and default indexes; + /// in short, it includes all defined indexes, even if they're overridden by some other index + /// definition. + /// + /// The indexes will be returned in the reverse of the order in which they were defined, such + /// that the last-defined index is the first item in the vector. + pub fn known_indexes(&'a self) -> impl Iterator { + if self.no_index { + Either::Left(self.flat_index.iter().rev()) + } else { + Either::Right( + std::iter::once(&*DEFAULT_INDEX) + .chain(self.flat_index.iter().rev()) + .chain(self.indexes.iter().rev()), + ) + } + } + /// Add all authenticated sources to the cache. pub fn cache_index_credentials(&self) { - for index in self.allowed_indexes() { + for index in self.known_indexes() { if let Some(credentials) = index.credentials() { let credentials = Arc::new(credentials); uv_auth::store_credentials(index.raw_url(), credentials.clone()); diff --git a/crates/uv/tests/it/tool_install.rs b/crates/uv/tests/it/tool_install.rs index 00cc17c04..0af2510fb 100644 --- a/crates/uv/tests/it/tool_install.rs +++ b/crates/uv/tests/it/tool_install.rs @@ -3709,3 +3709,126 @@ fn tool_install_credentials() { "#); }); } + +/// When installing from an authenticated index, the credentials should be omitted from the receipt. +#[test] +fn tool_install_default_credentials() -> Result<()> { + let context = TestContext::new("3.12") + .with_exclude_newer("2025-01-18T00:00:00Z") + .with_filtered_counts() + .with_filtered_exe_suffix(); + let tool_dir = context.temp_dir.child("tools"); + let bin_dir = context.temp_dir.child("bin"); + + // Write a `uv.toml` with a default index that has credentials. + let uv_toml = context.temp_dir.child("uv.toml"); + uv_toml.write_str(indoc::indoc! {r#" + [[index]] + url = "https://public:heron@pypi-proxy.fly.dev/basic-auth/simple" + default = true + authenticate = "always" + "#})?; + + // Install `executable-application` + uv_snapshot!(context.filters(), context.tool_install() + .arg("executable-application") + .arg("--config-file") + .arg(uv_toml.as_os_str()) + .env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str()) + .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()) + .env(EnvVars::PATH, bin_dir.as_os_str()), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved [N] packages in [TIME] + Prepared [N] packages in [TIME] + Installed [N] packages in [TIME] + + executable-application==0.3.0 + Installed 1 executable: app + "###); + + tool_dir + .child("executable-application") + .assert(predicate::path::is_dir()); + tool_dir + .child("executable-application") + .child("uv-receipt.toml") + .assert(predicate::path::exists()); + + let executable = bin_dir.child(format!("app{}", std::env::consts::EXE_SUFFIX)); + assert!(executable.exists()); + + // On Windows, we can't snapshot an executable file. + #[cfg(not(windows))] + insta::with_settings!({ + filters => context.filters(), + }, { + // Should run black in the virtual environment + assert_snapshot!(fs_err::read_to_string(executable).unwrap(), @r###" + #![TEMP_DIR]/tools/executable-application/bin/python + # -*- coding: utf-8 -*- + import sys + from executable_application import main + if __name__ == "__main__": + if sys.argv[0].endswith("-script.pyw"): + sys.argv[0] = sys.argv[0][:-11] + elif sys.argv[0].endswith(".exe"): + sys.argv[0] = sys.argv[0][:-4] + sys.exit(main()) + "###); + + }); + + insta::with_settings!({ + filters => context.filters(), + }, { + // We should have a tool receipt + assert_snapshot!(fs_err::read_to_string(tool_dir.join("executable-application").join("uv-receipt.toml")).unwrap(), @r#" + [tool] + requirements = [{ name = "executable-application" }] + entrypoints = [ + { name = "app", install-path = "[TEMP_DIR]/bin/app" }, + ] + + [tool.options] + index = [{ url = "https://pypi-proxy.fly.dev/basic-auth/simple", explicit = false, default = true, format = "simple", authenticate = "always" }] + exclude-newer = "2025-01-18T00:00:00Z" + "#); + }); + + // Attempt to upgrade without providing the credentials (from the config file). + uv_snapshot!(context.filters(), context.tool_upgrade() + .arg("executable-application") + .env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str()) + .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()) + .env(EnvVars::PATH, bin_dir.as_os_str()), @r" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + error: Failed to upgrade executable-application + Caused by: Failed to fetch: `https://pypi-proxy.fly.dev/basic-auth/simple/executable-application/` + Caused by: Missing credentials for https://pypi-proxy.fly.dev/basic-auth/simple/executable-application/ + "); + + // Attempt to upgrade. + uv_snapshot!(context.filters(), context.tool_upgrade() + .arg("executable-application") + .arg("--config-file") + .arg(uv_toml.as_os_str()) + .env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str()) + .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()) + .env(EnvVars::PATH, bin_dir.as_os_str()), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Nothing to upgrade + "); + + Ok(()) +} From 30b15361e5b38981ac3e7a273031bc68a38e64e3 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 23 Jul 2025 16:52:37 -0500 Subject: [PATCH 331/349] Publish riscv64 wheels to PyPI (#14852) This reverts commit 49b450109b825ec8fdec7600c2a8f763496f70b7 from https://github.com/astral-sh/uv/pull/14009 following https://github.com/pypi/warehouse/pull/18390 --- .github/workflows/publish-pypi.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index f6e4b1b4a..7897aa245 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -28,8 +28,6 @@ jobs: pattern: wheels_uv-* path: wheels_uv merge-multiple: true - - name: Remove wheels unsupported by PyPI - run: rm wheels_uv/*riscv* - name: Publish to PyPI run: uv publish -v wheels_uv/* @@ -49,7 +47,5 @@ jobs: pattern: wheels_uv_build-* path: wheels_uv_build merge-multiple: true - - name: Remove wheels unsupported by PyPI - run: rm wheels_uv_build/*riscv* - name: Publish to PyPI run: uv publish -v wheels_uv_build/* From 1ddfcee9e9a23c720f2f8b7f7e55cc9c7055c951 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 23 Jul 2025 17:44:48 -0500 Subject: [PATCH 332/349] Fix missed stabilization of removal of registry entry during Python uninstall (#14859) Funny enough, I caught this via https://github.com/astral-sh/uv/pull/14823 --- crates/uv/src/commands/python/uninstall.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/crates/uv/src/commands/python/uninstall.rs b/crates/uv/src/commands/python/uninstall.rs index c2e2e6877..3370e1f6f 100644 --- a/crates/uv/src/commands/python/uninstall.rs +++ b/crates/uv/src/commands/python/uninstall.rs @@ -112,13 +112,11 @@ async fn do_uninstall( } if !found { // Clear any remnants in the registry - if preview.is_enabled() { - #[cfg(windows)] - { - uv_python::windows_registry::remove_orphan_registry_entries( - &installed_installations, - ); - } + #[cfg(windows)] + { + uv_python::windows_registry::remove_orphan_registry_entries( + &installed_installations, + ); } if matches!(requests.as_slice(), [PythonRequest::Default]) { From 02e103f8676c4a86740893bb73e9c36f22292fbc Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Wed, 23 Jul 2025 22:00:03 -0400 Subject: [PATCH 333/349] Respect `--with` versions over base environment versions (#14863) ## Summary This fixes a regression from https://github.com/astral-sh/uv/pull/14447 that we seemingly didn't have test coverage for. Specifically, if you have a version of a package in your project, and then install a different version with `--with`, the environment should import the `--with` version. Closes #14860. --- crates/uv/src/commands/project/run.rs | 2 +- crates/uv/tests/it/run.rs | 27 +++++++++++++++++---------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index a1476c63c..6b44bf925 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -1068,8 +1068,8 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl ephemeral_env.set_overlay(format!( "import site; site.addsitedir(\"{}\"); site.addsitedir(\"{}\");", - base_site_packages.escape_for_python(), requirements_site_packages.escape_for_python(), + base_site_packages.escape_for_python(), ))?; // N.B. The order here matters — earlier interpreters take precedence over the diff --git a/crates/uv/tests/it/run.rs b/crates/uv/tests/it/run.rs index 2e9762a60..ed48be052 100644 --- a/crates/uv/tests/it/run.rs +++ b/crates/uv/tests/it/run.rs @@ -1167,14 +1167,17 @@ fn run_with() -> Result<()> { let test_script = context.temp_dir.child("main.py"); test_script.write_str(indoc! { r" import sniffio + + print(sniffio.__version__) " })?; // Requesting an unsatisfied requirement should install it. - uv_snapshot!(context.filters(), context.run().arg("--with").arg("iniconfig").arg("main.py"), @r###" + uv_snapshot!(context.filters(), context.run().arg("--with").arg("iniconfig").arg("main.py"), @r" success: true exit_code: 0 ----- stdout ----- + 1.3.0 ----- stderr ----- Resolved 2 packages in [TIME] @@ -1186,24 +1189,26 @@ fn run_with() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); // Requesting a satisfied requirement should use the base environment. - uv_snapshot!(context.filters(), context.run().arg("--with").arg("sniffio").arg("main.py"), @r###" + uv_snapshot!(context.filters(), context.run().arg("--with").arg("sniffio").arg("main.py"), @r" success: true exit_code: 0 ----- stdout ----- + 1.3.0 ----- stderr ----- Resolved 2 packages in [TIME] Audited 2 packages in [TIME] - "###); + "); // Unless the user requests a different version. - uv_snapshot!(context.filters(), context.run().arg("--with").arg("sniffio<1.3.0").arg("main.py"), @r###" + uv_snapshot!(context.filters(), context.run().arg("--with").arg("sniffio<1.3.0").arg("main.py"), @r" success: true exit_code: 0 ----- stdout ----- + 1.2.0 ----- stderr ----- Resolved 2 packages in [TIME] @@ -1212,15 +1217,16 @@ fn run_with() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + sniffio==1.2.0 - "###); + "); // If we request a dependency that isn't in the base environment, we should still respect any // other dependencies. In this case, `sniffio==1.3.0` is not the latest-compatible version, but // we should use it anyway. - uv_snapshot!(context.filters(), context.run().arg("--with").arg("anyio").arg("main.py"), @r###" + uv_snapshot!(context.filters(), context.run().arg("--with").arg("anyio").arg("main.py"), @r" success: true exit_code: 0 ----- stdout ----- + 1.3.0 ----- stderr ----- Resolved 2 packages in [TIME] @@ -1231,13 +1237,14 @@ fn run_with() -> Result<()> { + anyio==4.3.0 + idna==3.6 + sniffio==1.3.0 - "###); + "); // Even if we run with` --no-sync`. - uv_snapshot!(context.filters(), context.run().arg("--with").arg("anyio==4.2.0").arg("--no-sync").arg("main.py"), @r###" + uv_snapshot!(context.filters(), context.run().arg("--with").arg("anyio==4.2.0").arg("--no-sync").arg("main.py"), @r" success: true exit_code: 0 ----- stdout ----- + 1.3.0 ----- stderr ----- Resolved 3 packages in [TIME] @@ -1246,7 +1253,7 @@ fn run_with() -> Result<()> { + anyio==4.2.0 + idna==3.6 + sniffio==1.3.0 - "###); + "); // If the dependencies can't be resolved, we should reference `--with`. uv_snapshot!(context.filters(), context.run().arg("--with").arg("add").arg("main.py"), @r###" From 3b595156141bcfb17d2303cb17a714e3cf843ce3 Mon Sep 17 00:00:00 2001 From: Elijah Hartvigsen <35054042+Zij-IT@users.noreply.github.com> Date: Thu, 24 Jul 2025 11:55:14 +0200 Subject: [PATCH 334/349] Fix typos in uv_build reference documentation (#14853) ## Summary Fixes both typos mentioned in #14845. ## Test Plan It wasn't :D --------- Co-authored-by: konstin --- crates/uv-build-backend/src/settings.rs | 4 ++-- docs/reference/settings.md | 4 ++-- uv.schema.json | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/uv-build-backend/src/settings.rs b/crates/uv-build-backend/src/settings.rs index 9e9e44961..33e6d8c45 100644 --- a/crates/uv-build-backend/src/settings.rs +++ b/crates/uv-build-backend/src/settings.rs @@ -155,7 +155,7 @@ pub struct BuildBackendSettings { /// with this package as build requirement use the include directory to find additional header /// files. /// - `purelib` and `platlib`: Installed to the `site-packages` directory. It is not recommended - /// to uses these two options. + /// to use these two options. // TODO(konsti): We should show a flat example instead. // ```toml // [tool.uv.build-backend.data] @@ -165,7 +165,7 @@ pub struct BuildBackendSettings { #[option( default = r#"{}"#, value_type = "dict[str, str]", - example = r#"data = { "headers": "include/headers", "scripts": "bin" }"# + example = r#"data = { headers = "include/headers", scripts = "bin" }"# )] pub data: WheelDataIncludes, } diff --git a/docs/reference/settings.md b/docs/reference/settings.md index 55d3f8ae4..6469a584b 100644 --- a/docs/reference/settings.md +++ b/docs/reference/settings.md @@ -425,7 +425,7 @@ data files are included by placing them in the Python module instead of using da with this package as build requirement use the include directory to find additional header files. - `purelib` and `platlib`: Installed to the `site-packages` directory. It is not recommended - to uses these two options. + to use these two options. **Default value**: `{}` @@ -435,7 +435,7 @@ data files are included by placing them in the Python module instead of using da ```toml title="pyproject.toml" [tool.uv.build-backend] -data = { "headers": "include/headers", "scripts": "bin" } +data = { headers = "include/headers", scripts = "bin" } ``` --- diff --git a/uv.schema.json b/uv.schema.json index d8346aab1..7ca04d4f8 100644 --- a/uv.schema.json +++ b/uv.schema.json @@ -659,7 +659,7 @@ "type": "object", "properties": { "data": { - "description": "Data includes for wheels.\n\nEach entry is a directory, whose contents are copied to the matching directory in the wheel\nin `-.data/(purelib|platlib|headers|scripts|data)`. Upon installation, this\ndata is moved to its target location, as defined by\n. Usually, small\ndata files are included by placing them in the Python module instead of using data includes.\n\n- `scripts`: Installed to the directory for executables, `/bin` on Unix or\n `\\Scripts` on Windows. This directory is added to `PATH` when the virtual\n environment is activated or when using `uv run`, so this data type can be used to install\n additional binaries. Consider using `project.scripts` instead for Python entrypoints.\n- `data`: Installed over the virtualenv environment root.\n\n Warning: This may override existing files!\n\n- `headers`: Installed to the include directory. Compilers building Python packages\n with this package as build requirement use the include directory to find additional header\n files.\n- `purelib` and `platlib`: Installed to the `site-packages` directory. It is not recommended\n to uses these two options.", + "description": "Data includes for wheels.\n\nEach entry is a directory, whose contents are copied to the matching directory in the wheel\nin `-.data/(purelib|platlib|headers|scripts|data)`. Upon installation, this\ndata is moved to its target location, as defined by\n. Usually, small\ndata files are included by placing them in the Python module instead of using data includes.\n\n- `scripts`: Installed to the directory for executables, `/bin` on Unix or\n `\\Scripts` on Windows. This directory is added to `PATH` when the virtual\n environment is activated or when using `uv run`, so this data type can be used to install\n additional binaries. Consider using `project.scripts` instead for Python entrypoints.\n- `data`: Installed over the virtualenv environment root.\n\n Warning: This may override existing files!\n\n- `headers`: Installed to the include directory. Compilers building Python packages\n with this package as build requirement use the include directory to find additional header\n files.\n- `purelib` and `platlib`: Installed to the `site-packages` directory. It is not recommended\n to use these two options.", "allOf": [ { "$ref": "#/definitions/WheelDataIncludes" From 1150de3fc57342d53848fd0c2922ca3aacad9567 Mon Sep 17 00:00:00 2001 From: konsti Date: Thu, 24 Jul 2025 14:12:36 +0200 Subject: [PATCH 335/349] uv_build: Allow non-standard entrypoint names (#14867) It seems that non-standard entrypoints are still widely used, downgrading the error to a tracing warning. Fixes #14442 --------- Co-authored-by: Ed Morley <501702+edmorley@users.noreply.github.com> --- crates/uv-build-backend/src/metadata.rs | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/crates/uv-build-backend/src/metadata.rs b/crates/uv-build-backend/src/metadata.rs index d224fd788..9ccb3f6b2 100644 --- a/crates/uv-build-backend/src/metadata.rs +++ b/crates/uv-build-backend/src/metadata.rs @@ -7,7 +7,7 @@ use std::str::FromStr; use itertools::Itertools; use serde::Deserialize; -use tracing::{debug, trace}; +use tracing::{debug, trace, warn}; use version_ranges::Ranges; use walkdir::WalkDir; @@ -54,10 +54,6 @@ pub enum ValidationError { "Entrypoint groups must consist of letters and numbers separated by dots, invalid group: `{0}`" )] InvalidGroup(String), - #[error( - "Entrypoint names must consist of letters, numbers, dots, underscores and dashes; invalid name: `{0}`" - )] - InvalidName(String), #[error("Use `project.scripts` instead of `project.entry-points.console_scripts`")] ReservedScripts, #[error("Use `project.gui-scripts` instead of `project.entry-points.gui_scripts`")] @@ -620,12 +616,14 @@ impl PyProjectToml { let _ = writeln!(writer, "[{group}]"); for (name, object_reference) in entries { - // More strict than the spec, we enforce the recommendation if !name .chars() .all(|c| c.is_alphanumeric() || c == '.' || c == '-' || c == '_') { - return Err(ValidationError::InvalidName(name.to_string())); + warn!( + "Entrypoint names should consist of letters, numbers, dots, underscores and \ + dashes; non-compliant name: `{name}`" + ); } // TODO(konsti): Validate that the object references are valid Python identifiers. @@ -1403,16 +1401,6 @@ mod tests { assert_snapshot!(script_error(&contents), @"Entrypoint groups must consist of letters and numbers separated by dots, invalid group: `a@b`"); } - #[test] - fn invalid_entry_point_name() { - let contents = extend_project(indoc! {r#" - [project.scripts] - "a@b" = "bar" - "# - }); - assert_snapshot!(script_error(&contents), @"Entrypoint names must consist of letters, numbers, dots, underscores and dashes; invalid name: `a@b`"); - } - #[test] fn invalid_entry_point_conflict_scripts() { let contents = extend_project(indoc! {r#" From 23ed31b94da8ca2ba23866cbfd13f0998e3b04f3 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 24 Jul 2025 07:35:45 -0500 Subject: [PATCH 336/349] Consolidate environment hash filtering (#14864) --- crates/uv/tests/it/common/mod.rs | 5 ++ crates/uv/tests/it/python_find.rs | 34 +++-------- crates/uv/tests/it/run.rs | 17 ++---- crates/uv/tests/it/sync.rs | 97 +++++++------------------------ 4 files changed, 39 insertions(+), 114 deletions(-) diff --git a/crates/uv/tests/it/common/mod.rs b/crates/uv/tests/it/common/mod.rs index 4c686cb77..65483ffd6 100644 --- a/crates/uv/tests/it/common/mod.rs +++ b/crates/uv/tests/it/common/mod.rs @@ -706,6 +706,11 @@ impl TestContext { ), r#"requires = ["uv_build>=[CURRENT_VERSION],<[NEXT_BREAKING]"]"#.to_string(), )); + // Filter script environment hashes + filters.push(( + r"environments-v(\d+)[\\/](\w+)-[a-z0-9]+".to_string(), + "environments-v$1/$2-[HASH]".to_string(), + )); Self { root: ChildPath::new(root.path()), diff --git a/crates/uv/tests/it/python_find.rs b/crates/uv/tests/it/python_find.rs index 41eceeb92..d2ae51e38 100644 --- a/crates/uv/tests/it/python_find.rs +++ b/crates/uv/tests/it/python_find.rs @@ -837,16 +837,7 @@ fn python_find_script() { .with_filtered_python_names() .with_filtered_exe_suffix(); - let filters = context - .filters() - .into_iter() - .chain(vec![( - r"environments-v2/[\w-]+", - "environments-v2/[HASHEDNAME]", - )]) - .collect::>(); - - uv_snapshot!(filters, context.init().arg("--script").arg("foo.py"), @r###" + uv_snapshot!(context.filters(), context.init().arg("--script").arg("foo.py"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -855,22 +846,22 @@ fn python_find_script() { Initialized script at `foo.py` "###); - uv_snapshot!(filters, context.sync().arg("--script").arg("foo.py"), @r" + uv_snapshot!(context.filters(), context.sync().arg("--script").arg("foo.py"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - Creating script environment at: [CACHE_DIR]/environments-v2/[HASHEDNAME] + Creating script environment at: [CACHE_DIR]/environments-v2/foo-[HASH] Resolved in [TIME] Audited in [TIME] "); - uv_snapshot!(filters, context.python_find().arg("--script").arg("foo.py"), @r" + uv_snapshot!(context.filters(), context.python_find().arg("--script").arg("foo.py"), @r" success: true exit_code: 0 ----- stdout ----- - [CACHE_DIR]/environments-v2/[HASHEDNAME]/[BIN]/[PYTHON] + [CACHE_DIR]/environments-v2/foo-[HASH]/[BIN]/[PYTHON] ----- stderr ----- "); @@ -936,15 +927,6 @@ fn python_find_script_no_such_version() { .with_filtered_python_names() .with_filtered_exe_suffix() .with_filtered_python_sources(); - let filters = context - .filters() - .into_iter() - .chain(vec![( - r"environments-v2/[\w-]+", - "environments-v2/[HASHEDNAME]", - )]) - .collect::>(); - let script = context.temp_dir.child("foo.py"); script .write_str(indoc! {r#" @@ -955,13 +937,13 @@ fn python_find_script_no_such_version() { "#}) .unwrap(); - uv_snapshot!(filters, context.sync().arg("--script").arg("foo.py"), @r" + uv_snapshot!(context.filters(), context.sync().arg("--script").arg("foo.py"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - Creating script environment at: [CACHE_DIR]/environments-v2/[HASHEDNAME] + Creating script environment at: [CACHE_DIR]/environments-v2/foo-[HASH] Resolved in [TIME] Audited in [TIME] "); @@ -975,7 +957,7 @@ fn python_find_script_no_such_version() { "#}) .unwrap(); - uv_snapshot!(filters, context.python_find().arg("--script").arg("foo.py"), @r" + uv_snapshot!(context.filters(), context.python_find().arg("--script").arg("foo.py"), @r" success: false exit_code: 1 ----- stdout ----- diff --git a/crates/uv/tests/it/run.rs b/crates/uv/tests/it/run.rs index ed48be052..300a87f06 100644 --- a/crates/uv/tests/it/run.rs +++ b/crates/uv/tests/it/run.rs @@ -4031,17 +4031,8 @@ fn run_active_script_environment() -> Result<()> { "# })?; - let filters = context - .filters() - .into_iter() - .chain(vec![( - r"environments-v1/main-\w+", - "environments-v1/main-[HASH]", - )]) - .collect::>(); - // Running `uv run --script` with `VIRTUAL_ENV` should _not_ warn. - uv_snapshot!(&filters, context.run() + uv_snapshot!(context.filters(), context.run() .arg("--script") .arg("main.py") .env(EnvVars::VIRTUAL_ENV, "foo"), @r###" @@ -4058,7 +4049,7 @@ fn run_active_script_environment() -> Result<()> { "###); // Using `--no-active` should also _not_ warn. - uv_snapshot!(&filters, context.run() + uv_snapshot!(context.filters(), context.run() .arg("--no-active") .arg("--script") .arg("main.py") @@ -4077,7 +4068,7 @@ fn run_active_script_environment() -> Result<()> { .assert(predicate::path::missing()); // Using `--active` should create the environment - uv_snapshot!(&filters, context.run() + uv_snapshot!(context.filters(), context.run() .arg("--active") .arg("--script") .arg("main.py") @@ -4099,7 +4090,7 @@ fn run_active_script_environment() -> Result<()> { .assert(predicate::path::is_dir()); // Requesting a different Python version should invalidate the environment - uv_snapshot!(&filters, context.run() + uv_snapshot!(context.filters(), context.run() .arg("--active") .arg("-p").arg("3.12") .arg("--script") diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index a5ea6aded..49951b42b 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -4911,17 +4911,8 @@ fn sync_active_script_environment() -> Result<()> { "# })?; - let filters = context - .filters() - .into_iter() - .chain(vec![( - r"environments-v2/script-[a-z0-9]+", - "environments-v2/script-[HASH]", - )]) - .collect::>(); - // Running `uv sync --script` with `VIRTUAL_ENV` should warn - uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").env(EnvVars::VIRTUAL_ENV, "foo"), @r" + uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py").env(EnvVars::VIRTUAL_ENV, "foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4943,7 +4934,7 @@ fn sync_active_script_environment() -> Result<()> { .assert(predicate::path::missing()); // Using `--active` should create the environment - uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").env(EnvVars::VIRTUAL_ENV, "foo").arg("--active"), @r" + uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py").env(EnvVars::VIRTUAL_ENV, "foo").arg("--active"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4963,7 +4954,7 @@ fn sync_active_script_environment() -> Result<()> { .assert(predicate::path::is_dir()); // A subsequent sync will re-use the environment - uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").env(EnvVars::VIRTUAL_ENV, "foo").arg("--active"), @r" + uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py").env(EnvVars::VIRTUAL_ENV, "foo").arg("--active"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4975,7 +4966,7 @@ fn sync_active_script_environment() -> Result<()> { "); // Requesting another Python version will invalidate the environment - uv_snapshot!(&filters, context.sync() + uv_snapshot!(context.filters(), context.sync() .arg("--script") .arg("script.py") .env(EnvVars::VIRTUAL_ENV, "foo") @@ -5017,17 +5008,8 @@ fn sync_active_script_environment_json() -> Result<()> { "# })?; - let filters = context - .filters() - .into_iter() - .chain(vec![( - r"environments-v2/script-[a-z0-9]+", - "environments-v2/script-[HASH]", - )]) - .collect::>(); - // Running `uv sync --script` with `VIRTUAL_ENV` should warn - uv_snapshot!(&filters, context.sync() + uv_snapshot!(context.filters(), context.sync() .arg("--script").arg("script.py") .arg("--output-format").arg("json") .env(EnvVars::VIRTUAL_ENV, "foo"), @r#" @@ -5073,7 +5055,7 @@ fn sync_active_script_environment_json() -> Result<()> { .assert(predicate::path::missing()); // Using `--active` should create the environment - uv_snapshot!(&filters, context.sync() + uv_snapshot!(context.filters(), context.sync() .arg("--script").arg("script.py") .arg("--output-format").arg("json") .env(EnvVars::VIRTUAL_ENV, "foo").arg("--active"), @r#" @@ -5117,7 +5099,7 @@ fn sync_active_script_environment_json() -> Result<()> { .assert(predicate::path::is_dir()); // A subsequent sync will re-use the environment - uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").env(EnvVars::VIRTUAL_ENV, "foo").arg("--active"), @r" + uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py").env(EnvVars::VIRTUAL_ENV, "foo").arg("--active"), @r" success: true exit_code: 0 ----- stdout ----- @@ -5129,7 +5111,7 @@ fn sync_active_script_environment_json() -> Result<()> { "); // Requesting another Python version will invalidate the environment - uv_snapshot!(&filters, context.sync() + uv_snapshot!(context.filters(), context.sync() .arg("--script").arg("script.py") .arg("--output-format").arg("json") .env(EnvVars::VIRTUAL_ENV, "foo") @@ -9015,16 +8997,7 @@ fn sync_script() -> Result<()> { "# })?; - let filters = context - .filters() - .into_iter() - .chain(vec![( - r"environments-v2/script-\w+", - "environments-v2/script-[HASH]", - )]) - .collect::>(); - - uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r" + uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py"), @r" success: true exit_code: 0 ----- stdout ----- @@ -9056,7 +9029,7 @@ fn sync_script() -> Result<()> { "# })?; - uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r" + uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py"), @r" success: true exit_code: 0 ----- stdout ----- @@ -9082,7 +9055,7 @@ fn sync_script() -> Result<()> { "# })?; - uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r" + uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py"), @r" success: true exit_code: 0 ----- stdout ----- @@ -9107,7 +9080,7 @@ fn sync_script() -> Result<()> { "# })?; - uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r" + uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py"), @r" success: true exit_code: 0 ----- stdout ----- @@ -9125,7 +9098,7 @@ fn sync_script() -> Result<()> { "); // `--locked` and `--frozen` should fail with helpful error messages. - uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").arg("--locked"), @r" + uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py").arg("--locked"), @r" success: false exit_code: 2 ----- stdout ----- @@ -9135,7 +9108,7 @@ fn sync_script() -> Result<()> { error: `uv sync --locked` requires a script lockfile; run `uv lock --script script.py` to lock the script "); - uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").arg("--frozen"), @r" + uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py").arg("--frozen"), @r" success: false exit_code: 2 ----- stdout ----- @@ -9165,17 +9138,8 @@ fn sync_locked_script() -> Result<()> { "# })?; - let filters = context - .filters() - .into_iter() - .chain(vec![( - r"environments-v2/script-\w+", - "environments-v2/script-[HASH]", - )]) - .collect::>(); - // Lock the script. - uv_snapshot!(&filters, context.lock().arg("--script").arg("script.py"), @r###" + uv_snapshot!(context.filters(), context.lock().arg("--script").arg("script.py"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -9235,7 +9199,7 @@ fn sync_locked_script() -> Result<()> { ); }); - uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r" + uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py"), @r" success: true exit_code: 0 ----- stdout ----- @@ -9265,7 +9229,7 @@ fn sync_locked_script() -> Result<()> { })?; // Re-run with `--locked`. - uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").arg("--locked"), @r" + uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py").arg("--locked"), @r" success: false exit_code: 1 ----- stdout ----- @@ -9276,7 +9240,7 @@ fn sync_locked_script() -> Result<()> { The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. "); - uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r" + uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py"), @r" success: true exit_code: 0 ----- stdout ----- @@ -9367,7 +9331,7 @@ fn sync_locked_script() -> Result<()> { })?; // Re-run with `--locked`. - uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").arg("--locked"), @r" + uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py").arg("--locked"), @r" success: false exit_code: 1 ----- stdout ----- @@ -9379,7 +9343,7 @@ fn sync_locked_script() -> Result<()> { The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. "); - uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r" + uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py"), @r" success: true exit_code: 0 ----- stdout ----- @@ -9424,16 +9388,7 @@ fn sync_script_with_compatible_build_constraints() -> Result<()> { "# })?; - let filters = context - .filters() - .into_iter() - .chain(vec![( - r"environments-v2/script-\w+", - "environments-v2/script-[HASH]", - )]) - .collect::>(); - - uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r" + uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py"), @r" success: true exit_code: 0 ----- stdout ----- @@ -9459,14 +9414,6 @@ fn sync_script_with_incompatible_build_constraints() -> Result<()> { let context = TestContext::new("3.9"); let test_script = context.temp_dir.child("script.py"); - let filters = context - .filters() - .into_iter() - .chain(vec![( - r"environments-v2/script-\w+", - "environments-v2/script-[HASH]", - )]) - .collect::>(); // Incompatible build constraints. test_script.write_str(indoc! { r#" @@ -9485,7 +9432,7 @@ fn sync_script_with_incompatible_build_constraints() -> Result<()> { "# })?; - uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r" + uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py"), @r" success: false exit_code: 1 ----- stdout ----- From 7e78f54e7c17804d412640f940428e6b49329232 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 24 Jul 2025 15:51:15 -0500 Subject: [PATCH 337/349] Bump version to 0.8.3 (#14875) --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ Cargo.lock | 6 +++--- crates/uv-build/Cargo.toml | 2 +- crates/uv-build/pyproject.toml | 2 +- crates/uv-version/Cargo.toml | 2 +- crates/uv/Cargo.toml | 2 +- docs/concepts/build-backend.md | 2 +- 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/pre-commit.md | 10 +++++----- pyproject.toml | 2 +- 13 files changed, 50 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a9e0af94..ab84b82c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,32 @@ +## 0.8.3 + +### Python + +- Add CPython 3.14.0rc1 + +See the [`python-build-standalone` release notes](https://github.com/astral-sh/python-build-standalone/releases/tag/20250723) for more details. + +### Enhancements + +- Allow non-standard entrypoint names in `uv_build` ([#14867](https://github.com/astral-sh/uv/pull/14867)) +- Publish riscv64 wheels to PyPI ([#14852](https://github.com/astral-sh/uv/pull/14852)) + +### Bug fixes + +- Avoid writing redacted credentials to tool receipt ([#14855](https://github.com/astral-sh/uv/pull/14855)) +- Respect `--with` versions over base environment versions ([#14863](https://github.com/astral-sh/uv/pull/14863)) +- Respect credentials from all defined indexes ([#14858](https://github.com/astral-sh/uv/pull/14858)) +- Fix missed stabilization of removal of registry entry during Python uninstall ([#14859](https://github.com/astral-sh/uv/pull/14859)) +- Improve concurrency safety of Python downloads into cache ([#14846](https://github.com/astral-sh/uv/pull/14846)) + +### Documentation + +- Fix typos in `uv_build` reference documentation ([#14853](https://github.com/astral-sh/uv/pull/14853)) +- Move the "Cargo" install method further down in docs ([#14842](https://github.com/astral-sh/uv/pull/14842)) + ## 0.8.2 ### Enhancements diff --git a/Cargo.lock b/Cargo.lock index e8da02e2a..d0bd80e35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4645,7 +4645,7 @@ dependencies = [ [[package]] name = "uv" -version = "0.8.2" +version = "0.8.3" dependencies = [ "anstream", "anyhow", @@ -4811,7 +4811,7 @@ dependencies = [ [[package]] name = "uv-build" -version = "0.8.2" +version = "0.8.3" dependencies = [ "anyhow", "uv-build-backend", @@ -6005,7 +6005,7 @@ dependencies = [ [[package]] name = "uv-version" -version = "0.8.2" +version = "0.8.3" [[package]] name = "uv-virtualenv" diff --git a/crates/uv-build/Cargo.toml b/crates/uv-build/Cargo.toml index 063939748..3e80beb1f 100644 --- a/crates/uv-build/Cargo.toml +++ b/crates/uv-build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-build" -version = "0.8.2" +version = "0.8.3" edition.workspace = true rust-version.workspace = true homepage.workspace = true diff --git a/crates/uv-build/pyproject.toml b/crates/uv-build/pyproject.toml index fcf06ed05..970950ebc 100644 --- a/crates/uv-build/pyproject.toml +++ b/crates/uv-build/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uv-build" -version = "0.8.2" +version = "0.8.3" description = "The uv build backend" authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }] requires-python = ">=3.8" diff --git a/crates/uv-version/Cargo.toml b/crates/uv-version/Cargo.toml index 3afc69a98..9fa7125e1 100644 --- a/crates/uv-version/Cargo.toml +++ b/crates/uv-version/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-version" -version = "0.8.2" +version = "0.8.3" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml index 272b5ae87..ec8cf18bd 100644 --- a/crates/uv/Cargo.toml +++ b/crates/uv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv" -version = "0.8.2" +version = "0.8.3" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } diff --git a/docs/concepts/build-backend.md b/docs/concepts/build-backend.md index ce1af212d..e7288492f 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.8.2,<0.9.0"] +requires = ["uv_build>=0.8.3,<0.9.0"] build-backend = "uv_build" ``` diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 47791cec2..4b815b9bf 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.8.2/install.sh | sh + $ curl -LsSf https://astral.sh/uv/0.8.3/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.8.2/install.ps1 | iex" + PS> powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/0.8.3/install.ps1 | iex" ``` !!! tip diff --git a/docs/guides/integration/aws-lambda.md b/docs/guides/integration/aws-lambda.md index 4046b009e..9077cb96e 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.8.2 AS uv +FROM ghcr.io/astral-sh/uv:0.8.3 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.8.2 AS uv +FROM ghcr.io/astral-sh/uv:0.8.3 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 b17ee0f1e..4ebd34ca1 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.8.2` +- `ghcr.io/astral-sh/uv:{major}.{minor}.{patch}`, e.g., `ghcr.io/astral-sh/uv:0.8.3` - `ghcr.io/astral-sh/uv:{major}.{minor}`, e.g., `ghcr.io/astral-sh/uv:0.8` (the latest patch version) @@ -75,7 +75,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.8.2-alpine`. +`ghcr.io/astral-sh/uv:{major}.{minor}-{base}`, e.g., `ghcr.io/astral-sh/uv:0.8.3-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. @@ -116,7 +116,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.8.2 /uv /uvx /bin/ +COPY --from=ghcr.io/astral-sh/uv:0.8.3 /uv /uvx /bin/ ``` !!! tip @@ -134,7 +134,7 @@ COPY --from=ghcr.io/astral-sh/uv:0.8.2 /uv /uvx /bin/ Or, with the installer: ```dockerfile -ADD https://astral.sh/uv/0.8.2/install.sh /uv-installer.sh +ADD https://astral.sh/uv/0.8.3/install.sh /uv-installer.sh ``` ### Installing a project @@ -560,5 +560,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.8.2`, or (even better) the specific image digest, + version tag, e.g., `ghcr.io/astral-sh/uv:0.8.3`, 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 932c47033..582ac344d 100644 --- a/docs/guides/integration/github.md +++ b/docs/guides/integration/github.md @@ -47,7 +47,7 @@ jobs: uses: astral-sh/setup-uv@v6 with: # Install a specific version of uv. - version: "0.8.2" + version: "0.8.3" ``` ## Setting up Python diff --git a/docs/guides/integration/pre-commit.md b/docs/guides/integration/pre-commit.md index 2e83b4822..7bf393871 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.8.2 + rev: 0.8.3 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.8.2 + rev: 0.8.3 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.8.2 + rev: 0.8.3 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.8.2 + rev: 0.8.3 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.8.2 + rev: 0.8.3 hooks: # Compile requirements - id: pip-compile diff --git a/pyproject.toml b/pyproject.toml index d18374587..547c4dd28 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "uv" -version = "0.8.2" +version = "0.8.3" 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" From cd4cf27d88e8e99464169886288a1969dbdb7857 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 24 Jul 2025 17:29:40 -0500 Subject: [PATCH 338/349] Add test cases for dependent conflicting extras (#14879) Picked from #9130 --- crates/uv/tests/it/lock_conflict.rs | 244 ++++++++++++++++++++++++++++ 1 file changed, 244 insertions(+) diff --git a/crates/uv/tests/it/lock_conflict.rs b/crates/uv/tests/it/lock_conflict.rs index d67736c88..2025dbd8b 100644 --- a/crates/uv/tests/it/lock_conflict.rs +++ b/crates/uv/tests/it/lock_conflict.rs @@ -1057,6 +1057,7 @@ fn extra_unconditional() -> Result<()> { ----- stderr ----- Resolved 6 packages in [TIME] "###); + // This should error since we're enabling two conflicting extras. uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###" success: false @@ -1652,6 +1653,249 @@ fn extra_nested_across_workspace() -> Result<()> { Ok(()) } +/// The project declares conflicting extras, but one of the extras directly depends on the other. +#[test] +fn extra_depends_on_conflicting_extra() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "example" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + + [project.optional-dependencies] + foo = ["sortedcontainers==2.3.0", "example[bar]"] + bar = ["sortedcontainers==2.4.0"] + + [tool.uv] + conflicts = [ + [ + { extra = "foo" }, + { extra = "bar" }, + ], + ] + + [build-system] + requires = ["setuptools>=42"] + build-backend = "setuptools.build_meta" + + [tool.setuptools.packages.find] + include = ["example"] + "#, + )?; + + // This should fail to resolve, because the extras are always required together and + // `example[foo]` is unusable. + uv_snapshot!(context.filters(), context.lock(), @r" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + × No solution found when resolving dependencies: + ╰─▶ Because example[foo] depends on sortedcontainers==2.3.0 and sortedcontainers==2.4.0, we can conclude that example[foo]'s requirements are unsatisfiable. + And because your project requires example[foo], we can conclude that your project's requirements are unsatisfiable. + "); + + Ok(()) +} + +/// Like [`extra_depends_on_conflicting_extra`], but the conflict between the extras is mediated by +/// another package. +#[test] +fn extra_depends_on_conflicting_extra_transitive() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "example" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + + [project.optional-dependencies] + foo = ["sortedcontainers==2.3.0", "indirection"] + bar = ["sortedcontainers==2.4.0"] + + [tool.uv] + conflicts = [ + [ + { extra = "foo" }, + { extra = "bar" }, + ], + ] + + [tool.uv.sources] + indirection = { workspace = true } + + [tool.uv.workspace] + members = ["indirection"] + + [build-system] + requires = ["setuptools>=42"] + build-backend = "setuptools.build_meta" + + [tool.setuptools.packages.find] + include = ["example"] + "#, + )?; + + // Create the indirection subproject + let subproject_dir = context.temp_dir.child("indirection"); + subproject_dir.create_dir_all()?; + + let sub_pyproject_toml = subproject_dir.child("pyproject.toml"); + sub_pyproject_toml.write_str( + r#" + [project] + name = "indirection" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["example[bar]"] + + [tool.uv.sources] + example = { workspace = true } + + [build-system] + requires = ["setuptools>=42"] + build-backend = "setuptools.build_meta" + "#, + )?; + + // This succeeds, but probably shouldn't. There's an unconditional conflict in `example[foo] + // -> indirection[bar] -> example[bar]`, which means `example[foo]` can never be used. + uv_snapshot!(context.filters(), context.lock(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 4 packages in [TIME] + "); + + let lock = context.read("uv.lock"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + lock, @r#" + version = 1 + revision = 2 + requires-python = ">=3.12" + conflicts = [[ + { package = "example", extra = "bar" }, + { package = "example", extra = "foo" }, + ]] + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [manifest] + members = [ + "example", + "indirection", + ] + + [[package]] + name = "example" + version = "0.1.0" + source = { editable = "." } + + [package.optional-dependencies] + bar = [ + { name = "sortedcontainers", version = "2.4.0", source = { registry = "https://pypi.org/simple" } }, + ] + foo = [ + { name = "indirection" }, + { name = "sortedcontainers", version = "2.3.0", source = { registry = "https://pypi.org/simple" } }, + ] + + [package.metadata] + requires-dist = [ + { name = "indirection", marker = "extra == 'foo'", editable = "indirection" }, + { name = "sortedcontainers", marker = "extra == 'bar'", specifier = "==2.4.0" }, + { name = "sortedcontainers", marker = "extra == 'foo'", specifier = "==2.3.0" }, + ] + provides-extras = ["foo", "bar"] + + [[package]] + name = "indirection" + version = "0.1.0" + source = { editable = "indirection" } + dependencies = [ + { name = "example" }, + { name = "example", extra = ["bar"], marker = "extra == 'extra-7-example-bar'" }, + ] + + [package.metadata] + requires-dist = [{ name = "example", extras = ["bar"], editable = "." }] + + [[package]] + name = "sortedcontainers" + version = "2.3.0" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/14/10/6a9481890bae97da9edd6e737c9c3dec6aea3fc2fa53b0934037b35c89ea/sortedcontainers-2.3.0.tar.gz", hash = "sha256:59cc937650cf60d677c16775597c89a960658a09cf7c1a668f86e1e4464b10a1", size = 30509, upload-time = "2020-11-09T00:03:52.258Z" } + wheels = [ + { url = "https://files.pythonhosted.org/packages/20/4d/a7046ae1a1a4cc4e9bbed194c387086f06b25038be596543d026946330c9/sortedcontainers-2.3.0-py2.py3-none-any.whl", hash = "sha256:37257a32add0a3ee490bb170b599e93095eed89a55da91fa9f48753ea12fd73f", size = 29479, upload-time = "2020-11-09T00:03:50.723Z" }, + ] + + [[package]] + name = "sortedcontainers" + version = "2.4.0" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" } + wheels = [ + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, + ] + "# + ); + }); + + // Install from the lockfile + uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + example==0.1.0 (from file://[TEMP_DIR]/) + "); + + // Install with `foo` + uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--extra").arg("foo"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Found conflicting extras `example[bar]` and `example[foo]` enabled simultaneously + "); + + // Install the child package + uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--package").arg("indirection"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Prepared 2 packages in [TIME] + Installed 2 packages in [TIME] + + indirection==0.1.0 (from file://[TEMP_DIR]/indirection) + + sortedcontainers==2.4.0 + "); + + Ok(()) +} + /// This tests a "basic" case for specifying conflicting groups. #[test] fn group_basic() -> Result<()> { From e48a9c09924e90f36e09e742711e3115e871dc29 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 24 Jul 2025 17:29:56 -0500 Subject: [PATCH 339/349] Remove redundant `let Some` (#14880) --- crates/uv-resolver/src/resolver/mod.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/uv-resolver/src/resolver/mod.rs b/crates/uv-resolver/src/resolver/mod.rs index fb4092099..04a756830 100644 --- a/crates/uv-resolver/src/resolver/mod.rs +++ b/crates/uv-resolver/src/resolver/mod.rs @@ -3779,9 +3779,7 @@ impl Fork { if self.env.included_by_group(conflicting_item) { return true; } - if let Some(conflicting_item) = dep.package.conflicting_item() { - self.conflicts.remove(&conflicting_item); - } + self.conflicts.remove(&conflicting_item); false }); Some(self) From 05031becc3762b8b298e98046d6514a6cb6fb244 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 24 Jul 2025 17:40:55 -0500 Subject: [PATCH 340/349] Fix snapshot for GitHub message (#14881) --- crates/uv/tests/it/pip_install.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index 936f77aff..7a48b61fd 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -2479,8 +2479,7 @@ fn install_git_private_https_pat_not_authorized() { ├─▶ failed to clone into: [CACHE_DIR]/git-v0/db/8401f5508e3e612d ╰─▶ process didn't exit successfully: `git fetch --force --update-head-ok 'https://git:***@github.com/astral-test/uv-private-pypackage' '+HEAD:refs/remotes/origin/HEAD'` (exit status: 128) --- stderr - remote: Support for password authentication was removed on August 13, 2021. - remote: Please see https://docs.github.com/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication. + remote: Invalid username or token. Password authentication is not supported for Git operations. fatal: Authentication failed for 'https://github.com/astral-test/uv-private-pypackage/' "); } From 1146f3f62d88bcfe6d81a45987b1fd4745b3eeb4 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Fri, 25 Jul 2025 08:18:28 -0400 Subject: [PATCH 341/349] Avoid invalidating lockfile when path or workspace dependencies define explicit indexes (#14876) ## Summary This is an alternative to #14003 that takes advantage of the fact that we already validate that the requirements are up-to-date when validating the lockfile, and the requirements for pinned requirements include the index itself -- so rather than collecting all the explicit indexes upfront, we can just add them to the available list as we iterate over the lockfile's dependency graph. This gets all the tests passing from that PR, but with ~no performance impact and a much less invasive change. It also gets the "circular dependency" test passing, which is marked with a TODO in that PR. Closes https://github.com/astral-sh/uv/issues/11419. --- crates/uv-resolver/src/lock/mod.rs | 36 ++- crates/uv/tests/it/lock.rs | 487 +++++++++++++++++++++++++++++ 2 files changed, 521 insertions(+), 2 deletions(-) diff --git a/crates/uv-resolver/src/lock/mod.rs b/crates/uv-resolver/src/lock/mod.rs index 49cb851b3..2e3ee56d4 100644 --- a/crates/uv-resolver/src/lock/mod.rs +++ b/crates/uv-resolver/src/lock/mod.rs @@ -1431,7 +1431,7 @@ impl Lock { } // Collect the set of available indexes (both `--index-url` and `--find-links` entries). - let remotes = indexes.map(|locations| { + let mut remotes = indexes.map(|locations| { locations .allowed_indexes() .into_iter() @@ -1444,7 +1444,7 @@ impl Lock { .collect::>() }); - let locals = indexes.map(|locations| { + let mut locals = indexes.map(|locations| { locations .allowed_indexes() .into_iter() @@ -1717,6 +1717,38 @@ impl Lock { return Ok(SatisfiesResult::MissingVersion(&package.id.name)); } + // Add any explicit indexes to the list of known locals or remotes. These indexes may + // not be available as top-level configuration (i.e., if they're defined within a + // workspace member), but we already validated that the dependencies are up-to-date, so + // we can consider them "available". + for requirement in &package.metadata.requires_dist { + if let RequirementSource::Registry { + index: Some(index), .. + } = &requirement.source + { + match &index.url { + IndexUrl::Pypi(_) | IndexUrl::Url(_) => { + if let Some(remotes) = remotes.as_mut() { + remotes.insert(UrlString::from( + index.url().without_credentials().as_ref(), + )); + } + } + IndexUrl::Path(url) => { + if let Some(locals) = locals.as_mut() { + if let Some(path) = url.to_file_path().ok().and_then(|path| { + relative_to(&path, root) + .or_else(|_| std::path::absolute(path)) + .ok() + }) { + locals.insert(path.into_boxed_path()); + } + } + } + } + } + } + // Recurse. for dep in &package.dependencies { if seen.insert(&dep.package_id) { diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index 8a006ffb6..9f38a168a 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -29411,3 +29411,490 @@ fn test_tilde_equals_python_version() -> Result<()> { Ok(()) } + +/// Test that lockfile validation includes explicit indexes from path dependencies. +/// +#[test] +fn lock_path_dependency_explicit_index() -> Result<()> { + let context = TestContext::new("3.12"); + + // Create the path dependency with explicit index + let pkg_a = context.temp_dir.child("pkg_a"); + fs_err::create_dir_all(&pkg_a)?; + + let pyproject_toml = pkg_a.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "pkg-a" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["iniconfig"] + + [tool.uv.sources] + iniconfig = { index = "inner-index" } + + [[tool.uv.index]] + name = "inner-index" + url = "https://pypi-proxy.fly.dev/simple" + explicit = true + "#, + )?; + + // Create a project that depends on pkg_a + let pkg_b = context.temp_dir.child("pkg_b"); + fs_err::create_dir_all(&pkg_b)?; + + let pyproject_toml = pkg_b.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "pkg-b" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["pkg-a"] + + [tool.uv.sources] + pkg-a = { path = "../pkg_a/", editable = true } + black = { index = "outer-index" } + + [[tool.uv.index]] + name = "outer-index" + url = "https://outer-index.com/simple" + explicit = true + "#, + )?; + + uv_snapshot!(context.filters(), context.lock().current_dir(&pkg_b), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + Resolved 3 packages in [TIME] + "); + + uv_snapshot!(context.filters(), context.lock().arg("--check").current_dir(&pkg_b), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + Resolved 3 packages in [TIME] + "); + + Ok(()) +} + +/// Test that lockfile validation includes explicit indexes from path dependencies +/// defined in a non-root workspace member. +#[test] +fn lock_path_dependency_explicit_index_workspace_member() -> Result<()> { + let context = TestContext::new("3.12"); + + // Create the path dependency with explicit index + let pkg_a = context.temp_dir.child("pkg_a"); + fs_err::create_dir_all(&pkg_a)?; + + let pyproject_toml = pkg_a.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "pkg-a" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["iniconfig"] + + [tool.uv.sources] + iniconfig = { index = "inner-index" } + + [[tool.uv.index]] + name = "inner-index" + url = "https://pypi-proxy.fly.dev/simple" + explicit = true + "#, + )?; + + // Create a project that depends on pkg_a + let member = context.temp_dir.child("member"); + fs_err::create_dir_all(&member)?; + + let pyproject_toml = member.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "member" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["pkg-a"] + + [tool.uv.sources] + pkg-a = { path = "../pkg_a/", editable = true } + black = { index = "middle-index" } + + [[tool.uv.index]] + name = "middle-index" + url = "https://middle-index.com/simple" + explicit = true + "#, + )?; + + // Create a root with workspace member + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "root-project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["member"] + + [tool.uv.workspace] + members = ["member"] + + [tool.uv.sources] + member = { workspace = true } + anyio = { index = "outer-index" } + + [[tool.uv.index]] + name = "outer-index" + url = "https://outer-index.com/simple" + explicit = true + "#, + )?; + + uv_snapshot!(context.filters(), context.lock(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 4 packages in [TIME] + "); + + uv_snapshot!(context.filters(), context.lock().arg("--check"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 4 packages in [TIME] + "); + + Ok(()) +} + +/// Test that lockfile validation works correctly when path dependency has +/// both explicit and non-explicit indexes. +#[test] +fn lock_path_dependency_mixed_indexes() -> Result<()> { + let context = TestContext::new("3.12"); + + // Create the path dependency with both explicit and non-explicit indexes. + let pkg_a = context.temp_dir.child("pkg_a"); + fs_err::create_dir_all(&pkg_a)?; + + let pyproject_toml = pkg_a.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "pkg-a" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["iniconfig", "anyio"] + + [tool.uv.sources] + iniconfig = { index = "explicit-index" } + anyio = { index = "non-explicit-index" } + + [[tool.uv.index]] + name = "non-explicit-index" + url = "https://pypi-proxy.fly.dev/simple" + + [[tool.uv.index]] + name = "explicit-index" + url = "https://pypi.org/simple" + explicit = true + "#, + )?; + + // Create a project that depends on pkg_a. + let pkg_b = context.temp_dir.child("pkg_b"); + fs_err::create_dir_all(&pkg_b)?; + + let pyproject_toml = pkg_b.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "pkg-b" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["pkg-a"] + + [tool.uv.sources] + pkg-a = { path = "../pkg_a/", editable = true } + black = { index = "outer-index" } + + [[tool.uv.index]] + name = "outer-index" + url = "https://outer-index.com/simple" + explicit = true + "#, + )?; + + uv_snapshot!(context.filters(), context.lock().current_dir(&pkg_b), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + Resolved 6 packages in [TIME] + "); + + uv_snapshot!(context.filters(), context.lock().arg("--check").current_dir(&pkg_b), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + Resolved 6 packages in [TIME] + "); + + Ok(()) +} + +/// Test that path dependencies without an index don't affect validation. +#[test] +fn lock_path_dependency_no_index() -> Result<()> { + let context = TestContext::new("3.12"); + + // Create the path dependency without explicit indexes. + let pkg_a = context.temp_dir.child("pkg_a"); + fs_err::create_dir_all(&pkg_a)?; + + let pyproject_toml = pkg_a.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "pkg-a" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["requests"] + "#, + )?; + + // Create a project that depends on pkg_a. + let pkg_b = context.temp_dir.child("pkg_b"); + fs_err::create_dir_all(&pkg_b)?; + + let pyproject_toml = pkg_b.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "pkg-b" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["pkg-a"] + + [tool.uv.sources] + pkg-a = { path = "../pkg_a/", editable = true } + "#, + )?; + + uv_snapshot!(context.filters(), context.lock().current_dir(&pkg_b), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + Resolved 7 packages in [TIME] + "); + + uv_snapshot!(context.filters(), context.lock().arg("--check").current_dir(&pkg_b), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + Resolved 7 packages in [TIME] + "); + + Ok(()) +} + +/// Test that a nested path dependency with an explicit index validates correctly. +#[test] +fn lock_nested_path_dependency_explicit_index() -> Result<()> { + let context = TestContext::new("3.12"); + + // Create the inner dependency with explicit index. + let pkg_a = context.temp_dir.child("pkg_a"); + fs_err::create_dir_all(&pkg_a)?; + + let pyproject_toml = pkg_a.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "pkg-a" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["iniconfig"] + + [tool.uv.sources] + iniconfig = { index = "inner-index" } + + [[tool.uv.index]] + name = "inner-index" + url = "https://pypi-proxy.fly.dev/simple" + explicit = true + "#, + )?; + + // Create intermediate dependency that depends on pkg_a. + let pkg_b = context.temp_dir.child("pkg_b"); + fs_err::create_dir_all(&pkg_b)?; + + let pyproject_toml = pkg_b.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "pkg-b" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["pkg-a"] + + [tool.uv.sources] + pkg-a = { path = "../pkg_a/", editable = true } + "#, + )?; + + // Create a project that depends on intermediate dependency. + let pkg_c = context.temp_dir.child("pkg_c"); + fs_err::create_dir_all(&pkg_c)?; + + let pyproject_toml = pkg_c.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "pkg-c" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["pkg-b"] + + [tool.uv.sources] + pkg-b = { path = "../pkg_b/", editable = true } + black = { index = "outer-index" } + + [[tool.uv.index]] + name = "outer-index" + url = "https://outer-index.com/simple" + explicit = true + "#, + )?; + + uv_snapshot!(context.filters(), context.lock().current_dir(&pkg_c), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + Resolved 4 packages in [TIME] + "); + + uv_snapshot!(context.filters(), context.lock().arg("--check").current_dir(&pkg_c), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + Resolved 4 packages in [TIME] + "); + + Ok(()) +} + +/// Test that validating circular path dependency indexes doesn't cause an infinite loop. +#[test] +fn lock_circular_path_dependency_explicit_index() -> Result<()> { + let context = TestContext::new("3.12"); + + // Create pkg_a (with explicit index) that depends on pkg_b. + let pkg_a = context.temp_dir.child("pkg_a"); + fs_err::create_dir_all(&pkg_a)?; + + let pyproject_toml = pkg_a.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "pkg-a" + version = "0.1.0" + requires-python = ">=3.10" + dependencies = ["pkg-b", "iniconfig"] + + [tool.uv.sources] + pkg-b = { path = "../pkg_b/" } + iniconfig = { index = "index-a" } + + [[tool.uv.index]] + name = "index-a" + url = "https://pypi-proxy.fly.dev/simple" + explicit = true + "#, + )?; + + // Create pkg_b that depends on pkg_a. This is a circular dependency. + let pkg_b = context.temp_dir.child("pkg_b"); + fs_err::create_dir_all(&pkg_b)?; + + let pyproject_toml = pkg_b.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "pkg-b" + version = "0.1.0" + requires-python = ">=3.10" + dependencies = ["pkg-a", "anyio"] + + [tool.uv.sources] + pkg-a = { path = "../pkg_a/" } + anyio = { index = "index-b" } + + [[tool.uv.index]] + name = "index-b" + url = "https://pypi.org/simple" + explicit = true + default = true + "#, + )?; + + // This should not hang or crash due to the circular dependency. + uv_snapshot!(context.filters(), context.lock().current_dir(&pkg_a), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + Resolved 8 packages in [TIME] + "); + + uv_snapshot!(context.filters(), context.lock().arg("--check").current_dir(&pkg_a), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + Resolved 8 packages in [TIME] + "); + + Ok(()) +} From 9376cf5482ee3befac83654ddf733e24864e269e Mon Sep 17 00:00:00 2001 From: konsti Date: Fri, 25 Jul 2025 17:18:24 +0200 Subject: [PATCH 342/349] Remove prioritized dist duplication (#14887) `Candidate` has an optional field `prioritized`, which was mostly redundant with `CandidateDist`. Specifically, it was only `None`, if `CandidateDist` was `Installed`. This commit removes this duplication. --- .../src/prioritized_distribution.rs | 18 ++++++-- crates/uv-resolver/src/candidate_selector.rs | 46 +++++++++++++------ crates/uv-resolver/src/resolver/mod.rs | 5 +- 3 files changed, 50 insertions(+), 19 deletions(-) diff --git a/crates/uv-distribution-types/src/prioritized_distribution.rs b/crates/uv-distribution-types/src/prioritized_distribution.rs index 52ac2fbd1..5046eea21 100644 --- a/crates/uv-distribution-types/src/prioritized_distribution.rs +++ b/crates/uv-distribution-types/src/prioritized_distribution.rs @@ -91,13 +91,21 @@ impl CompatibleDist<'_> { } } + // For installable distributions, return the prioritized distribution it was derived from. + pub fn prioritized(&self) -> Option<&PrioritizedDist> { + match self { + CompatibleDist::InstalledDist(_) => None, + CompatibleDist::SourceDist { prioritized, .. } + | CompatibleDist::CompatibleWheel { prioritized, .. } + | CompatibleDist::IncompatibleWheel { prioritized, .. } => Some(prioritized), + } + } + /// Return the set of supported platform the distribution, in terms of their markers. pub fn implied_markers(&self) -> MarkerTree { - match self { - CompatibleDist::InstalledDist(_) => MarkerTree::TRUE, - CompatibleDist::SourceDist { prioritized, .. } => prioritized.0.markers, - CompatibleDist::CompatibleWheel { prioritized, .. } => prioritized.0.markers, - CompatibleDist::IncompatibleWheel { prioritized, .. } => prioritized.0.markers, + match self.prioritized() { + Some(prioritized) => prioritized.0.markers, + None => MarkerTree::TRUE, } } } diff --git a/crates/uv-resolver/src/candidate_selector.rs b/crates/uv-resolver/src/candidate_selector.rs index e03302966..57776bda4 100644 --- a/crates/uv-resolver/src/candidate_selector.rs +++ b/crates/uv-resolver/src/candidate_selector.rs @@ -266,7 +266,6 @@ impl CandidateSelector { return Some(Candidate { name: package_name, version, - prioritized: None, dist: CandidateDist::Compatible(CompatibleDist::InstalledDist( dist, )), @@ -368,7 +367,6 @@ impl CandidateSelector { return Some(Candidate { name: package_name, version, - prioritized: None, dist: CandidateDist::Compatible(CompatibleDist::InstalledDist(dist)), choice_kind: VersionChoiceKind::Installed, }); @@ -546,10 +544,14 @@ impl CandidateSelector { // exclude-newer in our error messages. if matches!( candidate.dist(), - CandidateDist::Incompatible( - IncompatibleDist::Source(IncompatibleSource::ExcludeNewer(_)) - | IncompatibleDist::Wheel(IncompatibleWheel::ExcludeNewer(_)) - ) + CandidateDist::Incompatible { + incompatible_dist: IncompatibleDist::Source(IncompatibleSource::ExcludeNewer( + _ + )) | IncompatibleDist::Wheel( + IncompatibleWheel::ExcludeNewer(_) + ), + .. + } ) { continue; } @@ -572,7 +574,7 @@ impl CandidateSelector { // even though there are compatible wheels on PyPI. Thus, we need to ensure that we // return the first _compatible_ candidate across all indexes, if such a candidate // exists. - if matches!(candidate.dist(), CandidateDist::Incompatible(_)) { + if matches!(candidate.dist(), CandidateDist::Incompatible { .. }) { if incompatible.is_none() { incompatible = Some(candidate); } @@ -602,7 +604,25 @@ impl CandidateSelector { #[derive(Debug, Clone)] pub(crate) enum CandidateDist<'a> { Compatible(CompatibleDist<'a>), - Incompatible(IncompatibleDist), + Incompatible { + /// The reason the prioritized distribution is incompatible. + incompatible_dist: IncompatibleDist, + /// The prioritized distribution that had no compatible wheelr or sdist. + prioritized_dist: &'a PrioritizedDist, + }, +} + +impl CandidateDist<'_> { + /// For an installable dist, return the prioritized distribution. + fn prioritized(&self) -> Option<&PrioritizedDist> { + match self { + CandidateDist::Compatible(dist) => dist.prioritized(), + CandidateDist::Incompatible { + incompatible_dist: _, + prioritized_dist: prioritized, + } => Some(prioritized), + } + } } impl<'a> From<&'a PrioritizedDist> for CandidateDist<'a> { @@ -621,7 +641,10 @@ impl<'a> From<&'a PrioritizedDist> for CandidateDist<'a> { } else { IncompatibleDist::Unavailable }; - CandidateDist::Incompatible(dist) + CandidateDist::Incompatible { + incompatible_dist: dist, + prioritized_dist: value, + } } } } @@ -654,8 +677,6 @@ pub(crate) struct Candidate<'a> { name: &'a PackageName, /// The version of the package. version: &'a Version, - /// The prioritized distribution for the package. - prioritized: Option<&'a PrioritizedDist>, /// The distributions to use for resolving and installing the package. dist: CandidateDist<'a>, /// Whether this candidate was selected from a preference. @@ -672,7 +693,6 @@ impl<'a> Candidate<'a> { Self { name, version, - prioritized: Some(dist), dist: CandidateDist::from(dist), choice_kind, } @@ -709,7 +729,7 @@ impl<'a> Candidate<'a> { /// Return the prioritized distribution for the candidate. pub(crate) fn prioritized(&self) -> Option<&PrioritizedDist> { - self.prioritized + self.dist.prioritized() } } diff --git a/crates/uv-resolver/src/resolver/mod.rs b/crates/uv-resolver/src/resolver/mod.rs index 04a756830..6af21702a 100644 --- a/crates/uv-resolver/src/resolver/mod.rs +++ b/crates/uv-resolver/src/resolver/mod.rs @@ -1271,7 +1271,10 @@ impl ResolverState dist, - CandidateDist::Incompatible(incompatibility) => { + CandidateDist::Incompatible { + incompatible_dist: incompatibility, + prioritized_dist: _, + } => { // If the version is incompatible because no distributions are compatible, exit early. return Ok(Some(ResolverVersion::Unavailable( candidate.version().clone(), From bfb4bc2aebe1dbff9aeba84d0c96e3ffcbf0ddd1 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Fri, 25 Jul 2025 11:01:57 -0500 Subject: [PATCH 343/349] Split preview mode into separate feature flags (#14823) I think this would give us better hygiene than a global flag. It makes it easier for users to opt-in to overlapping features, such as Python upgrades and Python bin installations and to disable warnings for preview mode without opting in to a bunch of other features. In general, I want to reduce the burden for putting something under preview. The `--preview` and `--no-preview` flags are retained as global overrides. A new `--preview-features` option is added which accepts comma separated features or can be passed multiple times, e.g., `--preview-features add-bounds,pylock`. There's a `UV_PREVIEW_FEATURES` environment variable for that option (I'm not sure if we should overload `UV_PREVIEW`, but could be convinced). --- Cargo.lock | 2 + crates/uv-bench/benches/uv.rs | 4 +- crates/uv-build-frontend/src/lib.rs | 4 +- crates/uv-cli/src/lib.rs | 25 +- crates/uv-configuration/Cargo.toml | 2 + crates/uv-configuration/src/preview.rs | 248 ++++- crates/uv-dev/src/compile.rs | 4 +- crates/uv-dispatch/src/lib.rs | 6 +- crates/uv-python/src/discovery.rs | 14 +- crates/uv-python/src/environment.rs | 4 +- crates/uv-python/src/installation.rs | 10 +- crates/uv-python/src/lib.rs | 168 ++-- crates/uv-python/src/managed.rs | 12 +- crates/uv-static/src/env_vars.rs | 3 + crates/uv-tool/src/lib.rs | 4 +- crates/uv-virtualenv/src/lib.rs | 4 +- crates/uv-virtualenv/src/virtualenv.rs | 4 +- crates/uv/src/commands/build_frontend.rs | 8 +- crates/uv/src/commands/pip/check.rs | 4 +- crates/uv/src/commands/pip/compile.rs | 6 +- crates/uv/src/commands/pip/freeze.rs | 4 +- crates/uv/src/commands/pip/install.rs | 11 +- crates/uv/src/commands/pip/list.rs | 4 +- crates/uv/src/commands/pip/show.rs | 4 +- crates/uv/src/commands/pip/sync.rs | 11 +- crates/uv/src/commands/pip/tree.rs | 4 +- crates/uv/src/commands/pip/uninstall.rs | 4 +- crates/uv/src/commands/project/add.rs | 15 +- crates/uv/src/commands/project/environment.rs | 4 +- crates/uv/src/commands/project/export.rs | 4 +- crates/uv/src/commands/project/init.rs | 8 +- crates/uv/src/commands/project/lock.rs | 12 +- crates/uv/src/commands/project/mod.rs | 24 +- crates/uv/src/commands/project/remove.rs | 4 +- crates/uv/src/commands/project/run.rs | 4 +- crates/uv/src/commands/project/sync.rs | 12 +- crates/uv/src/commands/project/tree.rs | 4 +- crates/uv/src/commands/project/version.rs | 8 +- crates/uv/src/commands/python/find.rs | 6 +- crates/uv/src/commands/python/install.rs | 19 +- crates/uv/src/commands/python/list.rs | 4 +- crates/uv/src/commands/python/pin.rs | 6 +- crates/uv/src/commands/python/uninstall.rs | 6 +- crates/uv/src/commands/tool/common.rs | 4 +- crates/uv/src/commands/tool/dir.rs | 4 +- crates/uv/src/commands/tool/install.rs | 4 +- crates/uv/src/commands/tool/run.rs | 6 +- crates/uv/src/commands/tool/upgrade.rs | 6 +- crates/uv/src/commands/venv.rs | 7 +- crates/uv/src/settings.rs | 12 +- crates/uv/tests/it/common/mod.rs | 4 +- crates/uv/tests/it/edit.rs | 4 +- crates/uv/tests/it/python_install.rs | 12 +- crates/uv/tests/it/show_settings.rs | 846 +++++++++++++++++- docs/reference/environment.md | 4 + 55 files changed, 1327 insertions(+), 304 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d0bd80e35..b22a79c0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5037,6 +5037,7 @@ name = "uv-configuration" version = "0.0.1" dependencies = [ "anyhow", + "bitflags 2.9.1", "clap", "either", "fs-err 3.1.1", @@ -5061,6 +5062,7 @@ dependencies = [ "uv-pep508", "uv-platform-tags", "uv-static", + "uv-warnings", ] [[package]] diff --git a/crates/uv-bench/benches/uv.rs b/crates/uv-bench/benches/uv.rs index 8380ccd60..eedc0f92f 100644 --- a/crates/uv-bench/benches/uv.rs +++ b/crates/uv-bench/benches/uv.rs @@ -87,7 +87,7 @@ mod resolver { use uv_client::RegistryClient; use uv_configuration::{ BuildOptions, Concurrency, ConfigSettings, Constraints, IndexStrategy, - PackageConfigSettings, PreviewMode, SourceStrategy, + PackageConfigSettings, Preview, SourceStrategy, }; use uv_dispatch::{BuildDispatch, SharedState}; use uv_distribution::DistributionDatabase; @@ -194,7 +194,7 @@ mod resolver { sources, workspace_cache, concurrency, - PreviewMode::Enabled, + Preview::default(), ); let markers = if universal { diff --git a/crates/uv-build-frontend/src/lib.rs b/crates/uv-build-frontend/src/lib.rs index e2a128747..b815719ba 100644 --- a/crates/uv-build-frontend/src/lib.rs +++ b/crates/uv-build-frontend/src/lib.rs @@ -28,7 +28,7 @@ use tokio::sync::{Mutex, Semaphore}; use tracing::{Instrument, debug, info_span, instrument, warn}; use uv_cache_key::cache_digest; -use uv_configuration::PreviewMode; +use uv_configuration::Preview; use uv_configuration::{BuildKind, BuildOutput, ConfigSettings, SourceStrategy}; use uv_distribution::BuildRequires; use uv_distribution_types::{IndexLocations, Requirement, Resolution}; @@ -286,7 +286,7 @@ impl SourceBuild { mut environment_variables: FxHashMap, level: BuildOutput, concurrent_builds: usize, - preview: PreviewMode, + preview: Preview, ) -> Result { let temp_dir = build_context.cache().venv_dir()?; diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index b6a41f3e7..1b41050b2 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -11,8 +11,8 @@ use clap::{Args, Parser, Subcommand}; use uv_cache::CacheArgs; use uv_configuration::{ ConfigSettingEntry, ConfigSettingPackageEntry, ExportFormat, IndexStrategy, - KeyringProviderType, PackageNameSpecifier, ProjectBuildBackend, TargetTriple, TrustedHost, - TrustedPublishing, VersionControlSystem, + KeyringProviderType, PackageNameSpecifier, PreviewFeatures, ProjectBuildBackend, TargetTriple, + TrustedHost, TrustedPublishing, VersionControlSystem, }; use uv_distribution_types::{Index, IndexUrl, Origin, PipExtraIndex, PipFindLinks, PipIndex}; use uv_normalize::{ExtraName, GroupName, PackageName, PipGroupName}; @@ -273,7 +273,7 @@ pub struct GlobalArgs { )] pub allow_insecure_host: Option>>, - /// Whether to enable experimental, preview features. + /// Whether to enable all experimental preview features. /// /// Preview features may change without warning. #[arg(global = true, long, hide = true, env = EnvVars::UV_PREVIEW, value_parser = clap::builder::BoolishValueParser::new(), overrides_with("no_preview"))] @@ -282,6 +282,25 @@ pub struct GlobalArgs { #[arg(global = true, long, overrides_with("preview"), hide = true)] pub no_preview: bool, + /// Enable experimental preview features. + /// + /// Preview features may change without warning. + /// + /// Use comma-separated values or pass multiple times to enable multiple features. + /// + /// The following features are available: `python-install-default`, `python-upgrade`, + /// `json-output`, `pylock`, `add-bounds`. + #[arg( + global = true, + long = "preview-features", + env = EnvVars::UV_PREVIEW_FEATURES, + value_delimiter = ',', + hide = true, + alias = "preview-feature", + value_enum, + )] + pub preview_features: Vec, + /// Avoid discovering a `pyproject.toml` or `uv.toml` file. /// /// Normally, configuration files are discovered in the current directory, diff --git a/crates/uv-configuration/Cargo.toml b/crates/uv-configuration/Cargo.toml index 1c195ab3d..807340a01 100644 --- a/crates/uv-configuration/Cargo.toml +++ b/crates/uv-configuration/Cargo.toml @@ -27,7 +27,9 @@ uv-pep440 = { workspace = true } uv-pep508 = { workspace = true, features = ["schemars"] } uv-platform-tags = { workspace = true } uv-static = { workspace = true } +uv-warnings = { workspace = true } +bitflags = { workspace = true } clap = { workspace = true, features = ["derive"], optional = true } either = { workspace = true } fs-err = { workspace = true } diff --git a/crates/uv-configuration/src/preview.rs b/crates/uv-configuration/src/preview.rs index 38572589b..c8d67be5b 100644 --- a/crates/uv-configuration/src/preview.rs +++ b/crates/uv-configuration/src/preview.rs @@ -1,37 +1,243 @@ -use std::fmt::{Display, Formatter}; +use std::{ + fmt::{Display, Formatter}, + str::FromStr, +}; + +use thiserror::Error; +use uv_warnings::warn_user_once; + +bitflags::bitflags! { + #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] + pub struct PreviewFeatures: u32 { + const PYTHON_INSTALL_DEFAULT = 1 << 0; + const PYTHON_UPGRADE = 1 << 1; + const JSON_OUTPUT = 1 << 2; + const PYLOCK = 1 << 3; + const ADD_BOUNDS = 1 << 4; + } +} + +impl PreviewFeatures { + /// Returns the string representation of a single preview feature flag. + /// + /// Panics if given a combination of flags. + fn flag_as_str(self) -> &'static str { + match self { + Self::PYTHON_INSTALL_DEFAULT => "python-install-default", + Self::PYTHON_UPGRADE => "python-upgrade", + Self::JSON_OUTPUT => "json-output", + Self::PYLOCK => "pylock", + Self::ADD_BOUNDS => "add-bounds", + _ => panic!("`flag_as_str` can only be used for exactly one feature flag"), + } + } +} + +impl Display for PreviewFeatures { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + if self.is_empty() { + write!(f, "none") + } else { + let features: Vec<&str> = self.iter().map(PreviewFeatures::flag_as_str).collect(); + write!(f, "{}", features.join(",")) + } + } +} + +#[derive(Debug, Error, Clone)] +pub enum PreviewFeaturesParseError { + #[error("Empty string in preview features: {0}")] + Empty(String), +} + +impl FromStr for PreviewFeatures { + type Err = PreviewFeaturesParseError; + + fn from_str(s: &str) -> Result { + let mut flags = PreviewFeatures::empty(); + + for part in s.split(',') { + let part = part.trim(); + if part.is_empty() { + return Err(PreviewFeaturesParseError::Empty( + "Empty string in preview features".to_string(), + )); + } + + let flag = match part { + "python-install-default" => Self::PYTHON_INSTALL_DEFAULT, + "python-upgrade" => Self::PYTHON_UPGRADE, + "json-output" => Self::JSON_OUTPUT, + "pylock" => Self::PYLOCK, + "add-bounds" => Self::ADD_BOUNDS, + _ => { + warn_user_once!("Unknown preview feature: `{part}`"); + continue; + } + }; + + flags |= flag; + } + + Ok(flags) + } +} #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub enum PreviewMode { - #[default] - Disabled, - Enabled, +pub struct Preview { + flags: PreviewFeatures, } -impl PreviewMode { - pub fn is_enabled(&self) -> bool { - matches!(self, Self::Enabled) +impl Preview { + pub fn new(flags: PreviewFeatures) -> Self { + Self { flags } } - pub fn is_disabled(&self) -> bool { - matches!(self, Self::Disabled) + pub fn all() -> Self { + Self::new(PreviewFeatures::all()) } -} -impl From for PreviewMode { - fn from(version: bool) -> Self { - if version { - PreviewMode::Enabled - } else { - PreviewMode::Disabled + pub fn from_args( + preview: bool, + no_preview: bool, + preview_features: &[PreviewFeatures], + ) -> Self { + if no_preview { + return Self::default(); } + + if preview { + return Self::all(); + } + + let mut flags = PreviewFeatures::empty(); + + for features in preview_features { + flags |= *features; + } + + Self { flags } + } + + pub fn is_enabled(&self, flag: PreviewFeatures) -> bool { + self.flags.contains(flag) } } -impl Display for PreviewMode { +impl Display for Preview { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - Self::Disabled => write!(f, "disabled"), - Self::Enabled => write!(f, "enabled"), + if self.flags.is_empty() { + write!(f, "disabled") + } else if self.flags == PreviewFeatures::all() { + write!(f, "enabled") + } else { + write!(f, "{}", self.flags) } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_preview_features_from_str() { + // Test single feature + let features = PreviewFeatures::from_str("python-install-default").unwrap(); + assert_eq!(features, PreviewFeatures::PYTHON_INSTALL_DEFAULT); + + // Test multiple features + let features = PreviewFeatures::from_str("python-upgrade,json-output").unwrap(); + assert!(features.contains(PreviewFeatures::PYTHON_UPGRADE)); + assert!(features.contains(PreviewFeatures::JSON_OUTPUT)); + assert!(!features.contains(PreviewFeatures::PYLOCK)); + + // Test with whitespace + let features = PreviewFeatures::from_str("pylock , add-bounds").unwrap(); + assert!(features.contains(PreviewFeatures::PYLOCK)); + assert!(features.contains(PreviewFeatures::ADD_BOUNDS)); + + // Test empty string error + assert!(PreviewFeatures::from_str("").is_err()); + assert!(PreviewFeatures::from_str("pylock,").is_err()); + assert!(PreviewFeatures::from_str(",pylock").is_err()); + + // Test unknown feature (should be ignored with warning) + let features = PreviewFeatures::from_str("unknown-feature,pylock").unwrap(); + assert!(features.contains(PreviewFeatures::PYLOCK)); + assert_eq!(features.bits().count_ones(), 1); + } + + #[test] + fn test_preview_features_display() { + // Test empty + let features = PreviewFeatures::empty(); + assert_eq!(features.to_string(), "none"); + + // Test single feature + let features = PreviewFeatures::PYTHON_INSTALL_DEFAULT; + assert_eq!(features.to_string(), "python-install-default"); + + // Test multiple features + let features = PreviewFeatures::PYTHON_UPGRADE | PreviewFeatures::JSON_OUTPUT; + assert_eq!(features.to_string(), "python-upgrade,json-output"); + } + + #[test] + fn test_preview_display() { + // Test disabled + let preview = Preview::default(); + assert_eq!(preview.to_string(), "disabled"); + + // Test enabled (all features) + let preview = Preview::all(); + assert_eq!(preview.to_string(), "enabled"); + + // Test specific features + let preview = Preview::new(PreviewFeatures::PYTHON_UPGRADE | PreviewFeatures::PYLOCK); + assert_eq!(preview.to_string(), "python-upgrade,pylock"); + } + + #[test] + fn test_preview_from_args() { + // Test no_preview + let preview = Preview::from_args(true, true, &[]); + assert_eq!(preview.to_string(), "disabled"); + + // Test preview (all features) + let preview = Preview::from_args(true, false, &[]); + assert_eq!(preview.to_string(), "enabled"); + + // Test specific features + let features = vec![ + PreviewFeatures::PYTHON_UPGRADE, + PreviewFeatures::JSON_OUTPUT, + ]; + let preview = Preview::from_args(false, false, &features); + assert!(preview.is_enabled(PreviewFeatures::PYTHON_UPGRADE)); + assert!(preview.is_enabled(PreviewFeatures::JSON_OUTPUT)); + assert!(!preview.is_enabled(PreviewFeatures::PYLOCK)); + } + + #[test] + fn test_as_str_single_flags() { + assert_eq!( + PreviewFeatures::PYTHON_INSTALL_DEFAULT.flag_as_str(), + "python-install-default" + ); + assert_eq!( + PreviewFeatures::PYTHON_UPGRADE.flag_as_str(), + "python-upgrade" + ); + assert_eq!(PreviewFeatures::JSON_OUTPUT.flag_as_str(), "json-output"); + assert_eq!(PreviewFeatures::PYLOCK.flag_as_str(), "pylock"); + assert_eq!(PreviewFeatures::ADD_BOUNDS.flag_as_str(), "add-bounds"); + } + + #[test] + #[should_panic(expected = "`flag_as_str` can only be used for exactly one feature flag")] + fn test_as_str_multiple_flags_panics() { + let features = PreviewFeatures::PYTHON_UPGRADE | PreviewFeatures::JSON_OUTPUT; + let _ = features.flag_as_str(); + } +} diff --git a/crates/uv-dev/src/compile.rs b/crates/uv-dev/src/compile.rs index d2b685b23..0e1392f63 100644 --- a/crates/uv-dev/src/compile.rs +++ b/crates/uv-dev/src/compile.rs @@ -4,7 +4,7 @@ use clap::Parser; use tracing::info; use uv_cache::{Cache, CacheArgs}; -use uv_configuration::{Concurrency, PreviewMode}; +use uv_configuration::{Concurrency, Preview}; use uv_python::{EnvironmentPreference, PythonEnvironment, PythonRequest}; #[derive(Parser)] @@ -26,7 +26,7 @@ pub(crate) async fn compile(args: CompileArgs) -> anyhow::Result<()> { &PythonRequest::default(), EnvironmentPreference::OnlyVirtual, &cache, - PreviewMode::Disabled, + Preview::default(), )? .into_interpreter(); interpreter.sys_executable().to_path_buf() diff --git a/crates/uv-dispatch/src/lib.rs b/crates/uv-dispatch/src/lib.rs index 2e34b583d..aa48fecf7 100644 --- a/crates/uv-dispatch/src/lib.rs +++ b/crates/uv-dispatch/src/lib.rs @@ -18,7 +18,7 @@ use uv_cache::Cache; use uv_client::RegistryClient; use uv_configuration::{ BuildKind, BuildOptions, ConfigSettings, Constraints, IndexStrategy, PackageConfigSettings, - PreviewMode, Reinstall, SourceStrategy, + Preview, Reinstall, SourceStrategy, }; use uv_configuration::{BuildOutput, Concurrency}; use uv_distribution::DistributionDatabase; @@ -99,7 +99,7 @@ pub struct BuildDispatch<'a> { sources: SourceStrategy, workspace_cache: WorkspaceCache, concurrency: Concurrency, - preview: PreviewMode, + preview: Preview, } impl<'a> BuildDispatch<'a> { @@ -123,7 +123,7 @@ impl<'a> BuildDispatch<'a> { sources: SourceStrategy, workspace_cache: WorkspaceCache, concurrency: Concurrency, - preview: PreviewMode, + preview: Preview, ) -> Self { Self { client, diff --git a/crates/uv-python/src/discovery.rs b/crates/uv-python/src/discovery.rs index f10b480e2..466ea4b0f 100644 --- a/crates/uv-python/src/discovery.rs +++ b/crates/uv-python/src/discovery.rs @@ -8,7 +8,7 @@ use std::{env, io, iter}; use std::{path::Path, path::PathBuf, str::FromStr}; use thiserror::Error; use tracing::{debug, instrument, trace}; -use uv_configuration::PreviewMode; +use uv_configuration::Preview; use which::{which, which_all}; use uv_cache::Cache; @@ -335,7 +335,7 @@ fn python_executables_from_installed<'a>( implementation: Option<&'a ImplementationName>, platform: PlatformRequest, preference: PythonPreference, - preview: PreviewMode, + preview: Preview, ) -> Box> + 'a> { let from_managed_installations = iter::once_with(move || { ManagedPythonInstallations::from_settings(None) @@ -485,7 +485,7 @@ fn python_executables<'a>( platform: PlatformRequest, environments: EnvironmentPreference, preference: PythonPreference, - preview: PreviewMode, + preview: Preview, ) -> Box> + 'a> { // Always read from `UV_INTERNAL__PARENT_INTERPRETER` — it could be a system interpreter let from_parent_interpreter = iter::once_with(|| { @@ -705,7 +705,7 @@ fn python_interpreters<'a>( environments: EnvironmentPreference, preference: PythonPreference, cache: &'a Cache, - preview: PreviewMode, + preview: Preview, ) -> impl Iterator> + 'a { python_interpreters_from_executables( // Perform filtering on the discovered executables based on their source. This avoids @@ -1053,7 +1053,7 @@ pub fn find_python_installations<'a>( environments: EnvironmentPreference, preference: PythonPreference, cache: &'a Cache, - preview: PreviewMode, + preview: Preview, ) -> Box> + 'a> { let sources = DiscoveryPreferences { python_preference: preference, @@ -1254,7 +1254,7 @@ pub(crate) fn find_python_installation( environments: EnvironmentPreference, preference: PythonPreference, cache: &Cache, - preview: PreviewMode, + preview: Preview, ) -> Result { let installations = find_python_installations(request, environments, preference, cache, preview); @@ -1353,7 +1353,7 @@ pub(crate) fn find_best_python_installation( environments: EnvironmentPreference, preference: PythonPreference, cache: &Cache, - preview: PreviewMode, + preview: Preview, ) -> Result { debug!("Starting Python discovery for {}", request); diff --git a/crates/uv-python/src/environment.rs b/crates/uv-python/src/environment.rs index 10cec16ad..f82319074 100644 --- a/crates/uv-python/src/environment.rs +++ b/crates/uv-python/src/environment.rs @@ -7,7 +7,7 @@ use owo_colors::OwoColorize; use tracing::debug; use uv_cache::Cache; -use uv_configuration::PreviewMode; +use uv_configuration::Preview; use uv_fs::{LockedFile, Simplified}; use uv_pep440::Version; @@ -153,7 +153,7 @@ impl PythonEnvironment { request: &PythonRequest, preference: EnvironmentPreference, cache: &Cache, - preview: PreviewMode, + preview: Preview, ) -> Result { let installation = match find_python_installation( request, diff --git a/crates/uv-python/src/installation.rs b/crates/uv-python/src/installation.rs index a5dbb55f2..3f5b506a6 100644 --- a/crates/uv-python/src/installation.rs +++ b/crates/uv-python/src/installation.rs @@ -8,7 +8,7 @@ use tracing::{debug, info}; use uv_cache::Cache; use uv_client::BaseClientBuilder; -use uv_configuration::PreviewMode; +use uv_configuration::Preview; use uv_pep440::{Prerelease, Version}; use crate::discovery::{ @@ -58,7 +58,7 @@ impl PythonInstallation { environments: EnvironmentPreference, preference: PythonPreference, cache: &Cache, - preview: PreviewMode, + preview: Preview, ) -> Result { let installation = find_python_installation(request, environments, preference, cache, preview)??; @@ -72,7 +72,7 @@ impl PythonInstallation { environments: EnvironmentPreference, preference: PythonPreference, cache: &Cache, - preview: PreviewMode, + preview: Preview, ) -> Result { Ok(find_best_python_installation( request, @@ -97,7 +97,7 @@ impl PythonInstallation { python_install_mirror: Option<&str>, pypy_install_mirror: Option<&str>, python_downloads_json_url: Option<&str>, - preview: PreviewMode, + preview: Preview, ) -> Result { let request = request.unwrap_or(&PythonRequest::Default); @@ -220,7 +220,7 @@ impl PythonInstallation { reporter: Option<&dyn Reporter>, python_install_mirror: Option<&str>, pypy_install_mirror: Option<&str>, - preview: PreviewMode, + preview: Preview, ) -> Result { let installations = ManagedPythonInstallations::from_settings(None)?.init()?; let installations_dir = installations.root(); diff --git a/crates/uv-python/src/lib.rs b/crates/uv-python/src/lib.rs index 2461f9006..8b8e9c129 100644 --- a/crates/uv-python/src/lib.rs +++ b/crates/uv-python/src/lib.rs @@ -135,7 +135,7 @@ mod tests { use indoc::{formatdoc, indoc}; use temp_env::with_vars; use test_log::test; - use uv_configuration::PreviewMode; + use uv_configuration::Preview; use uv_static::EnvVars; use uv_cache::Cache; @@ -468,7 +468,7 @@ mod tests { EnvironmentPreference::OnlySystem, PythonPreference::default(), &context.cache, - PreviewMode::Disabled, + Preview::default(), ) }); assert!( @@ -483,7 +483,7 @@ mod tests { EnvironmentPreference::OnlySystem, PythonPreference::default(), &context.cache, - PreviewMode::Disabled, + Preview::default(), ) }); assert!( @@ -508,7 +508,7 @@ mod tests { EnvironmentPreference::OnlySystem, PythonPreference::default(), &context.cache, - PreviewMode::Disabled, + Preview::default(), ) }); assert!( @@ -530,7 +530,7 @@ mod tests { EnvironmentPreference::OnlySystem, PythonPreference::default(), &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert!( @@ -592,7 +592,7 @@ mod tests { EnvironmentPreference::OnlySystem, PythonPreference::default(), &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert!( @@ -624,7 +624,7 @@ mod tests { EnvironmentPreference::OnlySystem, PythonPreference::default(), &context.cache, - PreviewMode::Disabled, + Preview::default(), ) }); assert!( @@ -661,7 +661,7 @@ mod tests { EnvironmentPreference::OnlySystem, PythonPreference::default(), &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert!( @@ -693,7 +693,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert_eq!( @@ -715,7 +715,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert_eq!( @@ -741,7 +741,7 @@ mod tests { EnvironmentPreference::OnlySystem, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert_eq!( @@ -767,7 +767,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert_eq!( @@ -790,7 +790,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; @@ -824,7 +824,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; @@ -858,7 +858,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })?; assert!( @@ -880,7 +880,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })?; assert!( @@ -902,7 +902,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; @@ -936,7 +936,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; @@ -973,7 +973,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert!( @@ -1004,7 +1004,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert!( @@ -1039,7 +1039,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert_eq!( @@ -1065,7 +1065,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert_eq!( @@ -1092,7 +1092,7 @@ mod tests { EnvironmentPreference::OnlyVirtual, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) }, )??; @@ -1117,7 +1117,7 @@ mod tests { EnvironmentPreference::OnlyVirtual, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) }, )?; @@ -1139,7 +1139,7 @@ mod tests { EnvironmentPreference::OnlySystem, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) }, )??; @@ -1162,7 +1162,7 @@ mod tests { EnvironmentPreference::OnlyVirtual, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) }, )??; @@ -1195,7 +1195,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) }, )??; @@ -1216,7 +1216,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) }, )??; @@ -1243,7 +1243,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; @@ -1261,7 +1261,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; @@ -1290,7 +1290,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert_eq!( @@ -1328,7 +1328,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) }, )??; @@ -1356,7 +1356,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) }, )??; @@ -1381,7 +1381,7 @@ mod tests { EnvironmentPreference::ExplicitSystem, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) }, )??; @@ -1406,7 +1406,7 @@ mod tests { EnvironmentPreference::OnlySystem, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) }, )??; @@ -1431,7 +1431,7 @@ mod tests { EnvironmentPreference::OnlyVirtual, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) }, )??; @@ -1469,7 +1469,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) }, )??; @@ -1497,7 +1497,7 @@ mod tests { EnvironmentPreference::OnlySystem, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert_eq!( @@ -1514,7 +1514,7 @@ mod tests { EnvironmentPreference::OnlySystem, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert_eq!( @@ -1531,7 +1531,7 @@ mod tests { EnvironmentPreference::OnlySystem, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })?; assert!( @@ -1553,7 +1553,7 @@ mod tests { EnvironmentPreference::OnlyVirtual, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })?; assert!( @@ -1570,7 +1570,7 @@ mod tests { EnvironmentPreference::OnlySystem, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) }, )?; @@ -1592,7 +1592,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert_eq!( @@ -1607,7 +1607,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })?; assert!( @@ -1621,7 +1621,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })?; assert!( @@ -1650,7 +1650,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert_eq!( @@ -1666,7 +1666,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert_eq!( @@ -1696,7 +1696,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert_eq!( @@ -1712,7 +1712,7 @@ mod tests { EnvironmentPreference::ExplicitSystem, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert_eq!( @@ -1728,7 +1728,7 @@ mod tests { EnvironmentPreference::OnlyVirtual, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert_eq!( @@ -1744,7 +1744,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert_eq!( @@ -1768,7 +1768,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert_eq!( @@ -1783,7 +1783,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert_eq!( @@ -1807,7 +1807,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert_eq!( @@ -1827,7 +1827,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) }, )??; @@ -1856,7 +1856,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert_eq!( @@ -1878,7 +1878,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })?; assert!( @@ -1908,7 +1908,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert_eq!( @@ -1924,7 +1924,7 @@ mod tests { EnvironmentPreference::ExplicitSystem, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })?; assert!( @@ -1951,7 +1951,7 @@ mod tests { EnvironmentPreference::ExplicitSystem, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) }) .unwrap() @@ -1976,7 +1976,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })?; assert!( @@ -1993,7 +1993,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert_eq!( @@ -2008,7 +2008,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert_eq!( @@ -2034,7 +2034,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert_eq!( @@ -2049,7 +2049,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert_eq!( @@ -2075,7 +2075,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert_eq!( @@ -2102,7 +2102,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert_eq!( @@ -2129,7 +2129,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert_eq!( @@ -2156,7 +2156,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert_eq!( @@ -2183,7 +2183,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert_eq!( @@ -2211,7 +2211,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })?; assert!( @@ -2233,7 +2233,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert_eq!( @@ -2248,7 +2248,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert_eq!( @@ -2274,7 +2274,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert_eq!( @@ -2289,7 +2289,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; assert_eq!( @@ -2327,7 +2327,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) }) .unwrap() @@ -2345,7 +2345,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) }) .unwrap() @@ -2387,7 +2387,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) }) .unwrap() @@ -2405,7 +2405,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) }) .unwrap() @@ -2442,7 +2442,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) }) .unwrap() @@ -2465,7 +2465,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) }) .unwrap() @@ -2488,7 +2488,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) }) .unwrap() @@ -2527,7 +2527,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; @@ -2580,7 +2580,7 @@ mod tests { EnvironmentPreference::Any, PythonPreference::OnlySystem, &context.cache, - PreviewMode::Disabled, + Preview::default(), ) })??; diff --git a/crates/uv-python/src/managed.rs b/crates/uv-python/src/managed.rs index 9ee72adda..d9b96e5ed 100644 --- a/crates/uv-python/src/managed.rs +++ b/crates/uv-python/src/managed.rs @@ -12,7 +12,7 @@ use itertools::Itertools; use same_file::is_same_file; use thiserror::Error; use tracing::{debug, warn}; -use uv_configuration::PreviewMode; +use uv_configuration::{Preview, PreviewFeatures}; #[cfg(windows)] use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT; @@ -519,7 +519,7 @@ impl ManagedPythonInstallation { /// Ensure the environment contains the symlink directory (or junction on Windows) /// pointing to the patch directory for this minor version. - pub fn ensure_minor_version_link(&self, preview: PreviewMode) -> Result<(), Error> { + pub fn ensure_minor_version_link(&self, preview: Preview) -> Result<(), Error> { if let Some(minor_version_link) = PythonMinorVersionLink::from_installation(self, preview) { minor_version_link.create_directory()?; } @@ -531,7 +531,7 @@ impl ManagedPythonInstallation { /// /// Unlike [`ensure_minor_version_link`], will not create a new symlink directory /// if one doesn't already exist, - pub fn update_minor_version_link(&self, preview: PreviewMode) -> Result<(), Error> { + pub fn update_minor_version_link(&self, preview: Preview) -> Result<(), Error> { if let Some(minor_version_link) = PythonMinorVersionLink::from_installation(self, preview) { if !minor_version_link.exists() { return Ok(()); @@ -702,7 +702,7 @@ impl PythonMinorVersionLink { pub fn from_executable( executable: &Path, key: &PythonInstallationKey, - preview: PreviewMode, + preview: Preview, ) -> Option { let implementation = key.implementation(); if !matches!( @@ -755,7 +755,7 @@ impl PythonMinorVersionLink { // If preview mode is disabled, still return a `MinorVersionSymlink` for // existing symlinks, allowing continued operations without the `--preview` // flag after initial symlink directory installation. - if preview.is_disabled() && !minor_version_link.exists() { + if !preview.is_enabled(PreviewFeatures::PYTHON_UPGRADE) && !minor_version_link.exists() { return None; } Some(minor_version_link) @@ -763,7 +763,7 @@ impl PythonMinorVersionLink { pub fn from_installation( installation: &ManagedPythonInstallation, - preview: PreviewMode, + preview: Preview, ) -> Option { PythonMinorVersionLink::from_executable( installation.executable(false).as_path(), diff --git a/crates/uv-static/src/env_vars.rs b/crates/uv-static/src/env_vars.rs index a18bc11a8..b8adf225f 100644 --- a/crates/uv-static/src/env_vars.rs +++ b/crates/uv-static/src/env_vars.rs @@ -225,6 +225,9 @@ impl EnvVars { /// Equivalent to the `--preview` argument. Enables preview mode. pub const UV_PREVIEW: &'static str = "UV_PREVIEW"; + /// Equivalent to the `--preview-features` argument. Enables specific preview features. + pub const UV_PREVIEW_FEATURES: &'static str = "UV_PREVIEW_FEATURES"; + /// Equivalent to the `--token` argument for self update. A GitHub token for authentication. pub const UV_GITHUB_TOKEN: &'static str = "UV_GITHUB_TOKEN"; diff --git a/crates/uv-tool/src/lib.rs b/crates/uv-tool/src/lib.rs index 4afc83bcb..902dbf2d0 100644 --- a/crates/uv-tool/src/lib.rs +++ b/crates/uv-tool/src/lib.rs @@ -1,7 +1,7 @@ use core::fmt; use fs_err as fs; -use uv_configuration::PreviewMode; +use uv_configuration::Preview; use uv_dirs::user_executable_directory; use uv_pep440::Version; use uv_pep508::{InvalidNameError, PackageName}; @@ -258,7 +258,7 @@ impl InstalledTools { &self, name: &PackageName, interpreter: Interpreter, - preview: PreviewMode, + preview: Preview, ) -> Result { let environment_path = self.tool_dir(name); diff --git a/crates/uv-virtualenv/src/lib.rs b/crates/uv-virtualenv/src/lib.rs index bcf1e9f97..7c682627b 100644 --- a/crates/uv-virtualenv/src/lib.rs +++ b/crates/uv-virtualenv/src/lib.rs @@ -3,7 +3,7 @@ use std::path::Path; use thiserror::Error; -use uv_configuration::PreviewMode; +use uv_configuration::Preview; use uv_python::{Interpreter, PythonEnvironment}; pub use virtualenv::{OnExisting, remove_virtualenv}; @@ -56,7 +56,7 @@ pub fn create_venv( relocatable: bool, seed: bool, upgradeable: bool, - preview: PreviewMode, + preview: Preview, ) -> Result { // Create the virtualenv at the given location. let virtualenv = virtualenv::create( diff --git a/crates/uv-virtualenv/src/virtualenv.rs b/crates/uv-virtualenv/src/virtualenv.rs index 5fa77034e..7c65ec1bf 100644 --- a/crates/uv-virtualenv/src/virtualenv.rs +++ b/crates/uv-virtualenv/src/virtualenv.rs @@ -12,7 +12,7 @@ use itertools::Itertools; use owo_colors::OwoColorize; use tracing::{debug, trace}; -use uv_configuration::PreviewMode; +use uv_configuration::Preview; use uv_fs::{CWD, Simplified, cachedir}; use uv_pypi_types::Scheme; use uv_python::managed::{PythonMinorVersionLink, create_link_to_executable}; @@ -59,7 +59,7 @@ pub(crate) fn create( relocatable: bool, seed: bool, upgradeable: bool, - preview: PreviewMode, + preview: Preview, ) -> Result { // Determine the base Python executable; that is, the Python executable that should be // considered the "base" for the virtual environment. diff --git a/crates/uv/src/commands/build_frontend.rs b/crates/uv/src/commands/build_frontend.rs index b3f9e5c89..24cda1cf3 100644 --- a/crates/uv/src/commands/build_frontend.rs +++ b/crates/uv/src/commands/build_frontend.rs @@ -16,7 +16,7 @@ use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{ BuildKind, BuildOptions, BuildOutput, Concurrency, ConfigSettings, Constraints, DependencyGroupsWithDefaults, HashCheckingMode, IndexStrategy, KeyringProviderType, - PackageConfigSettings, PreviewMode, SourceStrategy, + PackageConfigSettings, Preview, SourceStrategy, }; use uv_dispatch::{BuildDispatch, SharedState}; use uv_distribution_filename::{ @@ -117,7 +117,7 @@ pub(crate) async fn build_frontend( concurrency: Concurrency, cache: &Cache, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result { let build_result = build_impl( project_dir, @@ -185,7 +185,7 @@ async fn build_impl( concurrency: Concurrency, cache: &Cache, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result { // Extract the resolver settings. let ResolverSettings { @@ -437,7 +437,7 @@ async fn build_package( link_mode: LinkMode, config_setting: &ConfigSettings, config_settings_package: &PackageConfigSettings, - preview: PreviewMode, + preview: Preview, ) -> Result, Error> { let output_dir = if let Some(output_dir) = output_dir { Cow::Owned(std::path::absolute(output_dir)?) diff --git a/crates/uv/src/commands/pip/check.rs b/crates/uv/src/commands/pip/check.rs index bfbb20ee6..cf5ab350b 100644 --- a/crates/uv/src/commands/pip/check.rs +++ b/crates/uv/src/commands/pip/check.rs @@ -5,7 +5,7 @@ use anyhow::Result; use owo_colors::OwoColorize; use uv_cache::Cache; -use uv_configuration::PreviewMode; +use uv_configuration::Preview; use uv_distribution_types::{Diagnostic, InstalledDist}; use uv_installer::{SitePackages, SitePackagesDiagnostic}; use uv_python::{EnvironmentPreference, PythonEnvironment, PythonRequest}; @@ -20,7 +20,7 @@ pub(crate) fn pip_check( system: bool, cache: &Cache, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result { let start = Instant::now(); diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs index b9dda45c8..8c512a2e9 100644 --- a/crates/uv/src/commands/pip/compile.rs +++ b/crates/uv/src/commands/pip/compile.rs @@ -14,8 +14,8 @@ use uv_cache::Cache; use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{ BuildOptions, Concurrency, ConfigSettings, Constraints, ExportFormat, ExtrasSpecification, - IndexStrategy, NoBinary, NoBuild, PackageConfigSettings, PreviewMode, Reinstall, - SourceStrategy, Upgrade, + IndexStrategy, NoBinary, NoBuild, PackageConfigSettings, Preview, Reinstall, SourceStrategy, + Upgrade, }; use uv_configuration::{KeyringProviderType, TargetTriple}; use uv_dispatch::{BuildDispatch, SharedState}; @@ -110,7 +110,7 @@ pub(crate) async fn pip_compile( quiet: bool, cache: Cache, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result { // If the user provides a `pyproject.toml` or other TOML file as the output file, raise an // error. diff --git a/crates/uv/src/commands/pip/freeze.rs b/crates/uv/src/commands/pip/freeze.rs index 8c8491d45..6f663e03b 100644 --- a/crates/uv/src/commands/pip/freeze.rs +++ b/crates/uv/src/commands/pip/freeze.rs @@ -6,7 +6,7 @@ use itertools::Itertools; use owo_colors::OwoColorize; use uv_cache::Cache; -use uv_configuration::PreviewMode; +use uv_configuration::Preview; use uv_distribution_types::{Diagnostic, InstalledDist, Name}; use uv_installer::SitePackages; use uv_python::{EnvironmentPreference, PythonEnvironment, PythonRequest}; @@ -24,7 +24,7 @@ pub(crate) fn pip_freeze( paths: Option>, cache: &Cache, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result { // Detect the current Python interpreter. let environment = PythonEnvironment::find( diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs index cb1229d72..d3917db11 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -10,8 +10,8 @@ use uv_cache::Cache; use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{ BuildOptions, Concurrency, ConfigSettings, Constraints, DryRun, ExtrasSpecification, - HashCheckingMode, IndexStrategy, PackageConfigSettings, PreviewMode, Reinstall, SourceStrategy, - Upgrade, + HashCheckingMode, IndexStrategy, PackageConfigSettings, Preview, PreviewFeatures, Reinstall, + SourceStrategy, Upgrade, }; use uv_configuration::{KeyringProviderType, TargetTriple}; use uv_dispatch::{BuildDispatch, SharedState}; @@ -95,7 +95,7 @@ pub(crate) async fn pip_install( cache: Cache, dry_run: DryRun, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> anyhow::Result { let start = std::time::Instant::now(); @@ -133,9 +133,10 @@ pub(crate) async fn pip_install( .await?; if pylock.is_some() { - if preview.is_disabled() { + if !preview.is_enabled(PreviewFeatures::PYLOCK) { warn_user!( - "The `--pylock` setting is experimental and may change without warning. Pass `--preview` to disable this warning." + "The `--pylock` setting is experimental and may change without warning. Pass `--preview-features {}` to disable this warning.", + PreviewFeatures::PYLOCK ); } } diff --git a/crates/uv/src/commands/pip/list.rs b/crates/uv/src/commands/pip/list.rs index 40e8c770d..9205268ba 100644 --- a/crates/uv/src/commands/pip/list.rs +++ b/crates/uv/src/commands/pip/list.rs @@ -15,7 +15,7 @@ use uv_cache::{Cache, Refresh}; use uv_cache_info::Timestamp; use uv_cli::ListFormat; use uv_client::{BaseClientBuilder, RegistryClientBuilder}; -use uv_configuration::{Concurrency, IndexStrategy, KeyringProviderType, PreviewMode}; +use uv_configuration::{Concurrency, IndexStrategy, KeyringProviderType, Preview}; use uv_distribution_filename::DistFilename; use uv_distribution_types::{ Diagnostic, IndexCapabilities, IndexLocations, InstalledDist, Name, RequiresPython, @@ -54,7 +54,7 @@ pub(crate) async fn pip_list( system: bool, cache: &Cache, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result { // Disallow `--outdated` with `--format freeze`. if outdated && matches!(format, ListFormat::Freeze) { diff --git a/crates/uv/src/commands/pip/show.rs b/crates/uv/src/commands/pip/show.rs index 4d2b3c3a7..f7a46ea7a 100644 --- a/crates/uv/src/commands/pip/show.rs +++ b/crates/uv/src/commands/pip/show.rs @@ -7,7 +7,7 @@ use owo_colors::OwoColorize; use rustc_hash::FxHashMap; use uv_cache::Cache; -use uv_configuration::PreviewMode; +use uv_configuration::Preview; use uv_distribution_types::{Diagnostic, Name}; use uv_fs::Simplified; use uv_install_wheel::read_record_file; @@ -28,7 +28,7 @@ pub(crate) fn pip_show( files: bool, cache: &Cache, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result { if packages.is_empty() { #[allow(clippy::print_stderr)] diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index 2fe5fbe87..2053f535b 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -9,8 +9,8 @@ use uv_cache::Cache; use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{ BuildOptions, Concurrency, ConfigSettings, Constraints, DryRun, ExtrasSpecification, - HashCheckingMode, IndexStrategy, PackageConfigSettings, PreviewMode, Reinstall, SourceStrategy, - Upgrade, + HashCheckingMode, IndexStrategy, PackageConfigSettings, Preview, PreviewFeatures, Reinstall, + SourceStrategy, Upgrade, }; use uv_configuration::{KeyringProviderType, TargetTriple}; use uv_dispatch::{BuildDispatch, SharedState}; @@ -83,7 +83,7 @@ pub(crate) async fn pip_sync( cache: Cache, dry_run: DryRun, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result { let client_builder = BaseClientBuilder::new() .retries_from_env()? @@ -126,9 +126,10 @@ pub(crate) async fn pip_sync( .await?; if pylock.is_some() { - if preview.is_disabled() { + if !preview.is_enabled(PreviewFeatures::PYLOCK) { warn_user!( - "The `--pylock` setting is experimental and may change without warning. Pass `--preview` to disable this warning." + "The `--pylock` setting is experimental and may change without warning. Pass `--preview-features {}` to disable this warning.", + PreviewFeatures::PYLOCK ); } } diff --git a/crates/uv/src/commands/pip/tree.rs b/crates/uv/src/commands/pip/tree.rs index 81a566b8e..8b0aa0c3a 100644 --- a/crates/uv/src/commands/pip/tree.rs +++ b/crates/uv/src/commands/pip/tree.rs @@ -13,7 +13,7 @@ use tokio::sync::Semaphore; use uv_cache::{Cache, Refresh}; use uv_cache_info::Timestamp; use uv_client::{BaseClientBuilder, RegistryClientBuilder}; -use uv_configuration::{Concurrency, IndexStrategy, KeyringProviderType, PreviewMode}; +use uv_configuration::{Concurrency, IndexStrategy, KeyringProviderType, Preview}; use uv_distribution_types::{Diagnostic, IndexCapabilities, IndexLocations, Name, RequiresPython}; use uv_installer::SitePackages; use uv_normalize::PackageName; @@ -52,7 +52,7 @@ pub(crate) async fn pip_tree( system: bool, cache: &Cache, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result { // Detect the current Python interpreter. let environment = PythonEnvironment::find( diff --git a/crates/uv/src/commands/pip/uninstall.rs b/crates/uv/src/commands/pip/uninstall.rs index f617a0203..af7dfc8f3 100644 --- a/crates/uv/src/commands/pip/uninstall.rs +++ b/crates/uv/src/commands/pip/uninstall.rs @@ -7,7 +7,7 @@ use tracing::{debug, warn}; use uv_cache::Cache; use uv_client::BaseClientBuilder; -use uv_configuration::{DryRun, KeyringProviderType, PreviewMode}; +use uv_configuration::{DryRun, KeyringProviderType, Preview}; use uv_distribution_types::Requirement; use uv_distribution_types::{InstalledMetadata, Name, UnresolvedRequirement}; use uv_fs::Simplified; @@ -37,7 +37,7 @@ pub(crate) async fn pip_uninstall( network_settings: &NetworkSettings, dry_run: DryRun, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result { let start = std::time::Instant::now(); diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index 64419bb02..2b7938da6 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -18,8 +18,8 @@ use uv_cache_key::RepositoryUrl; use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{ Concurrency, Constraints, DependencyGroups, DependencyGroupsWithDefaults, DevMode, DryRun, - EditableMode, ExtrasSpecification, ExtrasSpecificationWithDefaults, InstallOptions, - PreviewMode, SourceStrategy, + EditableMode, ExtrasSpecification, ExtrasSpecificationWithDefaults, InstallOptions, Preview, + PreviewFeatures, SourceStrategy, }; use uv_dispatch::BuildDispatch; use uv_distribution::DistributionDatabase; @@ -95,10 +95,13 @@ pub(crate) async fn add( no_config: bool, cache: &Cache, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result { - if bounds.is_some() && preview.is_disabled() { - warn_user_once!("The bounds option is in preview and may change in any future release."); + if bounds.is_some() && !preview.is_enabled(PreviewFeatures::ADD_BOUNDS) { + warn_user_once!( + "The `bounds` option is in preview and may change in any future release. Pass `--preview-features {}` to disable this warning.", + PreviewFeatures::ADD_BOUNDS + ); } for source in &requirements { @@ -944,7 +947,7 @@ async fn lock_and_sync( concurrency: Concurrency, cache: &Cache, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result<(), ProjectError> { let mut lock = project::lock::LockOperation::new( if locked { diff --git a/crates/uv/src/commands/project/environment.rs b/crates/uv/src/commands/project/environment.rs index af3b3b351..675b1a960 100644 --- a/crates/uv/src/commands/project/environment.rs +++ b/crates/uv/src/commands/project/environment.rs @@ -12,7 +12,7 @@ use crate::settings::{NetworkSettings, ResolverInstallerSettings}; use uv_cache::{Cache, CacheBucket}; use uv_cache_key::{cache_digest, hash_digest}; -use uv_configuration::{Concurrency, Constraints, PreviewMode}; +use uv_configuration::{Concurrency, Constraints, Preview}; use uv_distribution_types::{Name, Resolution}; use uv_fs::PythonExt; use uv_python::{Interpreter, PythonEnvironment, canonicalize_executable}; @@ -119,7 +119,7 @@ impl CachedEnvironment { concurrency: Concurrency, cache: &Cache, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result { let interpreter = Self::base_interpreter(interpreter, cache)?; diff --git a/crates/uv/src/commands/project/export.rs b/crates/uv/src/commands/project/export.rs index c14bfd904..6b06e493a 100644 --- a/crates/uv/src/commands/project/export.rs +++ b/crates/uv/src/commands/project/export.rs @@ -9,7 +9,7 @@ use owo_colors::OwoColorize; use uv_cache::Cache; use uv_configuration::{ Concurrency, DependencyGroups, EditableMode, ExportFormat, ExtrasSpecification, InstallOptions, - PreviewMode, + Preview, }; use uv_normalize::{DefaultExtras, DefaultGroups, PackageName}; use uv_python::{PythonDownloads, PythonPreference, PythonRequest}; @@ -79,7 +79,7 @@ pub(crate) async fn export( quiet: bool, cache: &Cache, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result { // Identify the target. let workspace_cache = WorkspaceCache::default(); diff --git a/crates/uv/src/commands/project/init.rs b/crates/uv/src/commands/project/init.rs index 9ba2a434d..6e04524fb 100644 --- a/crates/uv/src/commands/project/init.rs +++ b/crates/uv/src/commands/project/init.rs @@ -12,7 +12,7 @@ use uv_cache::Cache; use uv_cli::AuthorFrom; use uv_client::BaseClientBuilder; use uv_configuration::{ - DependencyGroupsWithDefaults, PreviewMode, ProjectBuildBackend, VersionControlError, + DependencyGroupsWithDefaults, Preview, ProjectBuildBackend, VersionControlError, VersionControlSystem, }; use uv_fs::{CWD, Simplified}; @@ -62,7 +62,7 @@ pub(crate) async fn init( no_config: bool, cache: &Cache, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result { match init_kind { InitKind::Script => { @@ -201,7 +201,7 @@ async fn init_script( pin_python: bool, package: bool, no_config: bool, - preview: PreviewMode, + preview: Preview, ) -> Result<()> { if no_workspace { warn_user_once!("`--no-workspace` is a no-op for Python scripts, which are standalone"); @@ -296,7 +296,7 @@ async fn init_project( no_config: bool, cache: &Cache, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result<()> { // Discover the current workspace, if it exists. let workspace_cache = WorkspaceCache::default(); diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index 706c86593..8eb0d4869 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -12,8 +12,8 @@ use tracing::debug; use uv_cache::Cache; use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{ - Concurrency, Constraints, DependencyGroupsWithDefaults, DryRun, ExtrasSpecification, - PreviewMode, Reinstall, Upgrade, + Concurrency, Constraints, DependencyGroupsWithDefaults, DryRun, ExtrasSpecification, Preview, + Reinstall, Upgrade, }; use uv_dispatch::BuildDispatch; use uv_distribution::DistributionDatabase; @@ -93,7 +93,7 @@ pub(crate) async fn lock( no_config: bool, cache: &Cache, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> anyhow::Result { // If necessary, initialize the PEP 723 script. let script = match script { @@ -271,7 +271,7 @@ pub(super) struct LockOperation<'env> { cache: &'env Cache, workspace_cache: &'env WorkspaceCache, printer: Printer, - preview: PreviewMode, + preview: Preview, } impl<'env> LockOperation<'env> { @@ -286,7 +286,7 @@ impl<'env> LockOperation<'env> { cache: &'env Cache, workspace_cache: &'env WorkspaceCache, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Self { Self { mode, @@ -418,7 +418,7 @@ async fn do_lock( cache: &Cache, workspace_cache: &WorkspaceCache, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result { let start = std::time::Instant::now(); diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index e6f41c2af..a5953cd76 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -12,8 +12,8 @@ use uv_cache::{Cache, CacheBucket}; use uv_cache_key::cache_digest; use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{ - Concurrency, Constraints, DependencyGroupsWithDefaults, DryRun, ExtrasSpecification, - PreviewMode, Reinstall, SourceStrategy, Upgrade, + Concurrency, Constraints, DependencyGroupsWithDefaults, DryRun, ExtrasSpecification, Preview, + PreviewFeatures, Reinstall, SourceStrategy, Upgrade, }; use uv_dispatch::{BuildDispatch, SharedState}; use uv_distribution::{DistributionDatabase, LoweredRequirement}; @@ -647,7 +647,7 @@ impl ScriptInterpreter { active: Option, cache: &Cache, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result { // For now, we assume that scripts are never evaluated in the context of a workspace. let workspace = None; @@ -887,7 +887,7 @@ impl ProjectInterpreter { active: Option, cache: &Cache, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result { // Resolve the Python request and requirement for the workspace. let WorkspacePython { @@ -1269,7 +1269,7 @@ impl ProjectEnvironment { cache: &Cache, dry_run: DryRun, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result { // Lock the project environment to avoid synchronization issues. let _lock = ProjectInterpreter::lock(workspace) @@ -1279,7 +1279,7 @@ impl ProjectEnvironment { }) .ok(); - let upgradeable = preview.is_enabled() + let upgradeable = preview.is_enabled(PreviewFeatures::PYTHON_UPGRADE) && python .as_ref() .is_none_or(|request| !request.includes_patch()); @@ -1501,7 +1501,7 @@ impl ScriptEnvironment { cache: &Cache, dry_run: DryRun, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result { // Lock the script environment to avoid synchronization issues. let _lock = ScriptInterpreter::lock(script) @@ -1658,7 +1658,7 @@ pub(crate) async fn resolve_names( cache: &Cache, workspace_cache: &WorkspaceCache, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result, uv_requirements::Error> { // Partition the requirements into named and unnamed requirements. let (mut requirements, unnamed): (Vec<_>, Vec<_>) = @@ -1829,7 +1829,7 @@ pub(crate) async fn resolve_environment( concurrency: Concurrency, cache: &Cache, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result { warn_on_requirements_txt_setting(&spec.requirements, settings); @@ -2017,7 +2017,7 @@ pub(crate) async fn sync_environment( concurrency: Concurrency, cache: &Cache, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result { let InstallerSettingsRef { index_locations, @@ -2175,7 +2175,7 @@ pub(crate) async fn update_environment( workspace_cache: WorkspaceCache, dry_run: DryRun, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result { warn_on_requirements_txt_setting(&spec, &settings.resolver); @@ -2416,7 +2416,7 @@ pub(crate) async fn init_script_python_requirement( client_builder: &BaseClientBuilder<'_>, cache: &Cache, reporter: &PythonDownloadReporter, - preview: PreviewMode, + preview: Preview, ) -> anyhow::Result { let python_request = if let Some(request) = python { // (1) Explicit request from user diff --git a/crates/uv/src/commands/project/remove.rs b/crates/uv/src/commands/project/remove.rs index 50615699e..1af4c818f 100644 --- a/crates/uv/src/commands/project/remove.rs +++ b/crates/uv/src/commands/project/remove.rs @@ -10,7 +10,7 @@ use tracing::{debug, warn}; use uv_cache::Cache; use uv_configuration::{ Concurrency, DependencyGroups, DryRun, EditableMode, ExtrasSpecification, InstallOptions, - PreviewMode, + Preview, }; use uv_fs::Simplified; use uv_normalize::{DEV_DEPENDENCIES, DefaultExtras, DefaultGroups}; @@ -60,7 +60,7 @@ pub(crate) async fn remove( no_config: bool, cache: &Cache, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result { let target = if let Some(script) = script { // If we found a PEP 723 script and the user provided a project-only setting, warn. diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index 6b44bf925..20e11db18 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -19,7 +19,7 @@ use uv_cli::ExternalCommand; use uv_client::BaseClientBuilder; use uv_configuration::{ Concurrency, Constraints, DependencyGroups, DryRun, EditableMode, ExtrasSpecification, - InstallOptions, PreviewMode, + InstallOptions, Preview, }; use uv_distribution_types::Requirement; use uv_fs::which::is_executable; @@ -94,7 +94,7 @@ pub(crate) async fn run( printer: Printer, env_file: Vec, no_env_file: bool, - preview: PreviewMode, + preview: Preview, max_recursion_depth: u32, ) -> anyhow::Result { // Check if max recursion depth was exceeded. This most commonly happens diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index cbce0082a..7d1b0f3ce 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -14,7 +14,7 @@ use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{ Concurrency, Constraints, DependencyGroups, DependencyGroupsWithDefaults, DryRun, EditableMode, ExtrasSpecification, ExtrasSpecificationWithDefaults, HashCheckingMode, InstallOptions, - PreviewMode, TargetTriple, + Preview, PreviewFeatures, TargetTriple, }; use uv_dispatch::BuildDispatch; use uv_distribution_types::{ @@ -77,12 +77,14 @@ pub(crate) async fn sync( no_config: bool, cache: &Cache, printer: Printer, - preview: PreviewMode, + preview: Preview, output_format: SyncFormat, ) -> Result { - if preview.is_enabled() && matches!(output_format, SyncFormat::Json) { + if preview.is_enabled(PreviewFeatures::JSON_OUTPUT) && matches!(output_format, SyncFormat::Json) + { warn_user!( - "The `--output-format json` option is experimental and the schema may change without warning. Pass `--preview` to disable this warning." + "The `--output-format json` option is experimental and the schema may change without warning. Pass `--preview-features {}` to disable this warning.", + PreviewFeatures::JSON_OUTPUT ); } @@ -564,7 +566,7 @@ pub(super) async fn do_sync( workspace_cache: WorkspaceCache, dry_run: DryRun, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result<(), ProjectError> { // Extract the project settings. let InstallerSettingsRef { diff --git a/crates/uv/src/commands/project/tree.rs b/crates/uv/src/commands/project/tree.rs index 756820dc7..1d594bd53 100644 --- a/crates/uv/src/commands/project/tree.rs +++ b/crates/uv/src/commands/project/tree.rs @@ -7,7 +7,7 @@ use tokio::sync::Semaphore; use uv_cache::{Cache, Refresh}; use uv_cache_info::Timestamp; use uv_client::RegistryClientBuilder; -use uv_configuration::{Concurrency, DependencyGroups, PreviewMode, TargetTriple}; +use uv_configuration::{Concurrency, DependencyGroups, Preview, TargetTriple}; use uv_distribution_types::IndexCapabilities; use uv_normalize::DefaultGroups; use uv_pep508::PackageName; @@ -57,7 +57,7 @@ pub(crate) async fn tree( no_config: bool, cache: &Cache, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result { // Find the project requirements. let workspace_cache = WorkspaceCache::default(); diff --git a/crates/uv/src/commands/project/version.rs b/crates/uv/src/commands/project/version.rs index c4b32485d..566573594 100644 --- a/crates/uv/src/commands/project/version.rs +++ b/crates/uv/src/commands/project/version.rs @@ -11,7 +11,7 @@ use uv_cli::version::VersionInfo; use uv_cli::{VersionBump, VersionFormat}; use uv_configuration::{ Concurrency, DependencyGroups, DependencyGroupsWithDefaults, DryRun, EditableMode, - ExtrasSpecification, InstallOptions, PreviewMode, + ExtrasSpecification, InstallOptions, Preview, }; use uv_fs::Simplified; use uv_normalize::DefaultExtras; @@ -76,7 +76,7 @@ pub(crate) async fn project_version( no_config: bool, cache: &Cache, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result { // Read the metadata let project = find_target(project_dir, package.as_ref(), explicit_project).await?; @@ -414,7 +414,7 @@ async fn print_frozen_version( short: bool, output_format: VersionFormat, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result { // Discover the interpreter (this is the same interpreter --no-sync uses). let interpreter = ProjectInterpreter::discover( @@ -509,7 +509,7 @@ async fn lock_and_sync( no_config: bool, cache: &Cache, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result { // If frozen, don't touch the lock or sync at all if frozen { diff --git a/crates/uv/src/commands/python/find.rs b/crates/uv/src/commands/python/find.rs index e188e9d20..806e670a8 100644 --- a/crates/uv/src/commands/python/find.rs +++ b/crates/uv/src/commands/python/find.rs @@ -3,7 +3,7 @@ use std::fmt::Write; use std::path::Path; use uv_cache::Cache; -use uv_configuration::{DependencyGroupsWithDefaults, PreviewMode}; +use uv_configuration::{DependencyGroupsWithDefaults, Preview}; use uv_fs::Simplified; use uv_python::{ EnvironmentPreference, PythonDownloads, PythonInstallation, PythonPreference, PythonRequest, @@ -32,7 +32,7 @@ pub(crate) async fn find( python_preference: PythonPreference, cache: &Cache, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result { let environment_preference = if system { EnvironmentPreference::OnlySystem @@ -123,7 +123,7 @@ pub(crate) async fn find_script( no_config: bool, cache: &Cache, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result { let interpreter = match ScriptInterpreter::discover( script, diff --git a/crates/uv/src/commands/python/install.rs b/crates/uv/src/commands/python/install.rs index e54c44424..f2082ce8a 100644 --- a/crates/uv/src/commands/python/install.rs +++ b/crates/uv/src/commands/python/install.rs @@ -14,7 +14,7 @@ use owo_colors::OwoColorize; use rustc_hash::{FxHashMap, FxHashSet}; use tracing::{debug, trace}; -use uv_configuration::PreviewMode; +use uv_configuration::{Preview, PreviewFeatures}; use uv_fs::Simplified; use uv_python::downloads::{ self, ArchRequest, DownloadResult, ManagedPythonDownload, PythonDownloadRequest, @@ -161,7 +161,7 @@ pub(crate) async fn install( default: bool, python_downloads: PythonDownloads, no_config: bool, - preview: PreviewMode, + preview: Preview, printer: Printer, ) -> Result { let start = std::time::Instant::now(); @@ -170,15 +170,17 @@ pub(crate) async fn install( // `--default` is used. It's not clear how this overlaps with a global Python pin, but I'd be // surprised if `uv python find` returned the "newest" Python version rather than the one I just // installed with the `--default` flag. - if default && !preview.is_enabled() { + if default && !preview.is_enabled(PreviewFeatures::PYTHON_INSTALL_DEFAULT) { warn_user!( - "The `--default` option is experimental and may change without warning. Pass `--preview` to disable this warning" + "The `--default` option is experimental and may change without warning. Pass `--preview-features {}` to disable this warning", + PreviewFeatures::PYTHON_INSTALL_DEFAULT ); } - if upgrade && preview.is_disabled() { + if upgrade && !preview.is_enabled(PreviewFeatures::PYTHON_UPGRADE) { warn_user!( - "`uv python upgrade` is experimental and may change without warning. Pass `--preview` to disable this warning" + "`uv python upgrade` is experimental and may change without warning. Pass `--preview-features {}` to disable this warning", + PreviewFeatures::PYTHON_UPGRADE ); } @@ -737,12 +739,13 @@ fn create_bin_links( installations: &[&ManagedPythonInstallation], changelog: &mut Changelog, errors: &mut Vec<(InstallErrorKind, PythonInstallationKey, Error)>, - preview: PreviewMode, + preview: Preview, ) { // TODO(zanieb): We want more feedback on the `is_default_install` behavior before stabilizing // it. In particular, it may be confusing because it does not apply when versions are loaded // from a `.python-version` file. - let targets = if (default || (is_default_install && preview.is_enabled())) + let targets = if (default + || (is_default_install && preview.is_enabled(PreviewFeatures::PYTHON_INSTALL_DEFAULT))) && first_request.matches_installation(installation) { vec![ diff --git a/crates/uv/src/commands/python/list.rs b/crates/uv/src/commands/python/list.rs index 17528a11e..c30eecf83 100644 --- a/crates/uv/src/commands/python/list.rs +++ b/crates/uv/src/commands/python/list.rs @@ -2,7 +2,7 @@ use serde::Serialize; use std::collections::BTreeSet; use std::fmt::Write; use uv_cli::PythonListFormat; -use uv_configuration::PreviewMode; +use uv_configuration::Preview; use uv_pep440::Version; use anyhow::Result; @@ -65,7 +65,7 @@ pub(crate) async fn list( python_downloads: PythonDownloads, cache: &Cache, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result { let request = request.as_deref().map(PythonRequest::parse); let base_download_request = if python_preference == PythonPreference::OnlySystem { diff --git a/crates/uv/src/commands/python/pin.rs b/crates/uv/src/commands/python/pin.rs index 0e78e6b5c..064cc780c 100644 --- a/crates/uv/src/commands/python/pin.rs +++ b/crates/uv/src/commands/python/pin.rs @@ -8,7 +8,7 @@ use tracing::debug; use uv_cache::Cache; use uv_client::BaseClientBuilder; -use uv_configuration::{DependencyGroupsWithDefaults, PreviewMode}; +use uv_configuration::{DependencyGroupsWithDefaults, Preview}; use uv_fs::Simplified; use uv_python::{ EnvironmentPreference, PYTHON_VERSION_FILENAME, PythonDownloads, PythonInstallation, @@ -39,7 +39,7 @@ pub(crate) async fn pin( network_settings: NetworkSettings, cache: &Cache, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result { let workspace_cache = WorkspaceCache::default(); let virtual_project = if no_project { @@ -270,7 +270,7 @@ fn warn_if_existing_pin_incompatible_with_project( virtual_project: &VirtualProject, python_preference: PythonPreference, cache: &Cache, - preview: PreviewMode, + preview: Preview, ) { // Check if the pinned version is compatible with the project. if let Some(pin_version) = pep440_version_from_request(pin) { diff --git a/crates/uv/src/commands/python/uninstall.rs b/crates/uv/src/commands/python/uninstall.rs index 3370e1f6f..b58b5831a 100644 --- a/crates/uv/src/commands/python/uninstall.rs +++ b/crates/uv/src/commands/python/uninstall.rs @@ -11,7 +11,7 @@ use owo_colors::OwoColorize; use rustc_hash::{FxHashMap, FxHashSet}; use tracing::{debug, warn}; -use uv_configuration::PreviewMode; +use uv_configuration::Preview; use uv_fs::Simplified; use uv_python::downloads::PythonDownloadRequest; use uv_python::managed::{ @@ -30,7 +30,7 @@ pub(crate) async fn uninstall( targets: Vec, all: bool, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result { let installations = ManagedPythonInstallations::from_settings(install_dir)?.init()?; @@ -66,7 +66,7 @@ async fn do_uninstall( targets: Vec, all: bool, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result { let start = std::time::Instant::now(); diff --git a/crates/uv/src/commands/tool/common.rs b/crates/uv/src/commands/tool/common.rs index b24a64e25..5647afa32 100644 --- a/crates/uv/src/commands/tool/common.rs +++ b/crates/uv/src/commands/tool/common.rs @@ -7,7 +7,7 @@ use std::{collections::BTreeSet, ffi::OsString}; use tracing::{debug, warn}; use uv_cache::Cache; use uv_client::BaseClientBuilder; -use uv_configuration::PreviewMode; +use uv_configuration::Preview; use uv_distribution_types::Requirement; use uv_distribution_types::{InstalledDist, Name}; use uv_fs::Simplified; @@ -81,7 +81,7 @@ pub(crate) async fn refine_interpreter( python_preference: PythonPreference, python_downloads: PythonDownloads, cache: &Cache, - preview: PreviewMode, + preview: Preview, ) -> anyhow::Result, ProjectError> { let pip::operations::Error::Resolve(uv_resolver::ResolveError::NoSolution(no_solution_err)) = err diff --git a/crates/uv/src/commands/tool/dir.rs b/crates/uv/src/commands/tool/dir.rs index b246e701d..7f937e35e 100644 --- a/crates/uv/src/commands/tool/dir.rs +++ b/crates/uv/src/commands/tool/dir.rs @@ -2,12 +2,12 @@ use anstream::println; use anyhow::Context; use owo_colors::OwoColorize; -use uv_configuration::PreviewMode; +use uv_configuration::Preview; use uv_fs::Simplified; use uv_tool::{InstalledTools, tool_executable_dir}; /// Show the tool directory. -pub(crate) fn dir(bin: bool, _preview: PreviewMode) -> anyhow::Result<()> { +pub(crate) fn dir(bin: bool, _preview: Preview) -> anyhow::Result<()> { if bin { let executable_directory = tool_executable_dir()?; println!("{}", executable_directory.simplified_display().cyan()); diff --git a/crates/uv/src/commands/tool/install.rs b/crates/uv/src/commands/tool/install.rs index 12de5fd1f..192597e93 100644 --- a/crates/uv/src/commands/tool/install.rs +++ b/crates/uv/src/commands/tool/install.rs @@ -8,7 +8,7 @@ use tracing::{debug, trace}; use uv_cache::{Cache, Refresh}; use uv_cache_info::Timestamp; use uv_client::BaseClientBuilder; -use uv_configuration::{Concurrency, Constraints, DryRun, PreviewMode, Reinstall, Upgrade}; +use uv_configuration::{Concurrency, Constraints, DryRun, Preview, Reinstall, Upgrade}; use uv_distribution_types::{ NameRequirementSpecification, Requirement, RequirementSource, UnresolvedRequirementSpecification, @@ -62,7 +62,7 @@ pub(crate) async fn install( concurrency: Concurrency, cache: Cache, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result { let client_builder = BaseClientBuilder::new() .retries_from_env()? diff --git a/crates/uv/src/commands/tool/run.rs b/crates/uv/src/commands/tool/run.rs index 7c91b9fe9..1c86feb3a 100644 --- a/crates/uv/src/commands/tool/run.rs +++ b/crates/uv/src/commands/tool/run.rs @@ -17,7 +17,7 @@ use uv_cache_info::Timestamp; use uv_cli::ExternalCommand; use uv_client::BaseClientBuilder; use uv_configuration::Constraints; -use uv_configuration::{Concurrency, PreviewMode}; +use uv_configuration::{Concurrency, Preview}; use uv_distribution_types::InstalledDist; use uv_distribution_types::{ IndexUrl, Name, NameRequirementSpecification, Requirement, RequirementSource, @@ -101,7 +101,7 @@ pub(crate) async fn run( printer: Printer, env_file: Vec, no_env_file: bool, - preview: PreviewMode, + preview: Preview, ) -> anyhow::Result { /// Whether or not a path looks like a Python script based on the file extension. fn has_python_script_ext(path: &Path) -> bool { @@ -686,7 +686,7 @@ async fn get_or_create_environment( concurrency: Concurrency, cache: &Cache, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result<(ToolRequirement, PythonEnvironment), ProjectError> { let client_builder = BaseClientBuilder::new() .retries_from_env()? diff --git a/crates/uv/src/commands/tool/upgrade.rs b/crates/uv/src/commands/tool/upgrade.rs index 9d2d32a21..13e1f2ae3 100644 --- a/crates/uv/src/commands/tool/upgrade.rs +++ b/crates/uv/src/commands/tool/upgrade.rs @@ -7,7 +7,7 @@ use tracing::debug; use uv_cache::Cache; use uv_client::BaseClientBuilder; -use uv_configuration::{Concurrency, Constraints, DryRun, PreviewMode}; +use uv_configuration::{Concurrency, Constraints, DryRun, Preview}; use uv_distribution_types::Requirement; use uv_fs::CWD; use uv_normalize::PackageName; @@ -47,7 +47,7 @@ pub(crate) async fn upgrade( concurrency: Concurrency, cache: &Cache, printer: Printer, - preview: PreviewMode, + preview: Preview, ) -> Result { let installed_tools = InstalledTools::from_settings()?.init()?; let _lock = installed_tools.lock().await?; @@ -221,7 +221,7 @@ async fn upgrade_tool( filesystem: &ResolverInstallerOptions, installer_metadata: bool, concurrency: Concurrency, - preview: PreviewMode, + preview: Preview, ) -> Result { // Ensure the tool is installed. let existing_tool_receipt = match installed_tools.get_tool_receipt(name) { diff --git a/crates/uv/src/commands/venv.rs b/crates/uv/src/commands/venv.rs index cd44924ab..1f2ce3dfb 100644 --- a/crates/uv/src/commands/venv.rs +++ b/crates/uv/src/commands/venv.rs @@ -11,7 +11,8 @@ use uv_cache::Cache; use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{ BuildOptions, Concurrency, ConfigSettings, Constraints, DependencyGroups, IndexStrategy, - KeyringProviderType, NoBinary, NoBuild, PackageConfigSettings, PreviewMode, SourceStrategy, + KeyringProviderType, NoBinary, NoBuild, PackageConfigSettings, Preview, PreviewFeatures, + SourceStrategy, }; use uv_dispatch::{BuildDispatch, SharedState}; use uv_distribution_types::Requirement; @@ -81,7 +82,7 @@ pub(crate) async fn venv( cache: &Cache, printer: Printer, relocatable: bool, - preview: PreviewMode, + preview: Preview, ) -> Result { let workspace_cache = WorkspaceCache::default(); let project = if no_project { @@ -198,7 +199,7 @@ pub(crate) async fn venv( path.user_display().cyan() )?; - let upgradeable = preview.is_enabled() + let upgradeable = preview.is_enabled(PreviewFeatures::PYTHON_UPGRADE) && python_request .as_ref() .is_none_or(|request| !request.includes_patch()); diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 534640f94..b563b0b8e 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -23,9 +23,9 @@ use uv_client::Connectivity; use uv_configuration::{ BuildOptions, Concurrency, ConfigSettings, DependencyGroups, DryRun, EditableMode, ExportFormat, ExtrasSpecification, HashCheckingMode, IndexStrategy, InstallOptions, - KeyringProviderType, NoBinary, NoBuild, PackageConfigSettings, PreviewMode, - ProjectBuildBackend, Reinstall, RequiredVersion, SourceStrategy, TargetTriple, TrustedHost, - TrustedPublishing, Upgrade, VersionControlSystem, + KeyringProviderType, NoBinary, NoBuild, PackageConfigSettings, Preview, ProjectBuildBackend, + Reinstall, RequiredVersion, SourceStrategy, TargetTriple, TrustedHost, TrustedPublishing, + Upgrade, VersionControlSystem, }; use uv_distribution_types::{DependencyMetadata, Index, IndexLocations, IndexUrl, Requirement}; use uv_install_wheel::LinkMode; @@ -63,7 +63,7 @@ pub(crate) struct GlobalSettings { pub(crate) network_settings: NetworkSettings, pub(crate) concurrency: Concurrency, pub(crate) show_settings: bool, - pub(crate) preview: PreviewMode, + pub(crate) preview: Preview, pub(crate) python_preference: PythonPreference, pub(crate) python_downloads: PythonDownloads, pub(crate) no_progress: bool, @@ -117,10 +117,12 @@ impl GlobalSettings { .unwrap_or_else(Concurrency::threads), }, show_settings: args.show_settings, - preview: PreviewMode::from( + preview: Preview::from_args( flag(args.preview, args.no_preview, "preview") .combine(workspace.and_then(|workspace| workspace.globals.preview)) .unwrap_or(false), + args.no_preview, + &args.preview_features, ), python_preference, python_downloads: flag( diff --git a/crates/uv/tests/it/common/mod.rs b/crates/uv/tests/it/common/mod.rs index 65483ffd6..d7fbdaa6f 100644 --- a/crates/uv/tests/it/common/mod.rs +++ b/crates/uv/tests/it/common/mod.rs @@ -21,7 +21,7 @@ use regex::Regex; use tokio::io::AsyncWriteExt; use uv_cache::Cache; -use uv_configuration::PreviewMode; +use uv_configuration::Preview; use uv_fs::Simplified; use uv_python::managed::ManagedPythonInstallations; use uv_python::{ @@ -1505,7 +1505,7 @@ pub fn python_installations_for_versions( EnvironmentPreference::OnlySystem, PythonPreference::Managed, &cache, - PreviewMode::Disabled, + Preview::default(), ) { python.into_interpreter().sys_executable().to_owned() } else { diff --git a/crates/uv/tests/it/edit.rs b/crates/uv/tests/it/edit.rs index 05527b139..7fa46f0c3 100644 --- a/crates/uv/tests/it/edit.rs +++ b/crates/uv/tests/it/edit.rs @@ -13021,7 +13021,7 @@ fn add_bounds() -> Result<()> { ----- stdout ----- ----- stderr ----- - warning: The bounds option is in preview and may change in any future release. + warning: The `bounds` option is in preview and may change in any future release. Pass `--preview-features add-bounds` to disable this warning. Resolved 2 packages in [TIME] Prepared 1 package in [TIME] Installed 1 package in [TIME] @@ -13061,7 +13061,7 @@ fn add_bounds() -> Result<()> { ----- stdout ----- ----- stderr ----- - warning: The bounds option is in preview and may change in any future release. + warning: The `bounds` option is in preview and may change in any future release. Pass `--preview-features add-bounds` to disable this warning. Resolved 4 packages in [TIME] Prepared 2 packages in [TIME] Installed 2 packages in [TIME] diff --git a/crates/uv/tests/it/python_install.rs b/crates/uv/tests/it/python_install.rs index 41b046026..c5f98af0d 100644 --- a/crates/uv/tests/it/python_install.rs +++ b/crates/uv/tests/it/python_install.rs @@ -630,14 +630,14 @@ fn python_install_preview() { "###); // Should be a no-op when already installed - uv_snapshot!(context.filters(), context.python_install().arg("--preview"), @r###" + uv_snapshot!(context.filters(), context.python_install().arg("--preview"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Python is already installed. Use `uv python install ` to install another version. - "###); + "); // You can opt-in to a reinstall uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("--reinstall"), @r" @@ -1260,7 +1260,7 @@ fn python_install_default() { ----- stdout ----- ----- stderr ----- - warning: The `--default` option is experimental and may change without warning. Pass `--preview` to disable this warning + warning: The `--default` option is experimental and may change without warning. Pass `--preview-features python-install-default` to disable this warning Installed Python 3.13.5 in [TIME] + cpython-3.13.5-[PLATFORM] (python, python3) "); @@ -1294,7 +1294,7 @@ fn python_install_default() { ----- stdout ----- ----- stderr ----- - warning: The `--default` option is experimental and may change without warning. Pass `--preview` to disable this warning + warning: The `--default` option is experimental and may change without warning. Pass `--preview-features python-install-default` to disable this warning Installed Python 3.13.5 in [TIME] + cpython-3.13.5-[PLATFORM] (python, python3, python3.13) "); @@ -1379,7 +1379,7 @@ fn python_install_default() { ----- stdout ----- ----- stderr ----- - warning: The `--default` option is experimental and may change without warning. Pass `--preview` to disable this warning + warning: The `--default` option is experimental and may change without warning. Pass `--preview-features python-install-default` to disable this warning error: The `--default` flag cannot be used with multiple targets "); @@ -1390,7 +1390,7 @@ fn python_install_default() { ----- stdout ----- ----- stderr ----- - warning: The `--default` option is experimental and may change without warning. Pass `--preview` to disable this warning + warning: The `--default` option is experimental and may change without warning. Pass `--preview-features python-install-default` to disable this warning Installed Python 3.12.11 in [TIME] + cpython-3.12.11-[PLATFORM] (python, python3, python3.12) "); diff --git a/crates/uv/tests/it/show_settings.rs b/crates/uv/tests/it/show_settings.rs index bbcddd2b1..65555ba56 100644 --- a/crates/uv/tests/it/show_settings.rs +++ b/crates/uv/tests/it/show_settings.rs @@ -71,7 +71,11 @@ fn resolve_uv_toml() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -256,7 +260,11 @@ fn resolve_uv_toml() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -442,7 +450,11 @@ fn resolve_uv_toml() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -660,7 +672,11 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -847,7 +863,11 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -1010,7 +1030,11 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -1222,7 +1246,11 @@ fn resolve_index_url() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -1442,7 +1470,11 @@ fn resolve_index_url() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -1720,7 +1752,11 @@ fn resolve_find_links() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -1929,7 +1965,11 @@ fn resolve_top_level() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -2097,7 +2137,11 @@ fn resolve_top_level() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -2315,7 +2359,11 @@ fn resolve_top_level() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -2556,7 +2604,11 @@ fn resolve_user_configuration() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -2714,7 +2766,11 @@ fn resolve_user_configuration() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -2872,7 +2928,11 @@ fn resolve_user_configuration() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -3032,7 +3092,11 @@ fn resolve_user_configuration() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -3211,7 +3275,11 @@ fn resolve_tool() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -3380,7 +3448,11 @@ fn resolve_poetry_toml() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -3572,7 +3644,11 @@ fn resolve_both() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -3802,7 +3878,11 @@ fn resolve_both_special_fields() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -4111,7 +4191,11 @@ fn resolve_config_file() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -4396,7 +4480,11 @@ fn resolve_skip_empty() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -4557,7 +4645,11 @@ fn resolve_skip_empty() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -4737,7 +4829,11 @@ fn allow_insecure_host() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -4909,7 +5005,11 @@ fn index_priority() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -5129,7 +5229,11 @@ fn index_priority() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -5355,7 +5459,11 @@ fn index_priority() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -5576,7 +5684,11 @@ fn index_priority() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -5804,7 +5916,11 @@ fn index_priority() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -6025,7 +6141,11 @@ fn index_priority() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -6259,7 +6379,11 @@ fn verify_hashes() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -6410,7 +6534,11 @@ fn verify_hashes() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -6559,7 +6687,11 @@ fn verify_hashes() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -6710,7 +6842,11 @@ fn verify_hashes() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -6859,7 +6995,11 @@ fn verify_hashes() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -7009,7 +7149,11 @@ fn verify_hashes() -> anyhow::Result<()> { installs: 8, }, show_settings: true, - preview: Disabled, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, python_preference: Managed, python_downloads: Automatic, no_progress: false, @@ -7138,3 +7282,633 @@ fn verify_hashes() -> anyhow::Result<()> { Ok(()) } + +/// Test preview feature flagging. +#[test] +#[cfg_attr( + windows, + ignore = "Configuration tests are not yet supported on Windows" +)] +fn preview_features() { + let context = TestContext::new("3.12"); + + let cmd = || { + let mut cmd = context.version(); + cmd.arg("--show-settings"); + add_shared_args(cmd, context.temp_dir.path()) + }; + + uv_snapshot!(context.filters(), cmd().arg("--preview"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + GlobalSettings { + required_version: None, + quiet: 0, + verbose: 0, + color: Auto, + network_settings: NetworkSettings { + connectivity: Online, + native_tls: false, + allow_insecure_host: [], + }, + concurrency: Concurrency { + downloads: 50, + builds: 16, + installs: 8, + }, + show_settings: true, + preview: Preview { + flags: PreviewFeatures( + PYTHON_INSTALL_DEFAULT | PYTHON_UPGRADE | JSON_OUTPUT | PYLOCK | ADD_BOUNDS, + ), + }, + python_preference: Managed, + python_downloads: Automatic, + no_progress: false, + installer_metadata: true, + } + CacheSettings { + no_cache: false, + cache_dir: Some( + "[CACHE_DIR]/", + ), + } + VersionSettings { + value: None, + bump: [], + short: false, + output_format: Text, + dry_run: false, + locked: false, + frozen: false, + active: None, + no_sync: false, + package: None, + python: None, + install_mirrors: PythonInstallMirrors { + python_install_mirror: None, + pypy_install_mirror: None, + python_downloads_json_url: None, + }, + refresh: None( + Timestamp( + SystemTime { + tv_sec: [TIME], + tv_nsec: [TIME], + }, + ), + ), + settings: ResolverInstallerSettings { + resolver: ResolverSettings { + build_options: BuildOptions { + no_binary: None, + no_build: None, + }, + config_setting: ConfigSettings( + {}, + ), + config_settings_package: PackageConfigSettings( + {}, + ), + dependency_metadata: DependencyMetadata( + {}, + ), + exclude_newer: None, + fork_strategy: RequiresPython, + index_locations: IndexLocations { + indexes: [], + flat_index: [], + no_index: false, + }, + index_strategy: FirstIndex, + keyring_provider: Disabled, + link_mode: Clone, + no_build_isolation: false, + no_build_isolation_package: [], + prerelease: IfNecessaryOrExplicit, + resolution: Highest, + sources: Enabled, + upgrade: None, + }, + compile_bytecode: false, + reinstall: None, + }, + } + + ----- stderr ----- + "# + ); + + uv_snapshot!(context.filters(), cmd().arg("--preview").arg("--no-preview"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + GlobalSettings { + required_version: None, + quiet: 0, + verbose: 0, + color: Auto, + network_settings: NetworkSettings { + connectivity: Online, + native_tls: false, + allow_insecure_host: [], + }, + concurrency: Concurrency { + downloads: 50, + builds: 16, + installs: 8, + }, + show_settings: true, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, + python_preference: Managed, + python_downloads: Automatic, + no_progress: false, + installer_metadata: true, + } + CacheSettings { + no_cache: false, + cache_dir: Some( + "[CACHE_DIR]/", + ), + } + VersionSettings { + value: None, + bump: [], + short: false, + output_format: Text, + dry_run: false, + locked: false, + frozen: false, + active: None, + no_sync: false, + package: None, + python: None, + install_mirrors: PythonInstallMirrors { + python_install_mirror: None, + pypy_install_mirror: None, + python_downloads_json_url: None, + }, + refresh: None( + Timestamp( + SystemTime { + tv_sec: [TIME], + tv_nsec: [TIME], + }, + ), + ), + settings: ResolverInstallerSettings { + resolver: ResolverSettings { + build_options: BuildOptions { + no_binary: None, + no_build: None, + }, + config_setting: ConfigSettings( + {}, + ), + config_settings_package: PackageConfigSettings( + {}, + ), + dependency_metadata: DependencyMetadata( + {}, + ), + exclude_newer: None, + fork_strategy: RequiresPython, + index_locations: IndexLocations { + indexes: [], + flat_index: [], + no_index: false, + }, + index_strategy: FirstIndex, + keyring_provider: Disabled, + link_mode: Clone, + no_build_isolation: false, + no_build_isolation_package: [], + prerelease: IfNecessaryOrExplicit, + resolution: Highest, + sources: Enabled, + upgrade: None, + }, + compile_bytecode: false, + reinstall: None, + }, + } + + ----- stderr ----- + "# + ); + + uv_snapshot!(context.filters(), cmd().arg("--preview").arg("--preview-features").arg("python-install-default"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + GlobalSettings { + required_version: None, + quiet: 0, + verbose: 0, + color: Auto, + network_settings: NetworkSettings { + connectivity: Online, + native_tls: false, + allow_insecure_host: [], + }, + concurrency: Concurrency { + downloads: 50, + builds: 16, + installs: 8, + }, + show_settings: true, + preview: Preview { + flags: PreviewFeatures( + PYTHON_INSTALL_DEFAULT | PYTHON_UPGRADE | JSON_OUTPUT | PYLOCK | ADD_BOUNDS, + ), + }, + python_preference: Managed, + python_downloads: Automatic, + no_progress: false, + installer_metadata: true, + } + CacheSettings { + no_cache: false, + cache_dir: Some( + "[CACHE_DIR]/", + ), + } + VersionSettings { + value: None, + bump: [], + short: false, + output_format: Text, + dry_run: false, + locked: false, + frozen: false, + active: None, + no_sync: false, + package: None, + python: None, + install_mirrors: PythonInstallMirrors { + python_install_mirror: None, + pypy_install_mirror: None, + python_downloads_json_url: None, + }, + refresh: None( + Timestamp( + SystemTime { + tv_sec: [TIME], + tv_nsec: [TIME], + }, + ), + ), + settings: ResolverInstallerSettings { + resolver: ResolverSettings { + build_options: BuildOptions { + no_binary: None, + no_build: None, + }, + config_setting: ConfigSettings( + {}, + ), + config_settings_package: PackageConfigSettings( + {}, + ), + dependency_metadata: DependencyMetadata( + {}, + ), + exclude_newer: None, + fork_strategy: RequiresPython, + index_locations: IndexLocations { + indexes: [], + flat_index: [], + no_index: false, + }, + index_strategy: FirstIndex, + keyring_provider: Disabled, + link_mode: Clone, + no_build_isolation: false, + no_build_isolation_package: [], + prerelease: IfNecessaryOrExplicit, + resolution: Highest, + sources: Enabled, + upgrade: None, + }, + compile_bytecode: false, + reinstall: None, + }, + } + + ----- stderr ----- + "# + ); + + uv_snapshot!(context.filters(), cmd().arg("--preview-features").arg("python-install-default,python-upgrade"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + GlobalSettings { + required_version: None, + quiet: 0, + verbose: 0, + color: Auto, + network_settings: NetworkSettings { + connectivity: Online, + native_tls: false, + allow_insecure_host: [], + }, + concurrency: Concurrency { + downloads: 50, + builds: 16, + installs: 8, + }, + show_settings: true, + preview: Preview { + flags: PreviewFeatures( + PYTHON_INSTALL_DEFAULT | PYTHON_UPGRADE, + ), + }, + python_preference: Managed, + python_downloads: Automatic, + no_progress: false, + installer_metadata: true, + } + CacheSettings { + no_cache: false, + cache_dir: Some( + "[CACHE_DIR]/", + ), + } + VersionSettings { + value: None, + bump: [], + short: false, + output_format: Text, + dry_run: false, + locked: false, + frozen: false, + active: None, + no_sync: false, + package: None, + python: None, + install_mirrors: PythonInstallMirrors { + python_install_mirror: None, + pypy_install_mirror: None, + python_downloads_json_url: None, + }, + refresh: None( + Timestamp( + SystemTime { + tv_sec: [TIME], + tv_nsec: [TIME], + }, + ), + ), + settings: ResolverInstallerSettings { + resolver: ResolverSettings { + build_options: BuildOptions { + no_binary: None, + no_build: None, + }, + config_setting: ConfigSettings( + {}, + ), + config_settings_package: PackageConfigSettings( + {}, + ), + dependency_metadata: DependencyMetadata( + {}, + ), + exclude_newer: None, + fork_strategy: RequiresPython, + index_locations: IndexLocations { + indexes: [], + flat_index: [], + no_index: false, + }, + index_strategy: FirstIndex, + keyring_provider: Disabled, + link_mode: Clone, + no_build_isolation: false, + no_build_isolation_package: [], + prerelease: IfNecessaryOrExplicit, + resolution: Highest, + sources: Enabled, + upgrade: None, + }, + compile_bytecode: false, + reinstall: None, + }, + } + + ----- stderr ----- + "# + ); + + uv_snapshot!(context.filters(), cmd().arg("--preview-features").arg("python-install-default").arg("--preview-feature").arg("python-upgrade"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + GlobalSettings { + required_version: None, + quiet: 0, + verbose: 0, + color: Auto, + network_settings: NetworkSettings { + connectivity: Online, + native_tls: false, + allow_insecure_host: [], + }, + concurrency: Concurrency { + downloads: 50, + builds: 16, + installs: 8, + }, + show_settings: true, + preview: Preview { + flags: PreviewFeatures( + PYTHON_INSTALL_DEFAULT | PYTHON_UPGRADE, + ), + }, + python_preference: Managed, + python_downloads: Automatic, + no_progress: false, + installer_metadata: true, + } + CacheSettings { + no_cache: false, + cache_dir: Some( + "[CACHE_DIR]/", + ), + } + VersionSettings { + value: None, + bump: [], + short: false, + output_format: Text, + dry_run: false, + locked: false, + frozen: false, + active: None, + no_sync: false, + package: None, + python: None, + install_mirrors: PythonInstallMirrors { + python_install_mirror: None, + pypy_install_mirror: None, + python_downloads_json_url: None, + }, + refresh: None( + Timestamp( + SystemTime { + tv_sec: [TIME], + tv_nsec: [TIME], + }, + ), + ), + settings: ResolverInstallerSettings { + resolver: ResolverSettings { + build_options: BuildOptions { + no_binary: None, + no_build: None, + }, + config_setting: ConfigSettings( + {}, + ), + config_settings_package: PackageConfigSettings( + {}, + ), + dependency_metadata: DependencyMetadata( + {}, + ), + exclude_newer: None, + fork_strategy: RequiresPython, + index_locations: IndexLocations { + indexes: [], + flat_index: [], + no_index: false, + }, + index_strategy: FirstIndex, + keyring_provider: Disabled, + link_mode: Clone, + no_build_isolation: false, + no_build_isolation_package: [], + prerelease: IfNecessaryOrExplicit, + resolution: Highest, + sources: Enabled, + upgrade: None, + }, + compile_bytecode: false, + reinstall: None, + }, + } + + ----- stderr ----- + "# + ); + + uv_snapshot!(context.filters(), cmd() + .arg("--preview-features").arg("python-install-default").arg("--preview-feature").arg("python-upgrade") + .arg("--no-preview"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + GlobalSettings { + required_version: None, + quiet: 0, + verbose: 0, + color: Auto, + network_settings: NetworkSettings { + connectivity: Online, + native_tls: false, + allow_insecure_host: [], + }, + concurrency: Concurrency { + downloads: 50, + builds: 16, + installs: 8, + }, + show_settings: true, + preview: Preview { + flags: PreviewFeatures( + 0x0, + ), + }, + python_preference: Managed, + python_downloads: Automatic, + no_progress: false, + installer_metadata: true, + } + CacheSettings { + no_cache: false, + cache_dir: Some( + "[CACHE_DIR]/", + ), + } + VersionSettings { + value: None, + bump: [], + short: false, + output_format: Text, + dry_run: false, + locked: false, + frozen: false, + active: None, + no_sync: false, + package: None, + python: None, + install_mirrors: PythonInstallMirrors { + python_install_mirror: None, + pypy_install_mirror: None, + python_downloads_json_url: None, + }, + refresh: None( + Timestamp( + SystemTime { + tv_sec: [TIME], + tv_nsec: [TIME], + }, + ), + ), + settings: ResolverInstallerSettings { + resolver: ResolverSettings { + build_options: BuildOptions { + no_binary: None, + no_build: None, + }, + config_setting: ConfigSettings( + {}, + ), + config_settings_package: PackageConfigSettings( + {}, + ), + dependency_metadata: DependencyMetadata( + {}, + ), + exclude_newer: None, + fork_strategy: RequiresPython, + index_locations: IndexLocations { + indexes: [], + flat_index: [], + no_index: false, + }, + index_strategy: FirstIndex, + keyring_provider: Disabled, + link_mode: Clone, + no_build_isolation: false, + no_build_isolation_package: [], + prerelease: IfNecessaryOrExplicit, + resolution: Highest, + sources: Enabled, + upgrade: None, + }, + compile_bytecode: false, + reinstall: None, + }, + } + + ----- stderr ----- + "# + ); +} diff --git a/docs/reference/environment.md b/docs/reference/environment.md index 6b93dbe7e..1958b8780 100644 --- a/docs/reference/environment.md +++ b/docs/reference/environment.md @@ -306,6 +306,10 @@ Equivalent to the `--prerelease` command-line argument. For example, if set to Equivalent to the `--preview` argument. Enables preview mode. +### `UV_PREVIEW_FEATURES` + +Equivalent to the `--preview-features` argument. Enables specific preview features. + ### `UV_PROJECT` Equivalent to the `--project` command-line argument. From 8d9d929d3b4c7a49aea727b323c304f37279b01b Mon Sep 17 00:00:00 2001 From: Ben Beasley Date: Fri, 25 Jul 2025 12:09:38 -0400 Subject: [PATCH 344/349] Update Rust crate console to 0.16.0 (#14890) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary This revisits https://github.com/astral-sh/uv/pull/14364, which was opened by the renovate bot and originally failed with an error I don’t quite understand in https://github.com/astral-sh/uv/pull/14364#issuecomment-3017545431. Since 852aba4f90988b7bd437573b721062228d504b49 updated to `indicatif` 0.18, we now already have `console` 0.16 in the dependency tree. This PR adjusts the direct dependency on `console` to match. The only breaking change in [`console` 0.16.0](https://github.com/console-rs/console/releases/tag/0.16.0) is that crates that depend on `console` with `default-features = False` may need to explicitly enable the new `std` feature. This is the case for `uv`: while I did find that `cargo test` passes with just the `console` dependency version adjusted, this is due to [feature unification](https://doc.rust-lang.org/cargo/reference/features.html#feature-unification), i.e., the indirect dependency on `console` via `indicatif` 0.18 already requires its `std` feature. We can see by inspection that `uv` should also have a direct dependency on `console` with the `std` feature. For example, see: https://github.com/astral-sh/uv/blob/05031becc3762b8b298e98046d6514a6cb6fb244/crates/uv-console/src/lib.rs#L1 and note that `Term` is gated by the `std` feature in https://github.com/console-rs/console/blob/a51fcead7cda1fc6f5ac552a5588aaba8c069639/src/lib.rs#L90-L93 The addition of `features = ["std"]` is the key difference between this PR and https://github.com/astral-sh/uv/pull/14364. ## Test Plan `cargo test` --- Cargo.lock | 26 +++++++++++++------------- Cargo.toml | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b22a79c0f..54b0ccbff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -759,7 +759,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] @@ -1148,7 +1148,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1987,7 +1987,7 @@ checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" dependencies = [ "hermit-abi 0.4.0", "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2047,7 +2047,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2900,7 +2900,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3332,7 +3332,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3345,7 +3345,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.2", - "windows-sys 0.60.2", + "windows-sys 0.52.0", ] [[package]] @@ -3928,7 +3928,7 @@ dependencies = [ "getrandom 0.3.1", "once_cell", "rustix 1.0.8", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -4656,7 +4656,7 @@ dependencies = [ "base64 0.22.1", "byteorder", "clap", - "console 0.15.11", + "console 0.16.0", "ctrlc", "dotenvy", "dunce", @@ -5069,7 +5069,7 @@ dependencies = [ name = "uv-console" version = "0.0.1" dependencies = [ - "console 0.15.11", + "console 0.16.0", ] [[package]] @@ -5706,7 +5706,7 @@ version = "0.1.0" dependencies = [ "anyhow", "configparser", - "console 0.15.11", + "console 0.16.0", "fs-err 3.1.1", "futures", "rustc-hash", @@ -6013,7 +6013,7 @@ version = "0.8.3" name = "uv-virtualenv" version = "0.0.4" dependencies = [ - "console 0.15.11", + "console 0.16.0", "fs-err 3.1.1", "itertools 0.14.0", "owo-colors", @@ -6341,7 +6341,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index e89f786ea..d81535832 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,7 +93,7 @@ cargo-util = { version = "0.2.14" } clap = { version = "4.5.17", features = ["derive", "env", "string", "wrap_help"] } clap_complete_command = { version = "0.6.1" } configparser = { version = "3.1.0" } -console = { version = "0.15.11", default-features = false } +console = { version = "0.16.0", default-features = false, features = ["std"] } csv = { version = "1.3.0" } ctrlc = { version = "3.4.5" } dashmap = { version = "6.1.0" } From 7f91c49701a61b66b42ab831c5b587ad15a770bd Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Fri, 25 Jul 2025 11:23:05 -0500 Subject: [PATCH 345/349] Bump `dirs` to `6.0.0` to update `windows-sys` versions (#14898) See https://codeberg.org/dirs/dirs-rs#6 --- Cargo.lock | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 54b0ccbff..28c17ea06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1035,23 +1035,23 @@ dependencies = [ [[package]] name = "dirs" -version = "5.0.1" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ "dirs-sys", ] [[package]] name = "dirs-sys" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.48.0", + "windows-sys 0.60.2", ] [[package]] @@ -1148,7 +1148,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1591,11 +1591,11 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "home" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1987,7 +1987,7 @@ checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" dependencies = [ "hermit-abi 0.4.0", "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2047,7 +2047,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2900,7 +2900,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2994,13 +2994,13 @@ dependencies = [ [[package]] name = "redox_users" -version = "0.4.6" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ "getrandom 0.2.15", "libredox", - "thiserror 1.0.69", + "thiserror 2.0.12", ] [[package]] @@ -3332,7 +3332,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3345,7 +3345,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.2", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] @@ -3654,9 +3654,9 @@ checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f" [[package]] name = "shellexpand" -version = "3.1.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da03fa3b94cc19e3ebfc88c4229c49d8f08cdbd1228870a45f0ffdf84988e14b" +checksum = "8b1fdf65dd6331831494dd616b30351c38e96e45921a27745cf98490458b90bb" dependencies = [ "bstr", "dirs", @@ -3928,7 +3928,7 @@ dependencies = [ "getrandom 0.3.1", "once_cell", "rustix 1.0.8", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] From a701d3c4470e5c5a1f6583394fd1ee35e7480375 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Fri, 25 Jul 2025 13:57:49 -0500 Subject: [PATCH 346/349] Use workspace dependencies for crate dev-dependencies (#14903) --- Cargo.lock | 17 ++++++++--------- Cargo.toml | 17 +++++++++++++++++ crates/uv-auth/Cargo.toml | 4 ++-- crates/uv-build-backend/Cargo.toml | 2 +- crates/uv-build-frontend/Cargo.toml | 2 +- crates/uv-cli/Cargo.toml | 2 +- crates/uv-client/Cargo.toml | 8 ++++---- crates/uv-dirs/Cargo.toml | 2 +- crates/uv-distribution-filename/Cargo.toml | 2 +- crates/uv-distribution/Cargo.toml | 2 +- crates/uv-globfilter/Cargo.toml | 2 +- crates/uv-install-wheel/Cargo.toml | 2 +- crates/uv-pep508/Cargo.toml | 4 ++-- crates/uv-platform-tags/Cargo.toml | 2 +- crates/uv-publish/Cargo.toml | 2 +- crates/uv-pypi-types/Cargo.toml | 2 +- crates/uv-python/Cargo.toml | 10 +++++----- crates/uv-requirements-txt/Cargo.toml | 10 +++++----- crates/uv-resolver/Cargo.toml | 2 +- crates/uv-trampoline-builder/Cargo.toml | 4 ++-- crates/uv-workspace/Cargo.toml | 4 ++-- crates/uv/Cargo.toml | 18 +++++++++--------- 22 files changed, 68 insertions(+), 52 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 28c17ea06..22213c3f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2584,9 +2584,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.15" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" +checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" dependencies = [ "memchr", "thiserror 2.0.12", @@ -2595,9 +2595,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.15" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" +checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc" dependencies = [ "pest", "pest_generator", @@ -2605,9 +2605,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.15" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" +checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966" dependencies = [ "pest", "pest_meta", @@ -2618,11 +2618,10 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.15" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" +checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5" dependencies = [ - "once_cell", "pest", "sha2", ] diff --git a/Cargo.toml b/Cargo.toml index d81535832..f11b91556 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -194,6 +194,23 @@ wiremock = { version = "0.6.4" } xz2 = { version = "0.1.7" } zip = { version = "2.2.3", default-features = false, features = ["deflate", "zstd", "bzip2", "lzma", "xz"] } +# dev-dependencies +assert_cmd = { version = "2.0.16" } +assert_fs = { version = "1.1.2" } +byteorder = { version = "1.5.0" } +filetime = { version = "0.2.25" } +http-body-util = { version = "0.1.2" } +hyper = { version = "1.4.1", features = ["server", "http1"] } +hyper-util = { version = "0.1.8", features = ["tokio"] } +ignore = { version = "0.4.23" } +insta = { version = "1.40.0", features = ["json", "filters", "redactions"] } +predicates = { version = "3.1.2" } +similar = { version = "2.6.0" } +temp-env = { version = "0.3.6" } +test-case = { version = "3.3.1" } +test-log = { version = "0.2.16", features = ["trace"], default-features = false } +whoami = { version = "1.6.0" } + [workspace.metadata.cargo-shear] ignored = ["flate2", "xz2"] diff --git a/crates/uv-auth/Cargo.toml b/crates/uv-auth/Cargo.toml index e63fb1a50..cbe4d4787 100644 --- a/crates/uv-auth/Cargo.toml +++ b/crates/uv-auth/Cargo.toml @@ -33,8 +33,8 @@ tracing = { workspace = true } url = { workspace = true } [dev-dependencies] -insta = { version = "1.40.0" } +insta = { workspace = true } tempfile = { workspace = true } -test-log = { version = "0.2.16", features = ["trace"], default-features = false } +test-log = { workspace = true } tokio = { workspace = true } wiremock = { workspace = true } diff --git a/crates/uv-build-backend/Cargo.toml b/crates/uv-build-backend/Cargo.toml index 677cbc222..c1b52340a 100644 --- a/crates/uv-build-backend/Cargo.toml +++ b/crates/uv-build-backend/Cargo.toml @@ -56,6 +56,6 @@ schemars = ["dep:schemars", "uv-pypi-types/schemars"] [dev-dependencies] indoc = { workspace = true } -insta = { version = "1.40.0", features = ["filters"] } +insta = { workspace = true } regex = { workspace = true } tempfile = { workspace = true } diff --git a/crates/uv-build-frontend/Cargo.toml b/crates/uv-build-frontend/Cargo.toml index 748e7bb28..d06240090 100644 --- a/crates/uv-build-frontend/Cargo.toml +++ b/crates/uv-build-frontend/Cargo.toml @@ -48,4 +48,4 @@ tracing = { workspace = true } rustc-hash = { workspace = true } [dev-dependencies] -insta = { version = "1.40.0" } +insta = { workspace = true } diff --git a/crates/uv-cli/Cargo.toml b/crates/uv-cli/Cargo.toml index fa8453662..f0536951f 100644 --- a/crates/uv-cli/Cargo.toml +++ b/crates/uv-cli/Cargo.toml @@ -42,7 +42,7 @@ serde = { workspace = true } url = { workspace = true } [dev-dependencies] -insta = { version = "1.40.0", features = ["filters", "json"] } +insta = { workspace = true } [features] default = [] diff --git a/crates/uv-client/Cargo.toml b/crates/uv-client/Cargo.toml index bc7fc611f..cf1c603df 100644 --- a/crates/uv-client/Cargo.toml +++ b/crates/uv-client/Cargo.toml @@ -60,9 +60,9 @@ url = { workspace = true } [dev-dependencies] anyhow = { workspace = true } -http-body-util = { version = "0.1.2" } -hyper = { version = "1.4.1", features = ["server", "http1"] } -hyper-util = { version = "0.1.8", features = ["tokio"] } -insta = { version = "1.40.0", features = ["filters", "json", "redactions"] } +http-body-util = { workspace = true } +hyper = { workspace = true } +hyper-util = { workspace = true } +insta = { workspace = true } tokio = { workspace = true } wiremock = { workspace = true } diff --git a/crates/uv-dirs/Cargo.toml b/crates/uv-dirs/Cargo.toml index e28d96aa9..ab2b9b73f 100644 --- a/crates/uv-dirs/Cargo.toml +++ b/crates/uv-dirs/Cargo.toml @@ -24,5 +24,5 @@ fs-err = { workspace = true } tracing = { workspace = true } [dev-dependencies] -assert_fs = { version = "1.1.2" } +assert_fs = { workspace = true } indoc = { workspace = true } diff --git a/crates/uv-distribution-filename/Cargo.toml b/crates/uv-distribution-filename/Cargo.toml index 0dfdd623e..b0aab4790 100644 --- a/crates/uv-distribution-filename/Cargo.toml +++ b/crates/uv-distribution-filename/Cargo.toml @@ -29,4 +29,4 @@ smallvec = { workspace = true } thiserror = { workspace = true } [dev-dependencies] -insta = { version = "1.40.0" } +insta = { workspace = true } diff --git a/crates/uv-distribution/Cargo.toml b/crates/uv-distribution/Cargo.toml index 2c490590b..fd5a06471 100644 --- a/crates/uv-distribution/Cargo.toml +++ b/crates/uv-distribution/Cargo.toml @@ -60,7 +60,7 @@ zip = { workspace = true } [dev-dependencies] indoc = { workspace = true } -insta = { version = "1.40.0", features = ["filters", "json", "redactions"] } +insta = { workspace = true } [features] default = [] diff --git a/crates/uv-globfilter/Cargo.toml b/crates/uv-globfilter/Cargo.toml index ca45a92f6..cf6b93bde 100644 --- a/crates/uv-globfilter/Cargo.toml +++ b/crates/uv-globfilter/Cargo.toml @@ -22,7 +22,7 @@ walkdir = { workspace = true } [dev-dependencies] anstream = { workspace = true } fs-err = { workspace = true } -insta = "1.41.1" +insta = { workspace = true } tempfile = { workspace = true } [lints] diff --git a/crates/uv-install-wheel/Cargo.toml b/crates/uv-install-wheel/Cargo.toml index dd9162e68..d2d46ac40 100644 --- a/crates/uv-install-wheel/Cargo.toml +++ b/crates/uv-install-wheel/Cargo.toml @@ -56,5 +56,5 @@ self-replace = { workspace = true } [dev-dependencies] anyhow = { workspace = true } -assert_fs = { version = "1.1.2" } +assert_fs = { workspace = true } indoc = { workspace = true } diff --git a/crates/uv-pep508/Cargo.toml b/crates/uv-pep508/Cargo.toml index e9306da00..cb23cfc04 100644 --- a/crates/uv-pep508/Cargo.toml +++ b/crates/uv-pep508/Cargo.toml @@ -40,9 +40,9 @@ url = { workspace = true, features = ["serde"] } version-ranges = { workspace = true } [dev-dependencies] -insta = { version = "1.40.0" } +insta = { workspace = true } serde_json = { workspace = true } -tracing-test = { version = "0.2.5" } +tracing-test = { workspace = true } [features] tracing = ["dep:tracing", "uv-pep440/tracing"] diff --git a/crates/uv-platform-tags/Cargo.toml b/crates/uv-platform-tags/Cargo.toml index dcab932ad..cbddbbe68 100644 --- a/crates/uv-platform-tags/Cargo.toml +++ b/crates/uv-platform-tags/Cargo.toml @@ -25,4 +25,4 @@ serde = { workspace = true } thiserror = { workspace = true } [dev-dependencies] -insta = { version = "1.40.0" } +insta = { workspace = true } diff --git a/crates/uv-publish/Cargo.toml b/crates/uv-publish/Cargo.toml index c3dfeef39..bdc05a831 100644 --- a/crates/uv-publish/Cargo.toml +++ b/crates/uv-publish/Cargo.toml @@ -47,7 +47,7 @@ tracing = { workspace = true } url = { workspace = true } [dev-dependencies] -insta = { version = "1.36.1", features = ["json", "filters"] } +insta = { workspace = true } [lints] workspace = true diff --git a/crates/uv-pypi-types/Cargo.toml b/crates/uv-pypi-types/Cargo.toml index 0a94cc9ad..31a532d6e 100644 --- a/crates/uv-pypi-types/Cargo.toml +++ b/crates/uv-pypi-types/Cargo.toml @@ -43,7 +43,7 @@ url = { workspace = true } [dev-dependencies] anyhow = { workspace = true } -insta = { version = "1.40.0" } +insta = { workspace = true } [features] schemars = ["dep:schemars", "uv-normalize/schemars"] diff --git a/crates/uv-python/Cargo.toml b/crates/uv-python/Cargo.toml index d008b2d4e..53c70ba5f 100644 --- a/crates/uv-python/Cargo.toml +++ b/crates/uv-python/Cargo.toml @@ -78,13 +78,13 @@ windows-sys = { workspace = true } [dev-dependencies] anyhow = { workspace = true } -assert_fs = { version = "1.1.2" } +assert_fs = { workspace = true } indoc = { workspace = true } -insta = { version = "1.40.0" } -itertools = { version = "0.14.0" } -temp-env = { version = "0.3.6" } +insta = { workspace = true } +itertools = { workspace = true } +temp-env = { workspace = true } tempfile = { workspace = true } -test-log = { version = "0.2.16", features = ["trace"], default-features = false } +test-log = { workspace = true } [build-dependencies] serde_json = { workspace = true } diff --git a/crates/uv-requirements-txt/Cargo.toml b/crates/uv-requirements-txt/Cargo.toml index f82aa2c1c..478a90e43 100644 --- a/crates/uv-requirements-txt/Cargo.toml +++ b/crates/uv-requirements-txt/Cargo.toml @@ -40,11 +40,11 @@ http = ["reqwest", "reqwest-middleware"] [dev-dependencies] anyhow = { workspace = true } -assert_fs = { version = "1.1.2" } +assert_fs = { workspace = true } indoc = { workspace = true } -insta = { version = "1.40.0", features = ["filters"] } -itertools = { version = "0.14.0" } +insta = { workspace = true } +itertools = { workspace = true } regex = { workspace = true } tempfile = { workspace = true } -test-case = { version = "3.3.1" } -tokio = { version = "1.40.0" } +test-case = { workspace = true } +tokio = { workspace = true } diff --git a/crates/uv-resolver/Cargo.toml b/crates/uv-resolver/Cargo.toml index 715dacab8..b4feee290 100644 --- a/crates/uv-resolver/Cargo.toml +++ b/crates/uv-resolver/Cargo.toml @@ -70,7 +70,7 @@ tracing = { workspace = true } url = { workspace = true } [dev-dependencies] -insta = { version = "1.40.0" } +insta = { workspace = true } toml = { workspace = true } [features] diff --git a/crates/uv-trampoline-builder/Cargo.toml b/crates/uv-trampoline-builder/Cargo.toml index d5d5436fe..be38ddd82 100644 --- a/crates/uv-trampoline-builder/Cargo.toml +++ b/crates/uv-trampoline-builder/Cargo.toml @@ -29,8 +29,8 @@ thiserror = { workspace = true } zip = { workspace = true } [dev-dependencies] -assert_cmd = { version = "2.0.16" } -assert_fs = { version = "1.1.2" } +assert_cmd = { workspace = true } +assert_fs = { workspace = true } anyhow = { workspace = true } fs-err = { workspace = true } which = { workspace = true } diff --git a/crates/uv-workspace/Cargo.toml b/crates/uv-workspace/Cargo.toml index 36059f10f..a00662f14 100644 --- a/crates/uv-workspace/Cargo.toml +++ b/crates/uv-workspace/Cargo.toml @@ -48,8 +48,8 @@ tracing = { workspace = true } [dev-dependencies] anyhow = { workspace = true } -assert_fs = { version = "1.1.2" } -insta = { version = "1.40.0", features = ["filters", "json", "redactions"] } +assert_fs = { workspace = true } +insta = { workspace = true } regex = { workspace = true } tempfile = { workspace = true } diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml index ec8cf18bd..5acbb7f20 100644 --- a/crates/uv/Cargo.toml +++ b/crates/uv/Cargo.toml @@ -112,22 +112,22 @@ windows = { workspace = true } windows-result = { workspace = true } [dev-dependencies] -assert_cmd = { version = "2.0.16" } -assert_fs = { version = "1.1.2" } +assert_cmd = { workspace = true } +assert_fs = { workspace = true } base64 = { workspace = true } -byteorder = { version = "1.5.0" } -filetime = { version = "0.2.25" } +byteorder = { workspace = true } +filetime = { workspace = true } flate2 = { workspace = true, default-features = false } -ignore = { version = "0.4.23" } +ignore = { workspace = true } indoc = { workspace = true } -insta = { version = "1.40.0", features = ["filters", "json"] } -predicates = { version = "3.1.2" } +insta = { workspace = true } +predicates = { workspace = true } regex = { workspace = true } reqwest = { workspace = true, features = ["blocking"], default-features = false } -similar = { version = "2.6.0" } +similar = { workspace = true } tar = { workspace = true } tempfile = { workspace = true } -whoami = { version = "1.6.0" } +whoami = { workspace = true } wiremock = { workspace = true } zip = { workspace = true } From c489fcb633996c5b7afe6779b352c08c727cd946 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Fri, 25 Jul 2025 15:19:07 -0500 Subject: [PATCH 347/349] Update validation for `enviroments` and `required-environments` in `uv.toml` (#14905) See https://github.com/astral-sh/uv/pull/14322/files#r2231891679 Closes https://github.com/astral-sh/uv/issues/14904 --- crates/uv-settings/src/lib.rs | 27 +++++++++++++++++---------- crates/uv/tests/it/pip_compile.rs | 2 -- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/crates/uv-settings/src/lib.rs b/crates/uv-settings/src/lib.rs index 64f160aa3..ed226b549 100644 --- a/crates/uv-settings/src/lib.rs +++ b/crates/uv-settings/src/lib.rs @@ -211,8 +211,8 @@ fn validate_uv_toml(path: &Path, options: &Options) -> Result<(), Error> { override_dependencies: _, constraint_dependencies: _, build_constraint_dependencies: _, - environments: _, - required_environments: _, + environments, + required_environments, conflicts, workspace, sources, @@ -265,6 +265,19 @@ fn validate_uv_toml(path: &Path, options: &Options) -> Result<(), Error> { "build-backend", )); } + if environments.is_some() { + return Err(Error::PyprojectOnlyField( + path.to_path_buf(), + "environments", + )); + } + + if required_environments.is_some() { + return Err(Error::PyprojectOnlyField( + path.to_path_buf(), + "required-environments", + )); + } Ok(()) } @@ -336,8 +349,8 @@ fn warn_uv_toml_masked_fields(options: &Options) { override_dependencies, constraint_dependencies, build_constraint_dependencies, - environments, - required_environments, + environments: _, + required_environments: _, conflicts: _, workspace: _, sources: _, @@ -504,12 +517,6 @@ fn warn_uv_toml_masked_fields(options: &Options) { if build_constraint_dependencies.is_some() { masked_fields.push("build-constraint-dependencies"); } - if environments.is_some() { - masked_fields.push("environments"); - } - if required_environments.is_some() { - masked_fields.push("required-environments"); - } if !masked_fields.is_empty() { let field_listing = masked_fields.join("\n- "); warn_user!( diff --git a/crates/uv/tests/it/pip_compile.rs b/crates/uv/tests/it/pip_compile.rs index 8874949c2..34af0ed08 100644 --- a/crates/uv/tests/it/pip_compile.rs +++ b/crates/uv/tests/it/pip_compile.rs @@ -14266,8 +14266,6 @@ matplotlib fn importlib_metadata_not_repeated() -> Result<()> { let context = TestContext::new("3.12"); - let uv_toml = context.temp_dir.child("uv.toml"); - uv_toml.write_str(r#"environments = ["python_version >= '3.10'", "python_version >= '3.8' and python_version < '3.10'", "python_version < '3.8'"]"#)?; let requirements_in = context.temp_dir.child("requirements.in"); requirements_in.write_str("build")?; From 396e1980817b8f3e3590b52b3f6a32dbd08a022f Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Fri, 25 Jul 2025 15:19:24 -0500 Subject: [PATCH 348/349] Update documentation for preview flags (#14902) Follows #14823 --- crates/uv-cli/src/lib.rs | 6 ++- crates/uv/tests/it/help.rs | 6 +-- docs/concepts/preview.md | 74 ++++++++++++++++++++++++++++++++ docs/concepts/python-versions.md | 6 +-- docs/reference/cli.md | 6 ++- mkdocs.template.yml | 2 + 6 files changed, 89 insertions(+), 11 deletions(-) create mode 100644 docs/concepts/preview.md diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 1b41050b2..7cd743bc6 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -4872,8 +4872,10 @@ pub enum PythonCommand { /// See `uv help python` to view supported request formats. Install(PythonInstallArgs), - /// Upgrade installed Python versions to the latest supported patch release (requires the - /// `--preview` flag). + /// Upgrade installed Python versions. + /// + /// Upgrades versions to the latest supported patch release. Requires the `python-upgrade` + /// preview feature. /// /// A target Python minor version to upgrade may be provided, e.g., `3.13`. Multiple versions /// may be provided to perform more than one upgrade. diff --git a/crates/uv/tests/it/help.rs b/crates/uv/tests/it/help.rs index d4f46b0cb..54c10e972 100644 --- a/crates/uv/tests/it/help.rs +++ b/crates/uv/tests/it/help.rs @@ -292,8 +292,7 @@ fn help_subcommand() { Commands: list List the available Python installations install Download and install Python versions - upgrade Upgrade installed Python versions to the latest supported patch release (requires - the `--preview` flag) + upgrade Upgrade installed Python versions find Search for a Python installation pin Pin to a specific Python version dir Show the uv Python installation directory @@ -731,8 +730,7 @@ fn help_flag_subcommand() { Commands: list List the available Python installations install Download and install Python versions - upgrade Upgrade installed Python versions to the latest supported patch release (requires - the `--preview` flag) + upgrade Upgrade installed Python versions find Search for a Python installation pin Pin to a specific Python version dir Show the uv Python installation directory diff --git a/docs/concepts/preview.md b/docs/concepts/preview.md new file mode 100644 index 000000000..c84eda380 --- /dev/null +++ b/docs/concepts/preview.md @@ -0,0 +1,74 @@ +# Preview features + +uv includes opt-in preview features to provide an opportunity for community feedback and increase +confidence that changes are a net-benefit before enabling them for everyone. + +## Enabling preview features + +To enable all preview features, use the `--preview` flag: + +```console +$ uv run --preview ... +``` + +Or, set the `UV_PREVIEW` environment variable: + +```console +$ UV_PREVIEW=1 uv run ... +``` + +To enable specific preview features, use the `--preview-features` flag: + +```console +$ uv run --preview-features foo ... +``` + +The `--preview-features` flag can be repeated to enable multiple features: + +```console +$ uv run --preview-features foo --preview-features bar ... +``` + +Or, features can be provided in a comma separated list: + +```console +$ uv run --preview-features foo,bar ... +``` + +The `UV_PREVIEW_FEATURES` environment variable can be used similarly, e.g.: + +```console +$ UV_PREVIEW_FEATURES=foo,bar uv run ... +``` + +For backwards compatibility, enabling preview features that do not exist will warn, but not error. + +## Using preview features + +Often, preview features can be used without changing any preview settings if the behavior change is +gated by some sort of user interaction, For example, while `pylock.toml` support is in preview, you +can use `uv pip install` with a `pylock.toml` file without additional configuration because +specifying the `pylock.toml` file indicates you want to use the feature. However, a warning will be +displayed that the feature is in preview. The preview feature can be enabled to silence the warning. + +Other preview features change behavior without changes to your use of uv. For example, when the +`python-upgrade` feature is enabled, the default behavior of `uv python install` changes to allow uv +to upgrade Python versions transparently. This feature requires enabling the preview flag for proper +usage. + +## Available preview features + +The following preview features are available: + +- `add-bounds`: Allows configuring the + [default bounds for `uv add`](../reference/settings.md#add-bounds) invocations. +- `json-output`: Allows `--output-format json` for various uv commands. +- `pylock`: Allows installing from `pylock.toml` files. +- `python-install-default`: Allows + [installing `python` and `python3` executables](./python-versions.md#installing-python-executables). +- `python-upgrade`: Allows + [transparent Python version upgrades](./python-versions.md#upgrading-python-versions). + +## Disabling preview features + +The `--no-preview` option can be used to disable preview features. diff --git a/docs/concepts/python-versions.md b/docs/concepts/python-versions.md index 0c16218d4..3632869db 100644 --- a/docs/concepts/python-versions.md +++ b/docs/concepts/python-versions.md @@ -180,9 +180,9 @@ $ uv python upgrade After an upgrade, uv will prefer the new version, but will retain the existing version as it may still be used by virtual environments. -If the Python version was installed with preview enabled, e.g., `uv python install 3.12 --preview`, -virtual environments using the Python version will be automatically upgraded to the new patch -version. +If the Python version was installed with the `python-upgrade` [preview feature](./preview.md) +enabled, e.g., `uv python install 3.12 --preview-features python-upgrade`, virtual environments +using the Python version will be automatically upgraded to the new patch version. !!! note diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 9b27ea5cb..43f7a2aa7 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -2641,7 +2641,7 @@ uv python [OPTIONS]
      uv python list

      List the available Python installations

      uv python install

      Download and install Python versions

      -
      uv python upgrade

      Upgrade installed Python versions to the latest supported patch release (requires the --preview flag)

      +
      uv python upgrade

      Upgrade installed Python versions

      uv python find

      Search for a Python installation

      uv python pin

      Pin to a specific Python version

      uv python dir

      Show the uv Python installation directory

      @@ -2843,7 +2843,9 @@ uv python install [OPTIONS] [TARGETS]... ### uv python upgrade -Upgrade installed Python versions to the latest supported patch release (requires the `--preview` flag). +Upgrade installed Python versions. + +Upgrades versions to the latest supported patch release. Requires the `python-upgrade` preview feature. A target Python minor version to upgrade may be provided, e.g., `3.13`. Multiple versions may be provided to perform more than one upgrade. diff --git a/mkdocs.template.yml b/mkdocs.template.yml index 69a299b5b..e5b214ecf 100644 --- a/mkdocs.template.yml +++ b/mkdocs.template.yml @@ -131,6 +131,7 @@ plugins: - concepts/build-backend.md - concepts/authentication.md - concepts/cache.md + - concepts/preview.md The pip interface: - pip/environments.md - pip/packages.md @@ -210,6 +211,7 @@ nav: - Build backend: concepts/build-backend.md - Authentication: concepts/authentication.md - Caching: concepts/cache.md + - Preview features: concepts/preview.md # Note: The `pip` section was moved to the `concepts/` section but the # top-level directory structure was retained to ease the transition. - The pip interface: From 7b8dd5cfafb47606c8fee22032d9df206c000d22 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Fri, 25 Jul 2025 15:19:38 -0500 Subject: [PATCH 349/349] Run `cargo update` (#14899) --- .github/workflows/ci.yml | 7 + Cargo.lock | 966 ++++++++++++------------- crates/uv-installer/src/installer.rs | 22 +- crates/uv-resolver/src/resolver/mod.rs | 14 +- 4 files changed, 483 insertions(+), 526 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0e4afd098..a93e00038 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1969,6 +1969,10 @@ jobs: - name: "Print Python path" run: echo $(which python3) + # Needed for building Pydantic + - name: "Install build tools" + run: dnf install -y gcc + - name: "Validate global Python install" run: python3 scripts/check_system_python.py --uv ./uv @@ -2465,6 +2469,9 @@ jobs: - name: "Print Python path" run: echo $(which python3) + - name: Install build tools + run: yum install -y gcc + - name: "Validate global Python install" run: python3 scripts/check_system_python.py --uv ./uv diff --git a/Cargo.lock b/Cargo.lock index 22213c3f4..a345b9ab5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aho-corasick" @@ -55,36 +55,36 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.7" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" dependencies = [ "anstyle", - "once_cell", + "once_cell_polyfill", "windows-sys 0.59.0", ] @@ -201,11 +201,11 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.18" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df895a515f70646414f4b45c0b79082783b80552b373a68283012928df56f522" +checksum = "06575e6a9673580f52661c92107baabffbf41e2141373441cbcdc47cb733003c" dependencies = [ - "bzip2 0.4.4", + "bzip2", "flate2", "futures-core", "futures-io", @@ -238,7 +238,7 @@ dependencies = [ "futures", "http-content-range", "itertools 0.13.0", - "memmap2 0.9.5", + "memmap2 0.9.7", "reqwest", "reqwest-middleware", "thiserror 1.0.69", @@ -270,15 +270,15 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "axoasset" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ba1098cfaa17f0973d2b766ee07bedb3e81a29b35c8d8b26de5074e37011443" +checksum = "56b3b6c5d71b918c0f42f43f69b303d7529b4233a598d9d61759d75f0f2a44a2" dependencies = [ "camino", "image", @@ -295,31 +295,31 @@ dependencies = [ [[package]] name = "axoprocess" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4de46920588aef95658797996130bacd542436aee090084646521260a74bda7d" +checksum = "8a4b4798a6c02e91378537c63cd6e91726900b595450daa5d487bc3c11e95e1b" dependencies = [ "miette", - "thiserror 1.0.69", + "thiserror 2.0.12", "tracing", ] [[package]] name = "axotag" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d888fac0b73e64cbdf36a743fc5a25af5ae955c357535cb420b389bf1e1a6c54" +checksum = "dc923121fbc4cc72e9008436b5650b98e56f94b5799df59a1b4f572b5c6a7e6b" dependencies = [ "miette", "semver", - "thiserror 1.0.69", + "thiserror 2.0.12", ] [[package]] name = "axoupdater" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc194af960a8ddbc4f28be3fa14f8716aa22141fe40bf1762ae0948defadcce4" +checksum = "dc482a1926df098f4e3806b834f3fe73a1ab54b24ab0ac481f72de479af5e982" dependencies = [ "axoasset", "axoprocess", @@ -348,9 +348,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", @@ -426,9 +426,9 @@ checksum = "26c4925bc979b677330a8c7fe7a8c94af2dbb4a2d37b4a20a80d884400f46baa" [[package]] name = "bstr" -version = "1.11.3" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" dependencies = [ "memchr", "regex-automata 0.4.9", @@ -437,9 +437,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytecheck" @@ -466,9 +466,9 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.21.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" +checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" [[package]] name = "byteorder" @@ -484,19 +484,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" - -[[package]] -name = "bzip2" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" -dependencies = [ - "bzip2-sys", - "libc", -] +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "bzip2" @@ -519,9 +509,9 @@ dependencies = [ [[package]] name = "camino" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab" dependencies = [ "serde", ] @@ -557,9 +547,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.11" +version = "1.2.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4730490333d58093109dc02c23174c3f4d490998c3fed3cc8e82d57afedb9cf" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" dependencies = [ "jobserver", "libc", @@ -568,9 +558,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "cfg_aliases" @@ -640,9 +630,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.44" +version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375f9d8255adeeedd51053574fd8d4ba875ea5fa558e86617b07f09f1680c8b6" +checksum = "a5abde44486daf70c5be8b8f8f1b66c49f86236edf6fa2abadb4d961c4c6229a" dependencies = [ "clap", ] @@ -660,9 +650,9 @@ dependencies = [ [[package]] name = "clap_complete_nushell" -version = "4.5.5" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6a8b1593457dfc2fe539002b795710d022dc62a65bf15023f039f9760c7b18a" +checksum = "0a0c951694691e65bf9d421d597d68416c22de9632e884c28412cb8cd8b73dce" dependencies = [ "clap", "clap_complete", @@ -682,9 +672,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "codspeed" @@ -748,9 +738,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "colored" @@ -759,7 +749,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -804,9 +794,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ "core-foundation-sys", "libc", @@ -844,9 +834,9 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] @@ -910,9 +900,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-common" @@ -938,9 +928,9 @@ dependencies = [ [[package]] name = "csv-core" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d" dependencies = [ "memchr", ] @@ -1085,9 +1075,9 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "dyn-clone" -version = "1.0.17" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" +checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" [[package]] name = "either" @@ -1127,15 +1117,15 @@ checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "erased-serde" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24e2389d65ab4fab27dc2a5de7b191e1f6617d1f1c8855c0dc569c94a4cbb18d" +checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7" dependencies = [ "serde", "typeid", @@ -1143,12 +1133,12 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.10" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1256,9 +1246,9 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "fontconfig-parser" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fcfcd44ca6e90c921fee9fa665d530b21ef1327a4c1a6c5250ea44b776ada7" +checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646" dependencies = [ "roxmltree 0.20.0", ] @@ -1284,15 +1274,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fs-err" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" -dependencies = [ - "autocfg", -] - [[package]] name = "fs-err" version = "3.1.1" @@ -1427,27 +1408,29 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", + "js-sys", "libc", - "wasi 0.13.3+wasi-0.2.2", - "windows-targets 0.52.6", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", ] [[package]] @@ -1521,9 +1504,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.7" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" +checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" dependencies = [ "atomic-waker", "bytes", @@ -1540,9 +1523,9 @@ dependencies = [ [[package]] name = "half" -version = "2.4.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ "cfg-if", "crunchy", @@ -1573,15 +1556,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.3.9" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "hermit-abi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hex" @@ -1600,14 +1577,14 @@ dependencies = [ [[package]] name = "homedir" -version = "0.3.4" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bdbbd5bc8c5749697ccaa352fa45aff8730cf21c68029c0eef1ffed7c3d6ba2" +checksum = "68df315d2857b2d8d2898be54a85e1d001bbbe0dbb5f8ef847b48dd3a23c4527" dependencies = [ "cfg-if", - "nix 0.29.0", + "nix 0.30.1", "widestring", - "windows 0.57.0", + "windows 0.61.3", ] [[package]] @@ -1655,15 +1632,15 @@ dependencies = [ [[package]] name = "http-content-range" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4aa8e0a9f1496d70bdd43b1e30ff373857c952609ad64b89f50569cfb8cbfca" +checksum = "63f67baaf67a9ae8fae78ecee69294d552b764dbcd6f8735d0a9c9be20ab0c82" [[package]] name = "httparse" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpdate" @@ -1694,11 +1671,10 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.5" +version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "futures-util", "http", "hyper", "hyper-util", @@ -1708,14 +1684,14 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots 0.26.8", + "webpki-roots", ] [[package]] name = "hyper-util" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" +checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" dependencies = [ "base64 0.22.1", "bytes", @@ -1729,7 +1705,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.6.0", "tokio", "tower-service", "tracing", @@ -1737,21 +1713,22 @@ dependencies = [ [[package]] name = "icu_collections" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", + "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locid" -version = "1.5.0" +name = "icu_locale_core" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", "litemap", @@ -1760,31 +1737,11 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - [[package]] name = "icu_normalizer" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ "displaydoc", "icu_collections", @@ -1792,67 +1749,54 @@ dependencies = [ "icu_properties", "icu_provider", "smallvec", - "utf16_iter", - "utf8_iter", - "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "1.5.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ "displaydoc", "icu_collections", - "icu_locid_transform", + "icu_locale_core", "icu_properties_data", "icu_provider", - "tinystr", + "potential_utf", + "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "1.5.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", - "icu_locid", - "icu_provider_macros", + "icu_locale_core", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", + "zerotrie", "zerovec", ] -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "idna" version = "1.0.3" @@ -1866,9 +1810,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", @@ -1892,9 +1836,9 @@ dependencies = [ [[package]] name = "image" -version = "0.25.5" +version = "0.25.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b" +checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" dependencies = [ "bytemuck", "byteorder-lite", @@ -1954,9 +1898,9 @@ dependencies = [ [[package]] name = "io-uring" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" +checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" dependencies = [ "bitflags 2.9.1", "cfg-if", @@ -1981,11 +1925,11 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.15" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" +checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ - "hermit-abi 0.4.0", + "hermit-abi", "libc", "windows-sys 0.59.0", ] @@ -2031,9 +1975,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" @@ -2078,18 +2022,19 @@ dependencies = [ [[package]] name = "jobserver" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ + "getrandom 0.3.3", "libc", ] [[package]] name = "jpeg-decoder" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" +checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07" [[package]] name = "js-sys" @@ -2137,15 +2082,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.171" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libmimalloc-sys" -version = "0.1.39" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23aa6811d3bd4deb8a84dde645f943476d13b248d818edcf8ce0b2f37f036b44" +checksum = "bf88cd67e9de251c1781dbe2f641a1a3ad66eaae831b8a2c38fbdc5ddae16d4d" dependencies = [ "cc", "libc", @@ -2153,9 +2098,9 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "4488594b9328dee448adb906d8b126d9b7deb7cf5c22161ee591610bb1be83c0" dependencies = [ "bitflags 2.9.1", "libc", @@ -2179,38 +2124,38 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litemap" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", ] -[[package]] -name = "lockfree-object-pool" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" - [[package]] name = "log" version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "lzma-rs" version = "0.3.0" @@ -2288,9 +2233,9 @@ dependencies = [ [[package]] name = "memmap2" -version = "0.9.5" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +checksum = "483758ad303d734cec05e5c12b41d7e93e6a6390c5e9dae6bdeb7c1259012d28" dependencies = [ "libc", ] @@ -2325,9 +2270,9 @@ dependencies = [ [[package]] name = "mimalloc" -version = "0.1.43" +version = "0.1.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68914350ae34959d83f732418d51e2427a794055d0b9529f48259ac07af65633" +checksum = "b1791cbe101e95af5764f06f20f6760521f7158f69dbf9d6baf941ee1bf6bc40" dependencies = [ "libmimalloc-sys", ] @@ -2350,9 +2295,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.5" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", "simd-adler32", @@ -2360,13 +2305,13 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", ] [[package]] @@ -2380,18 +2325,18 @@ dependencies = [ [[package]] name = "munge" -version = "0.4.1" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64142d38c84badf60abf06ff9bd80ad2174306a5b11bd4706535090a30a419df" +checksum = "9cce144fab80fbb74ec5b89d1ca9d41ddf6b644ab7e986f7d3ed0aab31625cb1" dependencies = [ "munge_macro", ] [[package]] name = "munge_macro" -version = "0.4.1" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bb5c1d8184f13f7d0ccbeeca0def2f9a181bce2624302793005f5ca8aa62e5e" +checksum = "574af9cd5b9971cbfdf535d6a8d533778481b241c447826d976101e0149392a1" dependencies = [ "proc-macro2", "quote", @@ -2404,7 +2349,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" dependencies = [ - "rand", + "rand 0.8.5", ] [[package]] @@ -2467,11 +2412,11 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" dependencies = [ - "hermit-abi 0.3.9", + "hermit-abi", "libc", ] @@ -2491,10 +2436,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] -name = "oorandom" -version = "11.1.4" +name = "once_cell_polyfill" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "openssl-probe" @@ -2537,9 +2488,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -2547,9 +2498,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", @@ -2646,18 +2597,18 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" [[package]] name = "pin-project" -version = "1.1.8" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.8" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", @@ -2678,9 +2629,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "plain" @@ -2712,9 +2663,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.10.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "portable-atomic-util" @@ -2726,10 +2677,19 @@ dependencies = [ ] [[package]] -name = "ppv-lite86" -version = "0.2.20" +name = "potential_utf" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] @@ -2776,9 +2736,9 @@ dependencies = [ [[package]] name = "priority-queue" -version = "2.3.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef08705fa1589a1a59aa924ad77d14722cb0cd97b67dd5004ed5f4a4873fce8d" +checksum = "5676d703dda103cbb035b653a9f11448c0a7216c7926bd35fcb5865475d0c970" dependencies = [ "autocfg", "equivalent", @@ -2852,31 +2812,34 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.6" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" +checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" dependencies = [ "bytes", + "cfg_aliases", "pin-project-lite", "quinn-proto", "quinn-udp", "rustc-hash", "rustls", - "socket2", + "socket2 0.5.10", "thiserror 2.0.12", "tokio", "tracing", + "web-time", ] [[package]] name = "quinn-proto" -version = "0.11.9" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" +checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" dependencies = [ "bytes", - "getrandom 0.2.15", - "rand", + "getrandom 0.3.3", + "lru-slab", + "rand 0.9.2", "ring", "rustc-hash", "rustls", @@ -2890,14 +2853,14 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.9" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c40286217b4ba3a71d644d752e6a0b71f13f1b6a2c5311acfcbe0c2418ed904" +checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2", + "socket2 0.5.10", "tracing", "windows-sys 0.59.0", ] @@ -2917,6 +2880,12 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73" +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rancor" version = "0.1.0" @@ -2933,8 +2902,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", ] [[package]] @@ -2944,7 +2923,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -2953,7 +2942,16 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", ] [[package]] @@ -2984,9 +2982,9 @@ checksum = "3b42e27ef78c35d3998403c1d26f3efd9e135d3e5121b0a4845cc5cc27547f4f" [[package]] name = "redox_syscall" -version = "0.5.8" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +checksum = "7e8af0dde094006011e6a740d4879319439489813bd0bcdc7d821beaeeff48ec" dependencies = [ "bitflags 2.9.1", ] @@ -2997,7 +2995,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", "libredox", "thiserror 2.0.12", ] @@ -3031,7 +3029,7 @@ dependencies = [ "cfg-if", "libc", "rustix 1.0.8", - "windows 0.61.1", + "windows 0.61.3", ] [[package]] @@ -3130,7 +3128,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 1.0.1", + "webpki-roots", ] [[package]] @@ -3155,7 +3153,7 @@ dependencies = [ "anyhow", "async-trait", "futures", - "getrandom 0.2.15", + "getrandom 0.2.16", "http", "hyper", "reqwest", @@ -3192,14 +3190,14 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5875471e6cab2871bc150ecb8c727db5113c9338cc3354dc5ee3425b6aa40a1c" dependencies = [ - "rand", + "rand 0.8.5", ] [[package]] name = "rgb" -version = "0.8.50" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" +checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" dependencies = [ "bytemuck", ] @@ -3212,7 +3210,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.15", + "getrandom 0.2.16", "libc", "untrusted", "windows-sys 0.52.0", @@ -3311,9 +3309,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" [[package]] name = "rustc-hash" @@ -3343,15 +3341,15 @@ dependencies = [ "bitflags 2.9.1", "errno", "libc", - "linux-raw-sys 0.9.2", + "linux-raw-sys 0.9.4", "windows-sys 0.60.2", ] [[package]] name = "rustls" -version = "0.23.22" +version = "0.23.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb9263ab4eb695e42321db096e3b8fbd715a59b154d5c88d82db2175b681ba7" +checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" dependencies = [ "once_cell", "ring", @@ -3375,18 +3373,19 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ "web-time", + "zeroize", ] [[package]] name = "rustls-webpki" -version = "0.102.8" +version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ "ring", "rustls-pki-types", @@ -3395,9 +3394,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.19" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "rustybuzz" @@ -3417,9 +3416,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" @@ -3533,9 +3532,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" [[package]] name = "serde" @@ -3670,9 +3669,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.2" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" dependencies = [ "libc", ] @@ -3712,12 +3711,9 @@ checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "slab" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" [[package]] name = "smallvec" @@ -3733,14 +3729,24 @@ checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" [[package]] name = "socket2" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", ] +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "spdx" version = "0.10.9" @@ -3810,9 +3816,9 @@ checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" [[package]] name = "svg" -version = "0.17.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "700efb40f3f559c23c18b446e8ed62b08b56b2bb3197b36d57e0470b4102779e" +checksum = "94afda9cd163c04f6bee8b4bf2501c91548deae308373c436f36aeff3cf3c4a3" [[package]] name = "svgfilters" @@ -3866,9 +3872,9 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", @@ -3924,7 +3930,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ "fastrand", - "getrandom 0.3.1", + "getrandom 0.3.3", "once_cell", "rustix 1.0.8", "windows-sys 0.59.0", @@ -3932,11 +3938,11 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" +checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" dependencies = [ - "rustix 0.38.44", + "rustix 1.0.8", "windows-sys 0.59.0", ] @@ -4053,12 +4059,11 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", - "once_cell", ] [[package]] @@ -4108,9 +4113,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.7.6" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", @@ -4128,9 +4133,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" dependencies = [ "tinyvec_macros", ] @@ -4161,7 +4166,7 @@ dependencies = [ "pin-project-lite", "signal-hook-registry", "slab", - "socket2", + "socket2 0.5.10", "tokio-macros", "windows-sys 0.52.0", ] @@ -4179,9 +4184,9 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ "rustls", "tokio", @@ -4326,9 +4331,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.28" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", @@ -4337,9 +4342,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", @@ -4347,13 +4352,13 @@ dependencies = [ [[package]] name = "tracing-durations-export" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "382e025ef8e0db646343dd2cf56af9d7fe6f5eabce5f388f8e5ec7234f555a0f" +checksum = "32e0c2cfee378f62291f2703bbb949b99213306c2729fe977799653c3c3404b5" dependencies = [ "anyhow", - "fs-err 2.11.0", - "itertools 0.13.0", + "fs-err", + "itertools 0.14.0", "once_cell", "rustc-hash", "serde", @@ -4452,15 +4457,15 @@ checksum = "0609f771ad9c6155384897e1df4d948e692667cc0588548b68eb44d052b27633" [[package]] name = "typeid" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e13db2e0ccd5e14a544e8a246ba2312cd25223f616442d7f2cb0e3db614236e" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" [[package]] name = "typenum" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "ucd-trie" @@ -4506,9 +4511,9 @@ checksum = "10103c57044730945224467c09f71a4db0071c123a0648cc3e818913bde6b561" [[package]] name = "unicode-ident" -version = "1.0.16" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-linebreak" @@ -4609,12 +4614,6 @@ dependencies = [ "usvg", ] -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - [[package]] name = "utf8-width" version = "0.1.7" @@ -4635,11 +4634,13 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.12.1" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.3.3", + "js-sys", + "wasm-bindgen", ] [[package]] @@ -4661,7 +4662,7 @@ dependencies = [ "dunce", "filetime", "flate2", - "fs-err 3.1.1", + "fs-err", "futures", "http", "ignore", @@ -4746,7 +4747,7 @@ dependencies = [ "which", "whoami", "windows 0.59.0", - "windows-result 0.3.4", + "windows-result", "wiremock", "zip", ] @@ -4823,7 +4824,7 @@ version = "0.1.0" dependencies = [ "csv", "flate2", - "fs-err 3.1.1", + "fs-err", "globset", "indoc", "insta", @@ -4861,7 +4862,7 @@ name = "uv-build-frontend" version = "0.0.1" dependencies = [ "anstream", - "fs-err 3.1.1", + "fs-err", "indoc", "insta", "itertools 0.14.0", @@ -4896,7 +4897,7 @@ name = "uv-cache" version = "0.0.1" dependencies = [ "clap", - "fs-err 3.1.1", + "fs-err", "nanoid", "rmp-serde", "rustc-hash", @@ -4921,7 +4922,7 @@ name = "uv-cache-info" version = "0.0.1" dependencies = [ "anyhow", - "fs-err 3.1.1", + "fs-err", "globwalk", "schemars", "serde", @@ -4952,7 +4953,7 @@ dependencies = [ "anyhow", "clap", "clap_complete_command", - "fs-err 3.1.1", + "fs-err", "insta", "serde", "url", @@ -4983,7 +4984,7 @@ dependencies = [ "async_http_range_reader", "async_zip", "bytecheck", - "fs-err 3.1.1", + "fs-err", "futures", "html-escape", "http", @@ -5039,7 +5040,7 @@ dependencies = [ "bitflags 2.9.1", "clap", "either", - "fs-err 3.1.1", + "fs-err", "rayon", "rustc-hash", "same-file", @@ -5078,7 +5079,7 @@ dependencies = [ "anstream", "anyhow", "clap", - "fs-err 3.1.1", + "fs-err", "itertools 0.14.0", "markdown", "owo-colors", @@ -5122,7 +5123,7 @@ version = "0.0.1" dependencies = [ "assert_fs", "etcetera", - "fs-err 3.1.1", + "fs-err", "indoc", "tracing", "uv-static", @@ -5165,7 +5166,7 @@ version = "0.0.1" dependencies = [ "anyhow", "either", - "fs-err 3.1.1", + "fs-err", "futures", "indoc", "insta", @@ -5230,7 +5231,7 @@ version = "0.0.1" dependencies = [ "arcstr", "bitflags 2.9.1", - "fs-err 3.1.1", + "fs-err", "http", "itertools 0.14.0", "jiff", @@ -5271,7 +5272,7 @@ dependencies = [ "async-compression", "async_zip", "blake2", - "fs-err 3.1.1", + "fs-err", "futures", "md-5", "rayon", @@ -5297,7 +5298,7 @@ dependencies = [ "dunce", "either", "encoding_rs_io", - "fs-err 3.1.1", + "fs-err", "fs2", "junction", "path-slash", @@ -5320,7 +5321,7 @@ dependencies = [ "anyhow", "cargo-util", "dashmap", - "fs-err 3.1.1", + "fs-err", "reqwest", "reqwest-middleware", "thiserror 2.0.12", @@ -5353,7 +5354,7 @@ name = "uv-globfilter" version = "0.1.0" dependencies = [ "anstream", - "fs-err 3.1.1", + "fs-err", "globset", "insta", "owo-colors", @@ -5375,7 +5376,7 @@ dependencies = [ "configparser", "csv", "data-encoding", - "fs-err 3.1.1", + "fs-err", "indoc", "mailparse", "pathdiff", @@ -5409,7 +5410,7 @@ version = "0.0.1" dependencies = [ "anyhow", "async-channel", - "fs-err 3.1.1", + "fs-err", "futures", "rayon", "rustc-hash", @@ -5456,7 +5457,7 @@ name = "uv-metadata" version = "0.1.0" dependencies = [ "async_zip", - "fs-err 3.1.1", + "fs-err", "futures", "thiserror 2.0.12", "tokio", @@ -5562,7 +5563,7 @@ dependencies = [ "astral-tokio-tar", "async-compression", "base64 0.22.1", - "fs-err 3.1.1", + "fs-err", "futures", "glob", "insta", @@ -5633,7 +5634,7 @@ dependencies = [ "clap", "configparser", "dunce", - "fs-err 3.1.1", + "fs-err", "futures", "goblin", "indexmap", @@ -5685,7 +5686,7 @@ dependencies = [ "uv-warnings", "which", "windows-registry", - "windows-result 0.3.4", + "windows-result", "windows-sys 0.59.0", ] @@ -5706,7 +5707,7 @@ dependencies = [ "anyhow", "configparser", "console 0.16.0", - "fs-err 3.1.1", + "fs-err", "futures", "rustc-hash", "serde", @@ -5740,7 +5741,7 @@ version = "0.0.1" dependencies = [ "anyhow", "assert_fs", - "fs-err 3.1.1", + "fs-err", "indoc", "insta", "itertools 0.14.0", @@ -5828,7 +5829,7 @@ dependencies = [ name = "uv-scripts" version = "0.0.1" dependencies = [ - "fs-err 3.1.1", + "fs-err", "indoc", "memchr", "regex", @@ -5850,7 +5851,7 @@ name = "uv-settings" version = "0.0.1" dependencies = [ "clap", - "fs-err 3.1.1", + "fs-err", "schemars", "serde", "textwrap", @@ -5889,7 +5890,7 @@ dependencies = [ "uv-fs", "uv-static", "windows-registry", - "windows-result 0.3.4", + "windows-result", "windows-sys 0.59.0", ] @@ -5907,7 +5908,7 @@ dependencies = [ name = "uv-state" version = "0.0.1" dependencies = [ - "fs-err 3.1.1", + "fs-err", "tempfile", "uv-dirs", ] @@ -5923,7 +5924,7 @@ dependencies = [ name = "uv-tool" version = "0.0.1" dependencies = [ - "fs-err 3.1.1", + "fs-err", "pathdiff", "self-replace", "serde", @@ -5954,7 +5955,7 @@ version = "0.1.0" dependencies = [ "clap", "either", - "fs-err 3.1.1", + "fs-err", "schemars", "serde", "thiserror 2.0.12", @@ -5974,7 +5975,7 @@ dependencies = [ "anyhow", "assert_cmd", "assert_fs", - "fs-err 3.1.1", + "fs-err", "thiserror 2.0.12", "uv-fs", "which", @@ -6013,7 +6014,7 @@ name = "uv-virtualenv" version = "0.0.4" dependencies = [ "console 0.16.0", - "fs-err 3.1.1", + "fs-err", "itertools 0.14.0", "owo-colors", "pathdiff", @@ -6046,7 +6047,7 @@ dependencies = [ "anyhow", "assert_fs", "clap", - "fs-err 3.1.1", + "fs-err", "glob", "insta", "itertools 0.14.0", @@ -6100,9 +6101,9 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wait-timeout" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" dependencies = [ "libc", ] @@ -6128,15 +6129,15 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" -version = "0.13.3+wasi-0.2.2" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] @@ -6267,27 +6268,18 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.8" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "webpki-roots" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8782dd5a41a24eed3a4f40b606249b3e236ca61adf1f25ea4d45c73de122b502" +checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" dependencies = [ "rustls-pki-types", ] [[package]] name = "weezl" -version = "0.1.8" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" +checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" [[package]] name = "which" @@ -6314,9 +6306,9 @@ dependencies = [ [[package]] name = "widestring" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" +checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" [[package]] name = "winapi" @@ -6340,7 +6332,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -6349,16 +6341,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" -dependencies = [ - "windows-core 0.57.0", - "windows-targets 0.52.6", -] - [[package]] name = "windows" version = "0.59.0" @@ -6371,12 +6353,12 @@ dependencies = [ [[package]] name = "windows" -version = "0.61.1" +version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ "windows-collections", - "windows-core 0.61.0", + "windows-core 0.61.2", "windows-future", "windows-link", "windows-numerics", @@ -6388,19 +6370,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ - "windows-core 0.61.0", -] - -[[package]] -name = "windows-core" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" -dependencies = [ - "windows-implement 0.57.0", - "windows-interface 0.57.0", - "windows-result 0.1.2", - "windows-targets 0.52.6", + "windows-core 0.61.2", ] [[package]] @@ -6410,44 +6380,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "810ce18ed2112484b0d4e15d022e5f598113e220c53e373fb31e67e21670c1ce" dependencies = [ "windows-implement 0.59.0", - "windows-interface 0.59.1", - "windows-result 0.3.4", + "windows-interface", + "windows-result", "windows-strings 0.3.1", "windows-targets 0.53.2", ] [[package]] name = "windows-core" -version = "0.61.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement 0.60.0", - "windows-interface 0.59.1", + "windows-interface", "windows-link", - "windows-result 0.3.4", + "windows-result", "windows-strings 0.4.2", ] [[package]] name = "windows-future" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a1d6bbefcb7b60acd19828e1bc965da6fcf18a7e39490c5f8be71e54a19ba32" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ - "windows-core 0.61.0", + "windows-core 0.61.2", "windows-link", -] - -[[package]] -name = "windows-implement" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "windows-threading", ] [[package]] @@ -6472,17 +6432,6 @@ dependencies = [ "syn", ] -[[package]] -name = "windows-interface" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "windows-interface" version = "0.59.1" @@ -6506,7 +6455,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ - "windows-core 0.61.0", + "windows-core 0.61.2", "windows-link", ] @@ -6517,19 +6466,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" dependencies = [ "windows-link", - "windows-result 0.3.4", + "windows-result", "windows-strings 0.4.2", ] -[[package]] -name = "windows-result" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-result" version = "0.3.4" @@ -6640,6 +6580,15 @@ dependencies = [ "windows_x86_64_msvc 0.53.0", ] +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -6780,9 +6729,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" dependencies = [ "memchr", ] @@ -6819,34 +6768,27 @@ dependencies = [ [[package]] name = "wit-bindgen-rt" -version = "0.33.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags 2.9.1", ] -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - [[package]] name = "writeable" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "xattr" -version = "1.4.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e105d177a3871454f754b33bb0ee637ecaaac997446375fd3e5d43a2ed00c909" +checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909" dependencies = [ "libc", - "linux-raw-sys 0.4.15", - "rustix 0.38.44", + "rustix 1.0.8", ] [[package]] @@ -6872,9 +6814,9 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yoke" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ "serde", "stable_deref_trait", @@ -6884,9 +6826,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", @@ -6896,19 +6838,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ - "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", @@ -6917,18 +6858,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", @@ -6943,10 +6884,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" [[package]] -name = "zerovec" -version = "0.10.4" +name = "zerotrie" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" dependencies = [ "yoke", "zerofrom", @@ -6955,9 +6907,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.3" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", @@ -6966,12 +6918,12 @@ dependencies = [ [[package]] name = "zip" -version = "2.3.0" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84e9a772a54b54236b9b744aaaf8d7be01b4d6e99725523cb82cb32d1c81b1d7" +checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" dependencies = [ "arbitrary", - "bzip2 0.5.2", + "bzip2", "crc32fast", "crossbeam-utils", "displaydoc", @@ -6993,41 +6945,39 @@ checksum = "626bd9fa9734751fc50d6060752170984d7053f5a39061f524cda68023d4db8a" [[package]] name = "zopfli" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" +checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7" dependencies = [ "bumpalo", "crc32fast", - "lockfree-object-pool", "log", - "once_cell", "simd-adler32", ] [[package]] name = "zstd" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "7.2.1" +version = "7.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.13+zstd.1.5.6" +version = "2.0.15+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" dependencies = [ "cc", "pkg-config", diff --git a/crates/uv-installer/src/installer.rs b/crates/uv-installer/src/installer.rs index d81b8fa98..3903784ca 100644 --- a/crates/uv-installer/src/installer.rs +++ b/crates/uv-installer/src/installer.rs @@ -107,10 +107,10 @@ impl<'a> Installer<'a> { rayon::spawn(move || { let result = install( wheels, - layout, - installer_name, + &layout, + installer_name.as_ref(), link_mode, - reporter, + reporter.as_ref(), relocatable, installer_metadata, ); @@ -137,10 +137,10 @@ impl<'a> Installer<'a> { install( wheels, - self.venv.interpreter().layout(), - self.name, + &self.venv.interpreter().layout(), + self.name.as_ref(), self.link_mode, - self.reporter, + self.reporter.as_ref(), self.venv.relocatable(), self.metadata, ) @@ -151,10 +151,10 @@ impl<'a> Installer<'a> { #[instrument(skip_all, fields(num_wheels = %wheels.len()))] fn install( wheels: Vec, - layout: Layout, - installer_name: Option, + layout: &Layout, + installer_name: Option<&String>, link_mode: LinkMode, - reporter: Option>, + reporter: Option<&Arc>, relocatable: bool, installer_metadata: bool, ) -> Result> { @@ -163,7 +163,7 @@ fn install( let locks = uv_install_wheel::Locks::default(); wheels.par_iter().try_for_each(|wheel| { uv_install_wheel::install_wheel( - &layout, + layout, relocatable, wheel.path(), wheel.filename(), @@ -176,7 +176,7 @@ fn install( } else { Some(wheel.cache_info()) }, - installer_name.as_deref(), + installer_name.map(String::as_str), installer_metadata, link_mode, &locks, diff --git a/crates/uv-resolver/src/resolver/mod.rs b/crates/uv-resolver/src/resolver/mod.rs index 6af21702a..14eff5a9d 100644 --- a/crates/uv-resolver/src/resolver/mod.rs +++ b/crates/uv-resolver/src/resolver/mod.rs @@ -290,7 +290,7 @@ impl thread::Builder::new() .name("uv-resolver".into()) .spawn(move || { - let result = solver.solve(request_sink); + let result = solver.solve(&request_sink); // This may fail if the main thread returned early due to an error. let _ = tx.send(result); @@ -311,7 +311,7 @@ impl ResolverState, - request_sink: Sender, + request_sink: &Sender, ) -> Result { debug!( "Solving with installed Python version: {}", @@ -390,7 +390,7 @@ impl ResolverState ResolverState ResolverState ResolverState ResolverState