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 } 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 diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 9a2e5ba1d..843ee8dfb 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 }} @@ -132,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: . @@ -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 }} @@ -261,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 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b33f96fe2..bc77abd93 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 @@ -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,12 +208,12 @@ 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 - - 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 @@ -240,12 +240,12 @@ 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 - - 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,11 +279,11 @@ 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 - - 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" @@ -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' }} @@ -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 @@ -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 @@ -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 @@ -486,7 +511,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 +536,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 +560,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 +590,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 +625,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 +662,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 +675,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` @@ -661,7 +686,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 @@ -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 @@ -852,7 +904,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 +1052,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 @@ -1443,7 +1585,7 @@ jobs: run: chmod +x ./uv - name: "Configure AWS credentials" - uses: aws-actions/configure-aws-credentials@3bb878b6ab43ba8717918141cd07a0ea68cfe7ea + 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 }} @@ -2072,7 +2214,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 @@ -2090,6 +2232,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: windows-11-arm + 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 @@ -2337,7 +2501,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 +2538,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 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/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/.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 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 982d8f296..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.11.13 + rev: v0.12.2 hooks: - id: ruff-format - id: ruff diff --git a/CHANGELOG.md b/CHANGELOG.md index 159c39331..c1c163331 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,145 @@ +## 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 diff --git a/Cargo.lock b/Cargo.lock index 95e9308b4..ef7511af5 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" @@ -180,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", @@ -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" @@ -512,9 +530,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", @@ -672,22 +690,27 @@ checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "codspeed" -version = "2.10.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f4cce9c27c49c4f101fffeebb1826f41a9df2e7498b7cd4d95c0658b796c6c" +checksum = "922018102595f6668cdd09c03f4bff2d951ce2318c6dca4fe11bdcb24b65b2bf" 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.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c23d880a28a2aab52d38ca8481dd7a3187157d0a952196b6db1db3c8499725" +checksum = "24d8ad82d2383cb74995f58993cbdd2914aed57b2f91f46580310dd81dc3d05a" dependencies = [ "codspeed", "codspeed-criterion-compat-walltime", @@ -696,9 +719,9 @@ dependencies = [ [[package]] name = "codspeed-criterion-compat-walltime" -version = "2.10.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0a2f7365e347f4f22a67e9ea689bf7bc89900a354e22e26cf8a531a42c8fbb" +checksum = "61badaa6c452d192a29f8387147888f0ab358553597c3fe9bf8a162ef7c2fa64" dependencies = [ "anes", "cast", @@ -1142,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", @@ -1675,7 +1698,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots", + "webpki-roots 0.26.8", ] [[package]] @@ -1684,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", @@ -1691,7 +1715,9 @@ dependencies = [ "http", "http-body", "hyper", + "ipnet", "libc", + "percent-encoding", "pin-project-lite", "socket2", "tokio", @@ -1873,9 +1899,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", @@ -1922,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" @@ -2474,9 +2510,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" @@ -3039,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", @@ -3056,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", @@ -3075,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]] @@ -3328,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" @@ -3405,11 +3427,12 @@ dependencies = [ [[package]] name = "schemars" -version = "0.8.22" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" dependencies = [ "dyn-clone", + "ref-cast", "schemars_derive", "serde", "serde_json", @@ -3418,9 +3441,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.22" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +checksum = "33d020396d1d138dc19f1165df7545479dcd58d93810dc5d646a16e55abefa80" dependencies = [ "proc-macro2", "quote", @@ -3719,6 +3742,16 @@ 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", + "num-traits", +] + [[package]] name = "strict-num" version = "0.1.1" @@ -3934,9 +3967,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", @@ -3944,9 +3977,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", @@ -4138,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" @@ -4232,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" @@ -4569,7 +4608,7 @@ dependencies = [ [[package]] name = "uv" -version = "0.7.14" +version = "0.7.19" dependencies = [ "anstream", "anyhow", @@ -4583,7 +4622,6 @@ dependencies = [ "ctrlc", "dotenvy", "dunce", - "etcetera", "filetime", "flate2", "fs-err 3.1.1", @@ -4719,7 +4757,6 @@ dependencies = [ "uv-configuration", "uv-dispatch", "uv-distribution", - "uv-distribution-filename", "uv-distribution-types", "uv-extract", "uv-install-wheel", @@ -4735,7 +4772,7 @@ dependencies = [ [[package]] name = "uv-build" -version = "0.7.14" +version = "0.7.19" dependencies = [ "anyhow", "uv-build-backend", @@ -4798,6 +4835,7 @@ dependencies = [ "tokio", "toml_edit", "tracing", + "uv-cache-key", "uv-configuration", "uv-distribution", "uv-distribution-types", @@ -5135,7 +5173,6 @@ dependencies = [ "serde", "smallvec", "thiserror 2.0.12", - "url", "uv-cache-key", "uv-normalize", "uv-pep440", @@ -5178,6 +5215,7 @@ dependencies = [ "uv-pypi-types", "uv-redacted", "uv-small-str", + "uv-warnings", "version-ranges", ] @@ -5602,7 +5640,7 @@ dependencies = [ "uv-trampoline-builder", "uv-warnings", "which", - "windows-registry 0.5.2", + "windows-registry", "windows-result 0.3.4", "windows-sys 0.59.0", ] @@ -5806,7 +5844,7 @@ dependencies = [ "tracing", "uv-fs", "uv-static", - "windows-registry 0.5.2", + "windows-registry", "windows-result 0.3.4", "windows-sys 0.59.0", ] @@ -5904,6 +5942,7 @@ name = "uv-types" version = "0.0.1" dependencies = [ "anyhow", + "dashmap", "rustc-hash", "thiserror 2.0.12", "uv-cache", @@ -5923,7 +5962,7 @@ dependencies = [ [[package]] name = "uv-version" -version = "0.7.14" +version = "0.7.19" [[package]] name = "uv-virtualenv" @@ -6187,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" @@ -6330,7 +6378,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 +6448,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" @@ -6416,24 +6464,13 @@ dependencies = [ [[package]] name = "windows-registry" -version = "0.4.0" +version = "0.5.3" 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.2" -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 +6502,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", ] @@ -6701,8 +6738,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..fc19dcc9a 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" @@ -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"] } @@ -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"] } @@ -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"] } 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-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 { diff --git a/crates/uv-bench/Cargo.toml b/crates/uv-bench/Cargo.toml index 65ce78731..8c08d4dd2 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 } @@ -48,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.2", default-features = false, optional = true } +criterion = { version = "0.6.0", default-features = false, features = [ + "async_tokio", +] } jiff = { workspace = true } tokio = { 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); 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/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-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-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..5cbaece2e 100644 --- a/crates/uv-build-frontend/src/lib.rs +++ b/crates/uv-build-frontend/src/lib.rs @@ -25,12 +25,14 @@ 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; 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. @@ -434,6 +441,31 @@ impl SourceBuild { }) } + /// 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 = 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) + } + async fn get_resolved_requirements( build_context: &impl BuildContext, source_build_context: SourceBuildContext, @@ -604,6 +636,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. @@ -716,16 +751,15 @@ 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 { + // 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 @@ -737,7 +771,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 +784,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 +800,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 +814,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 +844,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 +859,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, diff --git a/crates/uv-build/Cargo.toml b/crates/uv-build/Cargo.toml index 26c046b2b..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.14" +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 4dab6a520..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.14" +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-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-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-client/src/base_client.rs b/crates/uv-client/src/base_client.rs index 85c384b0d..e11845adb 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!( @@ -971,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 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/name_specifiers.rs b/crates/uv-configuration/src/name_specifiers.rs index 5ff209948..3efeee1f2 100644 --- a/crates/uv-configuration/src/name_specifiers.rs +++ b/crates/uv-configuration/src/name_specifiers.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "schemars")] +use std::borrow::Cow; use std::str::FromStr; use uv_pep508::PackageName; @@ -63,28 +65,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..70c69eaf3 100644 --- a/crates/uv-configuration/src/required_version.rs +++ b/crates/uv-configuration/src/required_version.rs @@ -1,5 +1,6 @@ -use std::fmt::Formatter; -use std::str::FromStr; +#[cfg(feature = "schemars")] +use std::borrow::Cow; +use std::{fmt::Formatter, str::FromStr}; use uv_pep440::{Version, VersionSpecifier, VersionSpecifiers, VersionSpecifiersParseError}; @@ -36,20 +37,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/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-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-configuration/src/trusted_host.rs b/crates/uv-configuration/src/trusted_host.rs index 64fb14169..07ff2998a 100644 --- a/crates/uv-configuration/src/trusted_host.rs +++ b/crates/uv-configuration/src/trusted_host.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Deserializer}; +#[cfg(feature = "schemars")] +use std::borrow::Cow; use std::str::FromStr; use url::Url; @@ -143,20 +145,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-dev/src/generate_sysconfig_mappings.rs b/crates/uv-dev/src/generate_sysconfig_mappings.rs index 632d8f6c1..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/20250612/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-dispatch/src/lib.rs b/crates/uv-dispatch/src/lib.rs index 207f241ad..874e412e5 100644 --- a/crates/uv-dispatch/src/lib.rs +++ b/crates/uv-dispatch/src/lib.rs @@ -11,6 +11,7 @@ use itertools::Itertools; use rustc_hash::FxHashMap; 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 +36,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 +180,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 } @@ -448,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 { @@ -521,6 +520,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 +534,7 @@ impl SharedState { Self { git: self.git.clone(), capabilities: self.capabilities.clone(), + build_arena: self.build_arena.clone(), ..Default::default() } } @@ -556,4 +558,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-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 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/file.rs b/crates/uv-distribution-types/src/file.rs index e17901f80..a75af3977 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,16 +161,33 @@ 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`] (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)) } } @@ -252,16 +270,51 @@ 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(), &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(), - UrlString("https://example.com/path?query".into()) + &*url.without_fragment(), + &UrlString("https://example.com/path?query".into()) ); + assert!(matches!(url.without_fragment(), Cow::Owned(_))); + } - 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(), &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 a523b4811..0290018f1 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}; @@ -37,6 +38,8 @@ 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, ..)) => { @@ -92,20 +95,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." + }) } } @@ -140,6 +138,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 { @@ -162,6 +184,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 { @@ -214,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)) + } } } } @@ -411,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 { @@ -625,3 +689,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-distribution-types/src/pip_index.rs b/crates/uv-distribution-types/src/pip_index.rs index 6ce22abd2..18671e42f 100644 --- a/crates/uv-distribution-types/src/pip_index.rs +++ b/crates/uv-distribution-types/src/pip_index.rs @@ -3,6 +3,8 @@ //! flags set. use serde::{Deserialize, Deserializer, Serialize}; +#[cfg(feature = "schemars")] +use std::borrow::Cow; use std::path::Path; use crate::{Index, IndexUrl}; @@ -50,14 +52,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/requires_python.rs b/crates/uv-distribution-types/src/requires_python.rs index ae9fee7fe..49a4fd5c4 100644 --- a/crates/uv-distribution-types/src/requires_python.rs +++ b/crates/uv-distribution-types/src/requires_python.rs @@ -66,15 +66,8 @@ 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| 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-distribution-types/src/status_code_strategy.rs b/crates/uv-distribution-types/src/status_code_strategy.rs index a2940a23a..b019d0329 100644 --- a/crates/uv-distribution-types/src/status_code_strategy.rs +++ b/crates/uv-distribution-types/src/status_code_strategy.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "schemars")] +use std::borrow::Cow; use std::ops::Deref; use http::StatusCode; @@ -136,17 +138,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-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/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-distribution/src/source/mod.rs b/crates/uv-distribution/src/source/mod.rs index 90d77bd90..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; @@ -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 @@ -2270,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 @@ -2290,27 +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 { - 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()))? - .wheel(temp_dir.path()) - .await - .map_err(Error::Build)? + // 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)? + }; + + let build_kind = if source.is_editable() { + BuildKind::Editable + } else { + BuildKind::Wheel + }; + + 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, + }; + + 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. @@ -2365,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 @@ -2375,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(), ) @@ -2388,6 +2457,21 @@ 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); }; 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:?}" 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 { 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-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-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 d404390f3..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 { @@ -46,6 +49,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,31 +77,32 @@ 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 { 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 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: {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", @@ -91,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: {url} ({})", + "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); } @@ -108,7 +134,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)) } 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.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 2bd7dcd4d..38038ffcf 100644 --- a/crates/uv-pep440/src/version_ranges.rs +++ b/crates/uv-pep440/src/version_ranges.rs @@ -132,7 +132,7 @@ impl From for Ranges { 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 = range.intersection(&release_specifier_to_range(specifier, false)); } range } @@ -148,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() } } } @@ -223,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, }) } @@ -358,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 4255c13fa..e111c5118 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 { @@ -416,7 +457,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 } @@ -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), @@ -838,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-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..e2945743b 100644 --- a/crates/uv-pep508/src/lib.rs +++ b/crates/uv-pep508/src/lib.rs @@ -16,6 +16,8 @@ #![warn(missing_docs)] +#[cfg(feature = "schemars")] +use std::borrow::Cow; use std::error::Error; use std::fmt::{Debug, Display, Formatter}; use std::path::Path; @@ -334,22 +336,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/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 070a24b26..5739d7c98 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'`" + }) } } @@ -2279,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 @@ -2295,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 @@ -2515,7 +2516,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')", @@ -2536,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` @@ -3332,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-pep508/src/verbatim_url.rs b/crates/uv-pep508/src/verbatim_url.rs index 988bebc5e..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, } @@ -166,6 +171,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-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. diff --git a/crates/uv-pypi-types/src/conflicts.rs b/crates/uv-pypi-types/src/conflicts.rs index 94366bfd2..81064955a 100644 --- a/crates/uv-pypi-types/src/conflicts.rs +++ b/crates/uv-pypi-types/src/conflicts.rs @@ -3,6 +3,8 @@ use petgraph::{ graph::{DiGraph, NodeIndex}, }; use rustc_hash::{FxHashMap, FxHashSet}; +#[cfg(feature = "schemars")] +use std::borrow::Cow; use std::{collections::BTreeSet, hash::Hash, rc::Rc}; use uv_normalize::{ExtraName, GroupName, PackageName}; @@ -638,12 +640,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..47439f2c9 100644 --- a/crates/uv-pypi-types/src/identifier.rs +++ b/crates/uv-pypi-types/src/identifier.rs @@ -1,4 +1,6 @@ use serde::{Serialize, Serializer}; +#[cfg(feature = "schemars")] +use std::borrow::Cow; use std::fmt::Display; use std::str::FromStr; use thiserror::Error; @@ -99,25 +101,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/download-metadata.json b/crates/uv-python/download-metadata.json index 71fe83c78..b697da9c8 100644 --- a/crates/uv-python/download-metadata.json +++ b/crates/uv-python/download-metadata.json @@ -1,4 +1,836 @@ { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { + "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/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": { "name": "cpython", "arch": { @@ -4731,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-apple-darwin-install_only_stripped.tar.gz", - "sha256": "3e52e6b539dca2729788a06f3f47b2edfc30ba3ef82eb14926f0a23ed0ce4cff", + "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": { @@ -4747,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-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "fce29c000087f0ed966580aff703117d8238e2be043a90a2a0ec8352c0708db8", + "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": { @@ -4763,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-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "6957a6d66c633890fc97f3da818066cd0d10de7cf695a7c46c4c23b107c02fa7", + "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": { @@ -4779,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-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "1ca9efecff540162b22e5b86152864e621c97463061171f6734cd31d50e39f1d", + "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": { @@ -4795,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-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "4efad529678f93119e99bd5494070c76f5c54958bc9686ee12fd9e1950c80b27", + "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": { @@ -4811,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-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "abbd685fe948653ad79624e568f9f843233e808c8756d6d4429dbe1d3e7550f9", + "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": { @@ -4827,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-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "4a17bcc199782d0bbaaf073b0eedfac0ebfc5eeab2cc23b9b59968869708779c", + "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": { @@ -4843,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-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "6d877e1b2c302d205f0bddbc76b7ca465fa227d9252147df7821d5474d4ea147", + "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": { @@ -4859,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-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "5c63e7ffe47baff0a96a685c94fb5075612817741feb4e85ec3cc082c742b4f8", + "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": { @@ -4875,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-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "641d0cefa3124e42728fc5dac970d5a172a61d80d2a5a24995f2b6e9ddf71e3f", + "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": { @@ -4891,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_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "7f60b2f61fad6c846c7d7c8f523dee285c36cd9d53573314b6ca82eca4e80241", + "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": { @@ -4907,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_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "21c20b03866e1ec3dcd86224cf82396e58e175768e51030ab83ba21d482cfc26", + "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": { @@ -4923,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_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "ec8794e649b3e0abac7dccda7de20892ce1ba43f2148e65b2a66edeba42f4c61", + "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": { @@ -4939,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_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "56576436ccc2a9e509becd72ebdbc9abf147a471df6e1022dcd6967975ccee55", + "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": { @@ -4955,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-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "b6aa1c65d74b528130244888ee5b47fbf52451aabac2a98e5b87873e73705d87", + "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": { @@ -4971,8 +5803,24 @@ "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/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": { + "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/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": { @@ -4987,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-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "254754ec03dd94dc8e67f89b415253a9ee16f0d277478e0e01c25de45b7fc172", + "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": { @@ -5003,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-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "361fa8ca96403f890d0ef4f202ea410b2e121273830c979d6517f2c7e733b5e2", + "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": { @@ -5019,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-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "a29cb4ef8adcd343e0f5bc5c4371cbc859fc7ce6d8f1a3c8d0cd7e44c4b9b866", + "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": { @@ -5035,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-x86_64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "52aeb1b4073fa3f180d74a0712ceabc86dd2b40be499599e2e170948fb22acde", + "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": { @@ -5051,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-aarch64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "0ef13d13e16b4e58f167694940c6db54591db50bbc7ba61be6901ed5a69ad27b", + "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": { @@ -5067,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-armv7-unknown-linux-gnueabi-freethreaded%2Blto-full.tar.zst", - "sha256": "4eb024f92a1e832c7533d17d566c47eabcb7b5684112290695ef76a149282ee4", + "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": { @@ -5083,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-armv7-unknown-linux-gnueabihf-freethreaded%2Blto-full.tar.zst", - "sha256": "cac9d5fe76dcc4942b538d5f5a9fa6c20f261bc02a8e75821dd2ea4e6c214074", + "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": { @@ -5099,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-ppc64le-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "66545ad4b09385750529ef09a665fc0b0ce698f984df106d7b167e3f7d59eace", + "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": { @@ -5115,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-riscv64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "a82a741abefa7db61b2aeef36426bd56da5c69dc9dac105d68fba7fe658943ca", + "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": { @@ -5131,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-s390x-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "403c5758428013d5aa472841294c7b6ec91a572bb7123d02b7f1de24af4b0e13", + "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": { @@ -5147,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-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "33fdd6c42258cdf0402297d9e06842b53d9413d70849cee61755b9b5fb619836", + "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": { @@ -5163,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-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "35c387d86e2238d65c16962003475074a771bd96a6e6027606365dd9b23307c2", + "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": { @@ -5179,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_v2-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "5de3a03470af84e56557a5429137f96632536b0dc07ec119988e9936fcd586b5", + "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": { @@ -5195,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_v2-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "4e4f198e3cb5248921a7292620156412735b154201571f5da6198167b9888b5c", + "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": { @@ -5211,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_v3-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "b826fe552e2fcc12859f963410e2c1a109929fe5b73978a74f64c6c812fef92f", + "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": { @@ -5227,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-x86_64_v3-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "20702fe10bef77477eb02c5a1817650560142b477572f391c297e137daf7a057", + "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": { @@ -5243,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_v4-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "d4024ab82373300a8c1262033af61f64b3348379afe9a112004cf6988468b551", + "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": { @@ -5259,8 +6107,24 @@ "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/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": { + "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/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": { @@ -5275,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-i686-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", - "sha256": "9256369550eeb71729dca0094d098d161035806c24b9b9054cb8038b05bd7e0f", + "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": { @@ -5291,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-x86_64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", - "sha256": "9da2f02d81597340163174ee91d91a8733dad2af53fc1b7c79ecc45a739a89d5", + "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": { @@ -5307,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-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "6280fc111ff607571362c12e8b1b12a3799a3fbec60498bd0ebff77d30efa89f", + "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": { @@ -5323,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-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "48b4fb6454358be8383598876d389fcf5cb5144af07d200e0b0e7c7824084e3e", + "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": { @@ -5339,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-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "bd5df3367a45decbb740432d8e838975ac58c40fc495ed54dbbe321dccb0cd44", + "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": { @@ -5355,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-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "e95e45ad547ab71a919a22d76b065881926a00920c5cc1ee6d97be4d66daa12d", + "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": { @@ -5371,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-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "ba433760881a5b0da9b46dcdcf2dd8ca6917901e78dbac74c4ba3ab5e6f3ced3", + "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": { @@ -5387,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-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "f6f6f8187ede00fa3938d4c734008eafec2041636a8987e2c85df3273004b822", + "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": { @@ -5403,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-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "137c6262fa2b26f20d7e1bed1c1467a7665086bb88dc1c2cb40cf23e7da6d469", + "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": { @@ -5419,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-unknown-linux-musl-debug-full.tar.zst", - "sha256": "f3c2dce0fb4b62106d3957fc23b757a604251ff624a0d66ef126fab4ece9de0c", + "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": { @@ -5435,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_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "962846d36bbb78d76f6ac1db77fb37bb9fdda4d545d211048cc3212247890845", + "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": { @@ -5451,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_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "515b5cdbb612b606a2215aa2ce94114a2442b34e96a1fcc4a45cb3944a0cc159", + "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": { @@ -5467,8 +6331,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/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": { @@ -5483,8 +6347,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/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": { @@ -5499,8 +6363,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/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": { @@ -5515,8 +6379,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/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": { @@ -9739,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-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "74dd3b2bbbcb5c87a5044e1f3513fe3b07e72fcfdeb039d0ae83b754911ac31e", + "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": { @@ -9755,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-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "16797cdee1b879ce0f32d9162f2a3af8b91d8ccb663c75ed3afc2384845c24d7", + "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": { @@ -9771,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-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "df383a0992be93314880232c2ecbe9764ee65caee5f72a13ef672684fc7b8063", + "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": { @@ -9787,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-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "92c9c4bd3e4f8bd086ae7ff234273898e83340e4d65fa5b50b0e87db8197fdff", + "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": { @@ -9803,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-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "197e34e0a74504f2700d4e4c11cb0d281aa13c628af8b9ad21532250bda45659", + "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": { @@ -9819,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-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "f4bce8f1dcf7bef91d1ea54af48a45333983a41b83c0b8e33e9b07bb4b4499a0", + "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": { @@ -9835,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-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "26afb0f604cd9cac7af5e3078bbdcb7f701cd1f4956fba0620cc184bc9b32927", + "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": { @@ -9851,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-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "1c7327875d7669862fd1627c57a813378d866998c5d5008276c8952af7323d19", + "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": { @@ -9867,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-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "15a3c9964e485f04d3c92739aca190616e09b2c4fac29b263432f6f29f00c6cf", + "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": { @@ -9883,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-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "0b68e1de34febc8c57df3d0bf13e3397493bacc432b4cc3d27a338c2d4b8a428", + "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": { @@ -9899,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_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "351b7a97142bc0539ef67e1ad61961a99df419487af422b2242664702f3d3fde", + "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": { @@ -9915,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_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "2a4d4edb63585bbfc4afa4bddd5e3efb20202903925ace6f0797df1ad2a6189d", + "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": { @@ -9931,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-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "bb742a2e0bc09b0afd4c37056ea0bda16095d8468442eadb6285716d0fcb8ab0", + "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": { @@ -9947,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_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "5bf4aab9fea91a6daae95f40cd12c7d4127bed98bc5ea4fcbee9f03afc1530ef", + "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": { @@ -9963,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-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "05f1a3f7711aae5c65e4557ea102315a779cbe03e39f16dc405f8fc8ede25e83", + "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": { @@ -9979,8 +10843,24 @@ "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/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": { + "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/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": { @@ -9995,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-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "41d5dd36a6964f37b709061c5c01429579ef3c3e117ed7043d6a3a1f336671d6", + "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": { @@ -10011,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-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "51bc462f3d6caf4aef3d77209d01cd5f6c8fe8213c1ae739e573e1c2c473cb2b", + "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": { @@ -10027,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-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "208f407d3880dc84772d8a322776011abf362ac004540debbd2ea5e5f884f398", + "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": { @@ -10043,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-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "880268f3e83952d5bdb32422da2ce7f24ee24c6251b946514ffcdbaedc4ced37", + "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": { @@ -10059,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-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "d3128aba3c94c46d0f5d15474af6a8b340b08ada33a31ad20387cfa46891752c", + "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": { @@ -10075,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-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "3c43d94ef5fc2db0fd937f16616c9aceaf0ebc4846ae9454190ed30b0ad4830c", + "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": { @@ -10091,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-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "be24538f1d59620a3229582f7adf9ca0df3129bd6591ff45b6ce42d1bb57602f", + "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": { @@ -10107,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-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "fa5095dd99ffa5e15dbe151c3d98dbe534c230ce6b9669eef34e037fc885ed91", + "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": { @@ -10123,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-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "b1b114b14624ec9955ea1404908636580280a8537ce85ace2fd48197edf82ee0", + "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": { @@ -10139,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-unknown-linux-musl-debug-full.tar.zst", - "sha256": "8535b0850eab8f8cf6e29a5642f7501f5de0318d7805de6aa2cc69c0b7f4895d", + "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": { @@ -10155,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_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "d701b1a19ce8fd94c01a72cbe70dd0b79cb59686a7f2fd28c8d68c38d7603f44", + "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": { @@ -10171,8 +11051,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/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": { @@ -10187,8 +11067,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/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": { @@ -10203,8 +11083,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/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": { @@ -10219,8 +11099,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/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": { @@ -10235,8 +11115,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/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": { @@ -14283,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-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "3cc448659ee0d6aff8a90ca0dcaf00c29974f5d48ccc2c37e7a6e3baa6806005", + "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": { @@ -14299,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-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "6617e8e95ccbbd27fc82f29b0e56e9d9b8a346435c3510374e4410bfd1150421", + "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": { @@ -14315,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-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "ac7257a5c1c9757ce4aa61d6c9bc443cd8ab052105b0e1c6714040c6e9e50eff", + "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": { @@ -14331,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-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "0b01b96e0e4190f64fef6e2c76d0746321fc8cc91c7f3319452a90eaaa376c00", + "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": { @@ -14347,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-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "f30cc16c9ea9a2795b5cafef91f3637165781a6a7a54fdc4baf6438a0800e7ce", + "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": { @@ -14363,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-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "472ec854f7944528f366b08e8f6efbb4c02ed265eecc259c0e1f7cf12400ea14", + "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": { @@ -14379,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-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "024ba68fc755b595f4a21dbc1d8744231e51b76111d8d83e96691fb7acbf37a5", + "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": { @@ -14395,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-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "6f404269ce33fe0dd8cc918da001662b6ffdfbe7eb13f906cbc92e238f75f6be", + "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": { @@ -14411,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-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "059cbbfd84bfc9ef8a92605fa8aef645bbb45b792cac8adf865050a5e7d68909", + "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": { @@ -14427,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-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "0237b76f625f08683a2e615ae907428240d90898b17a60bdec88a85bf9095799", + "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": { @@ -14443,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_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "9ae198daf242ffd6ad5b395594aa157aba62a044d007202cb03659fbb94d3132", + "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": { @@ -14459,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-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "a74c87f366001349fcf3297b87698ac879256ed4b73776ff8fa145c457c0cd13", + "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": { @@ -14475,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_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "b24df39e08456e50fc99c43e695a46240b11251e8b43666f978ee98ec1197e05", + "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": { @@ -14491,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-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "2767b36bb48da971dc5af762b42df2c9d1f4839326d26c5a710b543372dcc640", + "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": { @@ -14507,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-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "a1478d014d07e4a56f0c136534931989a93110ab0b15a051a33fbf0c22bec0d2", + "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": { @@ -14523,8 +15403,24 @@ "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/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": { + "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/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": { @@ -14539,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-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "bdb7c0727af0b0d0d5f0d6b37a3139185a0f9259e4ce70f506c23e29e64fcb0f", + "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": { @@ -14555,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-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "22c2ab8be0d4028ddc115583f2c41c57ee269c115ef48a41ddde9adba5bac15b", + "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": { @@ -14571,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-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "fbd958ddbe10dd9d9304eb3d3dc5ed233524e1e96746e7703ceafedf2af3b82e", + "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": { @@ -14587,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-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "b03bcfe961888241beefc5648802041764f3914bcb7aadce8d2cbfffd23694d4", + "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": { @@ -14603,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-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "3b7d013b8c200194962ce7dd699d628282ae4343ecdfe33ab1e4ac3cb513e5a5", + "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": { @@ -14619,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-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "fc84e9f6c98b76525ae2267d24f27c69da6e1ddd77f1cac2613d8b152fa436f1", + "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": { @@ -14635,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-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "fc963382c154fbb924b05895cc9849e83998a32060083f7167ae53f5792ac145", + "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": { @@ -14651,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-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "01679e250e8693b9d330967741569822db15693926bcdf8e4875b51a8767e9c1", + "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": { @@ -14667,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-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "206917fe06cdeeb76abd30e3dd01a71355fd41b685c1dbbddbfd0ad47371d5b6", + "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": { @@ -14683,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-unknown-linux-musl-debug-full.tar.zst", - "sha256": "5b7a576ba0e99e7cf734b5a0f1f0fb48564c42a04d373e1370b22f2e70995d79", + "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": { @@ -14699,8 +15595,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/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": { @@ -14715,8 +15611,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/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": { @@ -14731,8 +15627,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/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": { @@ -14747,8 +15643,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/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": { @@ -14763,8 +15659,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/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": { @@ -14779,8 +15675,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/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": { @@ -18571,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-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "d588e367ad0ccc96080f02a6e534b272e1769aeddc0a2ce46da818c42893ebfd", + "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": { @@ -18587,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-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "2758bbee1709eb355cf0c84a098cf51807a94e2f818eb15a735b71c366b80f9b", + "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": { @@ -18603,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-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "89a01966d48e36f5ba206f3861ad41b6246160c3feae98a2ffe0c4ce611acfeb", + "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": { @@ -18619,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-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "a095eaeac162f0a57d85b7f7502621d9b9572a272563a59b89b69ae4217e031e", + "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": { @@ -18635,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-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "559799c5b87d742b3b71dc1e7b015a9cd6d130f7b6afcf6ad8716c75c204d47e", + "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": { @@ -18651,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-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "740a2e5f935c6d7799f959731b7cd8f855c1e572ad43f0ec16417c3390f4551d", + "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": { @@ -18667,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-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "00a436a8f8ad236d084a6d6a1308543755d9175e042b89ea3c06cc1b1651e6aa", + "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": { @@ -18683,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-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "d23284bc718fb049dd26056efc632042824409599cae9a4c2362277761b50e94", + "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": { @@ -18699,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-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "e884283a9a3bb97e9432bbda0bf274608d8fce2f27795485e4a93bbaef66e5a1", + "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": { @@ -18715,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-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "cea06c4409e889945c448ec223f906e9e996994d6b64f05e9d92dc1b6b46a5f8", + "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": { @@ -18731,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-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "4636b6756eb1a9f6db254aac8ae0007c39614fcbf065daf8dc547530ac77c685", + "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": { @@ -18747,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_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "6e0a68e53059d1ccf5a0efc17d60927e53c13e40b670425c121f35cd3fd10981", + "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": { @@ -18763,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-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "1c164e2eeb586d930a427e58db57b161b8ec4b9adf4d31797fdccf78d373a290", + "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": { @@ -18779,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-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "4ae64d7a6ba3c3cb056c2e17c18087b1052e13045e4fbb2e62e20947de78c916", + "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": { @@ -18795,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-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "cd64acdb527382c7791188f8b5947d1a9e80375ad933e325fb89a128af60234d", + "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": { @@ -18811,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-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "8d300050b13d3674f5d19bf402780ec2fb19bf594dd75fd488b83856ed104def", + "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": { @@ -18827,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-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "3df494fd91ccc55ea0b512f70d4b49b4bee781b6e31bfa65c8d859150b6d3715", + "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": { @@ -18843,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-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "4d6337bfafdb6af5c4c6fdb54fd983ead0c4d23cf40fb6b70fce0bd8b3b46b59", + "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": { @@ -18859,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-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "2808899e02b96a4ed8cfe7a4e9e42372c0d8746f7cdbf52d21645bd4da1f9f9f", + "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": { @@ -18875,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-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "e327ab463c630396b9a571393adfce4d63b546a2af280c894fef1c74dbb21223", + "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": { @@ -18891,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-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "031a467de049990d3133e6ff26612e5d227abda4decfa12ea8340ca8ec7e55d4", + "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": { @@ -18907,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-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "9b48f5108b419e4371142ec5a65a388473e4990181c82343c3dfaf3d87f02a5a", + "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": { @@ -18923,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-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "8215a063192a64fad2b5838b34d20adcd30da5cc2e9598f469eea8d3f0de09f5", + "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": { @@ -18939,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-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "77b9f02331df8acf41784648f75cc77c5ab854546a405b389067f61ded68a5c6", + "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": { @@ -18955,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-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "bba5598c6fc8f85e68b590950b5e871143647921197be208a94349d7656eafdf", + "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": { @@ -18971,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-unknown-linux-musl-debug-full.tar.zst", - "sha256": "6c006d1daae48ba176bb85fd0c444914d9e2ee20b58e8131c158bb30fe9097c9", + "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": { @@ -18987,8 +19883,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/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": { @@ -19003,8 +19899,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/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": { @@ -19019,8 +19915,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/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": { @@ -19035,8 +19931,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/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": { @@ -19051,8 +19947,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/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": { @@ -19067,8 +19963,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/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": { @@ -24011,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-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "05741156232cc28facaefbda2d037605dd71614d343c7702e0d9ab52c156945e", + "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": { @@ -24027,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-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "3e6cf3c8c717f82f2b06442e0b78ececa7e7c67376262e48bf468b03e525ef31", + "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": { @@ -24043,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-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "96ec244baeaf57a921da7797da41e49e9902a2a6b24c45072a8acee7ff9e881d", + "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": { @@ -24059,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-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "9c58d7126db2a66b81bff94b0e15a60974687f9ef74985d928e44397a10986cb", + "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": { @@ -24075,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-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "929fbed568da7325b7db351d32cd003ee77c4798f0f946a6840935054a07174f", + "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": { @@ -24091,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-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "b52371b6485ab4603da77ff49b8092d470586a2330e56d58d9f8683a5590ae68", + "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": { @@ -24107,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-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "ef4e83f827377bdb4656130ee953b442a33837ca31ef71547ac997d9979b91e4", + "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": { @@ -24123,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-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "c8f5d2951900e41db551b692f2aa368e89449d3a4cf2761a80eb350fbd25bc0b", + "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": { @@ -24139,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-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "461a5ef25341b2e9f62f21d3fa4184ac51af59cebb5fb9112fe64e338851109f", + "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": { @@ -24155,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-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "f5f3b7e7be74209fa76a9eed4645458e82bec3533d8aad1c45770506b1870571", + "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": { @@ -24171,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-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "302ae70b497361840e6f62e233f058ea0a41b91e4cc01050da940419b6b4b3d6", + "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": { @@ -24187,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_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "740f1f6e1c1a70e7e7aaa47c0274ee6208b16bd1fe8d0c3600ecccb2481a5881", + "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": { @@ -24203,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-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "d4689ad72cb22973d12c4f7128588ed257e4976d69d92f361da4ddbcec8ce193", + "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": { @@ -24219,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-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "5c094d6d91bf0e424c12b26627e0095f43879fefc8cf6050201b39b950163861", + "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": { @@ -24235,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-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "2933a49ca7292e5ed1849aeb59057ec75ea9e018295d1bb8a3c155a7e46b3dde", + "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": { @@ -24251,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-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "b8e92fe27a41d7d3751cdbffff7b4de3c84576fd960668555c20630d0860675e", + "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": { @@ -24267,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-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "d40486ddf29d6a70dda7ea8d56666733943de19ff8308b713175cba00d0f6a0f", + "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": { @@ -24283,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-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "5c4edcc6861f7fc72807c600666b060f3177a934577a0b1653f1ab511fdac4a0", + "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": { @@ -24299,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-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "c74a865c4e3848b38f4e1e96b24ba4e839c5776cb0ea72abe7b652a1524a1a51", + "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": { @@ -24315,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-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "5833d757964b5fe81f07183def651556ccd2c5cc9054373a6483b4ffb140ea72", + "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": { @@ -24331,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-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "f5880944d32da9b4b65e88a21e4e2c3410ca299886a86db19566936b36fc445a", + "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": { @@ -24347,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-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "5ef02120a1e82c3d2c60f04380c8cac171cea59bc647e6089d4b2971e70a4b06", + "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": { @@ -24363,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-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "b480c9dd67c1c2bccce609f145f562958e1235d294f8b5be385d3b5daca76e23", + "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": { @@ -24379,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-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "c2f3433e9820f2c8a70363d8faa7e881079e5b9e50a4764702c470add3c899ee", + "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": { @@ -24395,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-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "120bc8aafdc5dcb828c27301776ccab4204feb3ad38fe03d7b0c8321617762f4", + "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": { @@ -24411,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-unknown-linux-musl-debug-full.tar.zst", - "sha256": "c79452c6ac6b7287b4119ba7a94cdaaa7edd50dbb489c76a2b3f1e3d0563b35a", + "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": { @@ -24427,8 +25323,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/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": { @@ -24443,8 +25339,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/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": { @@ -24459,8 +25355,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/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": { @@ -24475,8 +25371,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/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": { @@ -24491,8 +25387,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/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": { @@ -24507,8 +25403,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/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/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-python/src/downloads.rs b/crates/uv-python/src/downloads.rs index 51f6f1d45..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; } } @@ -772,7 +816,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 { 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/interpreter.rs b/crates/uv-python/src/interpreter.rs index b62633283..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 @@ -977,33 +1002,18 @@ 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. - format!("{}.msgpack", cache_digest(&absolute)), + // paths. But we don't want to query the executable with symbolic links resolved because + // 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 @@ -1015,7 +1025,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() ); 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 => { 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-python/src/python_version.rs b/crates/uv-python/src/python_version.rs index 39e8e3429..c5d8f6365 100644 --- a/crates/uv-python/src/python_version.rs +++ b/crates/uv-python/src/python_version.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "schemars")] +use std::borrow::Cow; use std::fmt::{Display, Formatter}; use std::ops::Deref; use std::str::FromStr; @@ -65,26 +67,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-python/src/sysconfig/generated_mappings.rs b/crates/uv-python/src/sysconfig/generated_mappings.rs index 32a432b54..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 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-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-resolver/src/error.rs b/crates/uv-resolver/src/error.rs index adbdc3cc7..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, @@ -17,6 +18,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 +59,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 +77,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, }, @@ -148,7 +157,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 { @@ -359,6 +368,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 { @@ -396,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 { @@ -1213,6 +1236,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/exclude_newer.rs b/crates/uv-resolver/src/exclude_newer.rs index 40ec009f8..65fa55cfe 100644 --- a/crates/uv-resolver/src/exclude_newer.rs +++ b/crates/uv-resolver/src/exclude_newer.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "schemars")] +use std::borrow::Cow; use std::str::FromStr; use jiff::{Timestamp, ToSpan, tz::TimeZone}; @@ -67,25 +69,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-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/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/lock/mod.rs b/crates/uv-resolver/src/lock/mod.rs index 5af4377b9..beeadc912 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 @@ -1488,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) => { @@ -1793,7 +1799,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. @@ -2371,7 +2377,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 +2456,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 +2502,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), @@ -2523,19 +2547,41 @@ 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_url = - DisplaySafeUrl::from_file_path(workspace_root.join(path).join(file_path)) - .map_err(|()| LockErrorKind::PathToUrl)?; + 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 { 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), @@ -2545,9 +2591,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)?, @@ -3227,7 +3274,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)?; @@ -3660,14 +3709,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(), @@ -3787,34 +3828,60 @@ 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 url = reg_dist .file .url .to_url() - .map_err(LockErrorKind::InvalidUrl)? - .to_file_path() - .map_err(|()| LockErrorKind::UrlToPath)?; - 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, - }, - })) + .map_err(LockErrorKind::InvalidUrl)?; + + 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, + }, + })) + } } } } @@ -4117,25 +4184,46 @@ 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)?; - 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, - }) + .map_err(|()| LockErrorKind::UrlToPath { url: path.to_url() })?; + let wheel_url = wheel.file.url.to_url().map_err(LockErrorKind::InvalidUrl)?; + + 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, + }) + } } } } @@ -4173,8 +4261,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, @@ -4190,7 +4280,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( @@ -4203,9 +4293,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, @@ -4213,9 +4315,6 @@ impl Wheel { .into()); } }; - let file_url = - DisplaySafeUrl::from_file_path(root.join(index_path).join(file_path)) - .map_err(|()| LockErrorKind::PathToUrl)?; let file = Box::new(uv_distribution_types::File { dist_info_metadata: false, filename: SmallString::from(filename.to_string()), @@ -4223,7 +4322,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( @@ -4597,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()?)), } } @@ -5222,8 +5321,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( @@ -5421,11 +5525,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())] 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/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-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. 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-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-static/src/env_vars.rs b/crates/uv-static/src/env_vars.rs index 8193b6aec..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"; @@ -667,6 +663,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-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-types/Cargo.toml b/crates/uv-types/Cargo.toml index 0973a5218..f29af4ca4 100644 --- a/crates/uv-types/Cargo.toml +++ b/crates/uv-types/Cargo.toml @@ -31,6 +31,7 @@ uv-redacted = { workspace = true } uv-workspace = { workspace = true } anyhow = { workspace = true } +dashmap = { workspace = true } rustc-hash = { workspace = true } thiserror = { workspace = true } diff --git a/crates/uv-types/src/builds.rs b/crates/uv-types/src/builds.rs index ea5e0b6a3..e8c622057 100644 --- a/crates/uv-types/src/builds.rs +++ b/crates/uv-types/src/builds.rs @@ -1,3 +1,9 @@ +use std::path::Path; +use std::sync::Arc; + +use dashmap::DashMap; + +use uv_configuration::{BuildKind, SourceStrategy}; use uv_pep508::PackageName; use uv_python::PythonEnvironment; @@ -37,3 +43,42 @@ impl BuildIsolation<'_> { } } } + +/// 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, +} + +/// 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 6f724b27a..a95367fef 100644 --- a/crates/uv-types/src/traits.rs +++ b/crates/uv-types/src/traits.rs @@ -18,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 @@ -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; @@ -180,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 diff --git a/crates/uv-version/Cargo.toml b/crates/uv-version/Cargo.toml index b92014be4..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.14" +version = "0.7.19" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } 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-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-workspace/src/pyproject.rs b/crates/uv-workspace/src/pyproject.rs index 6499aad5d..124a62881 100644 --- a/crates/uv-workspace/src/pyproject.rs +++ b/crates/uv-workspace/src/pyproject.rs @@ -6,6 +6,8 @@ //! //! Then lowers them into a dependency specification. +#[cfg(feature = "schemars")] +use std::borrow::Cow; use std::collections::BTreeMap; use std::fmt::Formatter; use std::ops::Deref; @@ -23,6 +25,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::{ @@ -609,7 +612,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)] @@ -813,12 +816,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) } } @@ -1683,3 +1686,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/Cargo.toml b/crates/uv/Cargo.toml index e63cbfe40..0a352d2b1 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.19" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } @@ -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/src/commands/build_frontend.rs b/crates/uv/src/commands/build_frontend.rs index 9b97b40b1..2cef9a406 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}; @@ -188,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, @@ -504,16 +494,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 = @@ -615,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/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs index db80c2a8a..a1846d418 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; @@ -338,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) @@ -388,16 +386,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..aa6e6a6c9 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -1,12 +1,11 @@ use std::collections::{BTreeMap, BTreeSet}; use std::fmt::Write; use std::path::PathBuf; -use std::sync::Arc; 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}; @@ -237,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(); @@ -334,16 +339,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..8f26aaea2 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -1,10 +1,9 @@ use std::collections::{BTreeMap, BTreeSet}; use std::fmt::Write; -use std::sync::Arc; 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}; @@ -212,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(); @@ -267,16 +272,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/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 ae20b31d4..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) @@ -374,16 +380,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)? @@ -853,6 +850,7 @@ async fn lock_and_sync( Box::new(DefaultResolveLogger), concurrency, cache, + &WorkspaceCache::default(), printer, preview, ) @@ -974,6 +972,7 @@ async fn lock_and_sync( Box::new(SummaryResolveLogger), concurrency, cache, + &WorkspaceCache::default(), printer, preview, ) @@ -1021,6 +1020,7 @@ async fn lock_and_sync( installer_metadata, concurrency, cache, + WorkspaceCache::default(), DryRun::Disabled, printer, preview, diff --git a/crates/uv/src/commands/project/environment.rs b/crates/uv/src/commands/project/environment.rs index da3dc7f63..f43587ff0 100644 --- a/crates/uv/src/commands/project/environment.rs +++ b/crates/uv/src/commands/project/environment.rs @@ -44,13 +44,16 @@ 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, state, @@ -72,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()) { @@ -92,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/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..cd4242833 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 { @@ -586,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() { @@ -654,8 +652,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 +670,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/mod.rs b/crates/uv/src/commands/project/mod.rs index a3249b11a..c327e8a44 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)), @@ -769,7 +793,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 +809,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, )); } @@ -1220,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 @@ -1438,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()); @@ -1626,16 +1661,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)? @@ -1746,6 +1772,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, @@ -1796,16 +1823,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)? @@ -1842,7 +1860,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 @@ -1978,16 +1995,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 +2201,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/remove.rs b/crates/uv/src/commands/project/remove.rs index 7fd02277e..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 { @@ -302,6 +308,7 @@ pub(crate) async fn remove( Box::new(DefaultResolveLogger), concurrency, cache, + &WorkspaceCache::default(), printer, preview, ) @@ -357,6 +364,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..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 { @@ -264,6 +270,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 +316,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, @@ -384,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, @@ -407,7 +421,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 +479,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. @@ -698,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 { @@ -721,6 +740,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 +827,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..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 { @@ -324,7 +331,7 @@ pub(crate) async fn sync( installer_metadata, concurrency, cache, - workspace_cache, + workspace_cache.clone(), dry_run, printer, preview, @@ -371,6 +378,7 @@ pub(crate) async fn sync( Box::new(DefaultResolveLogger), concurrency, cache, + &workspace_cache, printer, preview, ) @@ -452,6 +460,7 @@ pub(crate) async fn sync( installer_metadata, concurrency, cache, + workspace_cache, dry_run, printer, preview, @@ -587,6 +596,7 @@ pub(super) async fn do_sync( installer_metadata: bool, concurrency: Concurrency, cache: &Cache, + workspace_cache: WorkspaceCache, dry_run: DryRun, printer: Printer, preview: PreviewMode, @@ -679,16 +689,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); @@ -748,7 +749,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..bc79f8eb9 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, ) @@ -384,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. @@ -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, diff --git a/crates/uv/src/commands/python/install.rs b/crates/uv/src/commands/python/install.rs index 7ad96fffe..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))); @@ -451,7 +464,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( @@ -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" 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/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/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 { 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()); diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 5cbeb1886..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 @@ -1386,6 +1394,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 @@ -1431,7 +1445,7 @@ impl AddSettings { Self { locked, frozen, - active: flag(active, no_active), + active: flag(active, no_active, "active"), no_sync, packages, requirements, @@ -1449,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, @@ -1525,7 +1539,7 @@ impl RemoveSettings { Self { locked, frozen, - active: flag(active, no_active), + active: flag(active, no_active, "active"), no_sync, packages, dependency_type, @@ -1597,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), @@ -1773,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, @@ -1786,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, @@ -1795,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), @@ -1949,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) @@ -2044,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) }, @@ -2193,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 @@ -2202,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) }, @@ -2261,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, @@ -2302,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, @@ -2342,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, @@ -2387,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, @@ -2436,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, @@ -2465,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, @@ -2532,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), @@ -2599,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/build.rs b/crates/uv/tests/it/build.rs index 706c1a681..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: 1", "exit status: 1"), (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"); @@ -1981,3 +1978,60 @@ 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` + "); + + uv_snapshot!(context.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(()) +} diff --git a/crates/uv/tests/it/build_backend.rs b/crates/uv/tests/it/build_backend.rs index c2d99ba3e..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") @@ -768,3 +760,129 @@ 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("--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(()) +} + +/// 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(()) +} diff --git a/crates/uv/tests/it/common/mod.rs b/crates/uv/tests/it/common/mod.rs index 4d65aa4a3..7b13c49b5 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; @@ -67,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. @@ -255,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(), ), ( @@ -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. @@ -523,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/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/edit.rs b/crates/uv/tests/it/edit.rs index d117b1e8c..0ae2a07a6 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<()> { @@ -4292,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" @@ -4302,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" + "# ); }); @@ -4321,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" }, @@ -7164,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 ----- @@ -7269,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 ----- @@ -9183,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" @@ -9196,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" + "# ); }); @@ -9221,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" }, @@ -9364,7 +9440,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 ----- @@ -9393,7 +9469,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 ----- @@ -9423,7 +9499,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 ----- @@ -9446,12 +9522,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 ----- @@ -9467,6 +9547,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<()> { @@ -11074,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" @@ -11084,8 +11204,8 @@ fn repeated_index_cli_reversed() -> Result<()> { ] [[tool.uv.index]] - url = "https://test.pypi.org/simple/" - "### + url = "https://test.pypi.org/simple" + "# ); }); @@ -11106,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" }, @@ -11892,6 +12012,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<()> { @@ -12019,14 +12194,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 diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index ac20124a0..5851022b8 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 `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#" + [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<()> { @@ -4949,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(); @@ -15479,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" }, @@ -15492,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" }, @@ -15512,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" }, @@ -23553,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###" @@ -23613,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###" @@ -23675,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###" @@ -23748,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###" @@ -27458,7 +27510,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'", @@ -27535,7 +27587,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'", @@ -28257,3 +28309,438 @@ 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" + + [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_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" + 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(()) +} + +#[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(()) +} + +/// 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(()) +} 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/pip_compile.rs b/crates/uv/tests/it/pip_compile.rs index 49e78a5c9..b99be1296 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; @@ -2910,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(()) @@ -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 @@ -12806,28 +12714,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(()) @@ -14765,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###" @@ -16431,7 +16342,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" @@ -16480,7 +16391,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" @@ -16564,7 +16475,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" @@ -16635,7 +16546,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" @@ -16685,7 +16596,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" @@ -16749,7 +16660,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" @@ -16818,7 +16729,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" @@ -16856,7 +16767,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" @@ -16897,7 +16808,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" @@ -16936,7 +16847,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" @@ -16973,7 +16884,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" @@ -17014,7 +16925,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" @@ -17054,7 +16965,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" @@ -17093,7 +17004,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" @@ -17141,7 +17052,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" @@ -17354,7 +17265,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" @@ -17615,3 +17526,80 @@ 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(()) +} + +/// 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(()) +} diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index e0876b23c..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 @@ -1515,16 +1512,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(()) @@ -2066,6 +2063,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")] @@ -2286,7 +2341,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); @@ -4872,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###" @@ -4943,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###" @@ -8543,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###" @@ -8873,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 ----- @@ -8926,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() @@ -11428,3 +11468,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) + " + ); +} 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/crates/uv/tests/it/python_install.rs b/crates/uv/tests/it/python_install.rs index 2b6f03d4b..7fd596cd8 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" ); }); } @@ -1367,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 @@ -1388,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 ----- "); @@ -1398,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 ----- "); @@ -1407,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 ----- "); diff --git a/crates/uv/tests/it/run.rs b/crates/uv/tests/it/run.rs index 65d13c527..98c2adbfe 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#" @@ -4686,6 +4685,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" @@ -4762,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"} @@ -4861,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#" @@ -4908,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(()) } @@ -4964,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!( diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index e6fe831d3..f9a71fe82 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"); @@ -355,6 +356,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 +501,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"]); @@ -1119,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 ----- @@ -1212,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 ----- @@ -1236,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 ----- @@ -6982,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###" @@ -7048,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###" @@ -7116,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###" @@ -8171,6 +8159,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!( @@ -8179,6 +8169,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(()) } @@ -9936,10 +9939,90 @@ 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 "); 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(()) +} + +#[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(()) +} diff --git a/crates/uv/tests/it/tool_install.rs b/crates/uv/tests/it/tool_install.rs index 88e73406f..6a2d38db8 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` @@ -449,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] @@ -495,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 @@ -822,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] @@ -840,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() @@ -1650,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 @@ -1683,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"#+", "#"), 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` 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<()> { diff --git a/crates/uv/tests/it/version.rs b/crates/uv/tests/it/version.rs index 1fda42705..97d30f4f4 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]""#, @@ -1952,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(()) +} 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(()) diff --git a/docs/concepts/build-backend.md b/docs/concepts/build-backend.md index 2162aaaa5..e68069ddb 100644 --- a/docs/concepts/build-backend.md +++ b/docs/concepts/build-backend.md @@ -2,103 +2,205 @@ !!! 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. -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 +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. + +## Choosing a build backend + +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 [library with extension modules](../concepts/projects/init.md#projects-with-extension-modules). -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`: +!!! tip -```toml + 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 + +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.14,<0.8.0"] +requires = ["uv_build>=0.7.19,<0.8.0"] build-backend = "uv_build" ``` +!!! note + + 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. + +To create a new project that uses the uv build backend, use `uv init`: + +```console +$ uv init --build-backend uv +``` + +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 + +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`. + +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 = "FOO" +module-root = "" +``` + +## Namespace packages + +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 +``` + +And the `module-name` configuration would be: + +```toml title="pyproject.toml" +[tool.uv.build-backend] +module-name = "foo.bar" +``` + !!! important - 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 `__init__.py` file is not included in `foo`, since it's the shared namespace module. -You can also create a new project that uses the uv build backend with `uv init`: +It's also possible to have a complex namespace package with more than one root module, e.g., with +the project structure: -```shell -uv init --build-backend uv +```text +pyproject.toml +src +├── foo +│   └── __init__.py +└── bar + └── __init__.py ``` -`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. +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: -## 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: - -```toml -[tool.uv.build-backend] -module-name = "PIL" -module-root = "" -``` - -For a namespace packages, the path can be dotted. The example below expects to find a -`src/cloud/db/schema/__init__.py`: - -```toml -[tool.uv.build-backend] -module-name = "cloud.db.schema" -``` - -Complex namespaces with more than one root module can be built by setting the `namespace` option, -which allows more than one root `__init__.py`: - -```toml +```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 +208,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 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 diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index a87ce695a..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.14/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.14/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/alternative-indexes.md b/docs/guides/integration/alternative-indexes.md index 52ec6e365..3e73efff0 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 ``` @@ -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. diff --git a/docs/guides/integration/aws-lambda.md b/docs/guides/integration/aws-lambda.md index 467088736..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.14 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.14 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 01e66f775..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.14` +- `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.14-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.14 /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.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.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.14`, 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 0bd1cc1b1..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.14" + version: "0.7.19" ``` ## Setting up Python diff --git a/docs/guides/integration/pre-commit.md b/docs/guides/integration/pre-commit.md index 326832e7e..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.14 + 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.14 + 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.14 + 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.14 + 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.14 + rev: 0.7.19 hooks: # Compile requirements - id: pip-compile 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..1356cb5d7 --- /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 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), +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/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 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:

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/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 diff --git a/pyproject.toml b/pyproject.toml index 86dd0a1e2..5d4261360 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "uv" -version = "0.7.14" +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" 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" 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" diff --git a/uv.schema.json b/uv.schema.json index 0b9bd6f15..dbc4f1168 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\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", + "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 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" + "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]` 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" + "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 }, "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" }, "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