diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml index 2560c5dde29f..054c5e3cfacd 100644 --- a/.github/workflows/build-binaries.yml +++ b/.github/workflows/build-binaries.yml @@ -156,7 +156,7 @@ jobs: windows: if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }} - runs-on: windows-latest-large + runs-on: github-windows-2022-x86_64-8 strategy: matrix: platform: diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 9ef0052342ff..843d80e4b2df 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -37,6 +37,12 @@ jobs: with: submodules: recursive + # Login to DockerHub first, to avoid rate-limiting + - uses: docker/login-action@v3 + with: + username: astralshbot + password: ${{ secrets.DOCKERHUB_TOKEN_RO }} + - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 @@ -108,6 +114,12 @@ jobs: - docker-build if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }} steps: + # Login to DockerHub first, to avoid rate-limiting + - uses: docker/login-action@v3 + with: + username: astralshbot + password: ${{ secrets.DOCKERHUB_TOKEN_RO }} + - name: Download digests uses: actions/download-artifact@v4 with: @@ -180,6 +192,12 @@ jobs: - python:3.9-slim-bookworm,python3.9-bookworm-slim - python:3.8-slim-bookworm,python3.8-bookworm-slim steps: + # Login to DockerHub first, to avoid rate-limiting + - uses: docker/login-action@v3 + with: + username: astralshbot + password: ${{ secrets.DOCKERHUB_TOKEN_RO }} + - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 @@ -266,6 +284,12 @@ jobs: - docker-publish-extra if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }} steps: + # Login to DockerHub first, to avoid rate-limiting + - uses: docker/login-action@v3 + with: + username: astralshbot + password: ${{ secrets.DOCKERHUB_TOKEN_RO }} + - name: Download digests uses: actions/download-artifact@v4 with: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0f2248890df4..fce3bceaa495 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -116,8 +116,7 @@ jobs: timeout-minutes: 15 needs: determine_changes if: ${{ github.repository == 'astral-sh/uv' && !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }} - runs-on: - labels: "windows-latest-xlarge" + runs-on: github-windows-2025-x86_64-16 name: "cargo clippy | windows" steps: - uses: actions/checkout@v4 @@ -173,8 +172,7 @@ jobs: timeout-minutes: 10 needs: determine_changes if: ${{ github.repository == 'astral-sh/uv' && !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }} - runs-on: - labels: "depot-ubuntu-22.04-16" + runs-on: depot-ubuntu-22.04-16 name: "cargo test | ubuntu" steps: - uses: actions/checkout@v4 @@ -219,8 +217,7 @@ jobs: timeout-minutes: 10 needs: determine_changes if: ${{ github.repository == 'astral-sh/uv' && !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }} - runs-on: - labels: "macos-latest-xlarge" + runs-on: macos-latest-xlarge # github-macos-14-aarch64-6 name: "cargo test | macos" steps: - uses: actions/checkout@v4 @@ -258,8 +255,7 @@ jobs: timeout-minutes: 15 needs: determine_changes if: ${{ github.repository == 'astral-sh/uv' && !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }} - runs-on: - labels: "windows-latest-xlarge" + runs-on: github-windows-2025-x86_64-16 name: "cargo test | windows" steps: - uses: actions/checkout@v4 @@ -334,7 +330,7 @@ jobs: timeout-minutes: 15 needs: determine_changes if: ${{ github.repository == 'astral-sh/uv' && !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }} - runs-on: windows-latest-xlarge + runs-on: github-windows-2025-x86_64-16 name: "check windows trampoline | ${{ matrix.target-arch }}" strategy: fail-fast: false @@ -456,8 +452,7 @@ jobs: timeout-minutes: 10 needs: determine_changes if: ${{ github.repository == 'astral-sh/uv' && !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }} - runs-on: - labels: ubuntu-latest-large + runs-on: github-ubuntu-24.04-x86_64-8 name: "build binary | linux" steps: - uses: actions/checkout@v4 @@ -484,8 +479,7 @@ jobs: timeout-minutes: 10 needs: determine_changes if: ${{ github.repository == 'astral-sh/uv' && !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }} - runs-on: - labels: macos-14 + runs-on: macos-14 # github-macos-14-aarch64-3 name: "build binary | macos aarch64" steps: - uses: actions/checkout@v4 @@ -507,8 +501,7 @@ jobs: timeout-minutes: 10 needs: determine_changes if: ${{ github.repository == 'astral-sh/uv' && !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }} - runs-on: - labels: macos-latest-large # Intel runner on GitHub + runs-on: macos-latest-large # github-macos-14-x86_64-12 name: "build binary | macos x86_64" steps: - uses: actions/checkout@v4 @@ -530,8 +523,7 @@ jobs: needs: determine_changes timeout-minutes: 10 if: ${{ github.repository == 'astral-sh/uv' && !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }} - runs-on: - labels: windows-latest-large + runs-on: github-windows-2025-x86_64-8 name: "build binary | windows" steps: - uses: actions/checkout@v4 @@ -563,8 +555,7 @@ jobs: name: "cargo build (msrv)" needs: determine_changes if: ${{ github.repository == 'astral-sh/uv' && !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }} - runs-on: - labels: ubuntu-latest-large + runs-on: github-ubuntu-24.04-x86_64-8 timeout-minutes: 10 steps: - uses: actions/checkout@v4 @@ -585,8 +576,7 @@ jobs: needs: determine_changes timeout-minutes: 10 if: ${{ github.repository == 'astral-sh/uv' && !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }} - runs-on: - labels: ubuntu-latest + runs-on: ubuntu-latest name: "build binary | freebsd" steps: @@ -1220,7 +1210,7 @@ jobs: timeout-minutes: 10 needs: build-binary-macos-aarch64 name: "check cache | macos aarch64" - runs-on: macos-14 + runs-on: macos-14 # github-macos-14-aarch64-3 steps: - uses: actions/checkout@v4 @@ -1468,7 +1458,7 @@ jobs: timeout-minutes: 10 needs: build-binary-macos-aarch64 name: "check system | python on macos aarch64" - runs-on: macos-14 + runs-on: macos-14 # github-macos-14-aarch64-3 steps: - uses: actions/checkout@v4 @@ -1492,7 +1482,7 @@ jobs: timeout-minutes: 10 needs: build-binary-macos-aarch64 name: "check system | homebrew python on macos aarch64" - runs-on: macos-14 + runs-on: macos-14 # github-macos-14-aarch64-3 steps: - uses: actions/checkout@v4 @@ -1517,7 +1507,7 @@ jobs: timeout-minutes: 10 needs: build-binary-macos-x86_64 name: "check system | python on macos x86_64" - runs-on: macos-13 + runs-on: macos-13 # github-macos-13-x86_64-4 steps: - uses: actions/checkout@v4 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e649da9743b3..ebd051fec2f1 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.28.4 + rev: v1.29.4 hooks: - id: typos @@ -42,7 +42,7 @@ repos: types_or: [yaml, json5] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.8.4 + rev: v0.9.1 hooks: - id: ruff-format - id: ruff diff --git a/CHANGELOG.md b/CHANGELOG.md index 23b3cfbac2ba..f1ca6fca8fb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,160 @@ # Changelog +## 0.5.18 + +### Bug fixes + +- Avoid forking for identical markers ([#10490](https://github.com/astral-sh/uv/pull/10490)) +- Avoid panic in `uv remove` when only comments exist ([#10484](https://github.com/astral-sh/uv/pull/10484)) +- Revert "improve shell compatibility of venv activate scripts (#10397)" ([#10497](https://github.com/astral-sh/uv/pull/10497)) + +## 0.5.17 + +This release includes support for generating lockfiles from scripts based on inline metadata, as defined in PEP 723. + +By default, scripts remain unlocked, and must be locked explicitly with `uv lock --script /path/to/script.py`, which +will generate a lockfile adjacent to the script (e.g., `script.py.lock`). Once generated, the lockfile will be +respected (and updated, if necessary) across `uv run --script`, `uv add --script`, and `uv remove --script` invocations. + +This release also includes support for `uv export --script` and `uv tree --script`. Both commands support PEP 723 +scripts with and without accompanying lockfiles. + +### Enhancements + +- Add support for locking PEP 723 scripts ([#10135](https://github.com/astral-sh/uv/pull/10135)) +- Respect PEP 723 script lockfiles in `uv run` ([#10136](https://github.com/astral-sh/uv/pull/10136)) +- Update PEP 723 lockfile in `uv add --script` ([#10145](https://github.com/astral-sh/uv/pull/10145)) +- Update PEP 723 lockfile in `uv remove --script` ([#10162](https://github.com/astral-sh/uv/pull/10162)) +- Add `--script` support to `uv export` for PEP 723 scripts ([#10160](https://github.com/astral-sh/uv/pull/10160)) +- Add `--script` support to `uv tree` for PEP 723 scripts ([#10159](https://github.com/astral-sh/uv/pull/10159)) +- Add `ls` alias to `uv {tool, python, pip} list` ([#10240](https://github.com/astral-sh/uv/pull/10240)) +- Allow reading `--with-requirements` from stdin in `uv add` and `uv run` ([#10447](https://github.com/astral-sh/uv/pull/10447)) +- Warn-and-ignore for unsupported `requirements.txt` options ([#10420](https://github.com/astral-sh/uv/pull/10420)) + +### Preview features + +- Add remaining Python type annotations to build backend ([#10434](https://github.com/astral-sh/uv/pull/10434)) + +### Performance + +- Avoid allocating for names in the PEP 508 parser ([#10476](https://github.com/astral-sh/uv/pull/10476)) +- Fetch concurrently for non-first-match index strategies ([#10432](https://github.com/astral-sh/uv/pull/10432)) +- Remove unnecessary `.to_string()` call ([#10419](https://github.com/astral-sh/uv/pull/10419)) +- Respect sentinels in package prioritization ([#10443](https://github.com/astral-sh/uv/pull/10443)) +- Use `ArcStr` for marker values ([#10453](https://github.com/astral-sh/uv/pull/10453)) +- Use `ArcStr` for package, extra, and group names ([#10475](https://github.com/astral-sh/uv/pull/10475)) +- Use `matches!` rather than `contains` in `requirements.txt` parsing ([#10423](https://github.com/astral-sh/uv/pull/10423)) +- Use faster disjointness check for markers ([#10439](https://github.com/astral-sh/uv/pull/10439)) +- Pre-compute PEP 508 markers from universal markers ([#10472](https://github.com/astral-sh/uv/pull/10472)) + +### Bug fixes + +- Fix `UV_FIND_LINKS` delimiter to split on commas ([#10477](https://github.com/astral-sh/uv/pull/10477)) +- Improve `uv tool list` output when tool environment is broken ([#10409](https://github.com/astral-sh/uv/pull/10409)) +- Only track markers for compatible versions ([#10457](https://github.com/astral-sh/uv/pull/10457)) +- Respect `requires-python` when installing tools ([#10401](https://github.com/astral-sh/uv/pull/10401)) +- Visit proxy packages eagerly ([#10441](https://github.com/astral-sh/uv/pull/10441)) +- Improve shell compatibility of `venv` activate scripts ([#10397](https://github.com/astral-sh/uv/pull/10397)) +- Read publish username from URL ([#10469](https://github.com/astral-sh/uv/pull/10469)) + +### Documentation + +- Add Lambda layer instructions to AWS Lambda guide ([#10411](https://github.com/astral-sh/uv/pull/10411)) +- Add `uv lock --script` to the docs ([#10414](https://github.com/astral-sh/uv/pull/10414)) +- Use Windows-specific instructions in Jupyter guide ([#10446](https://github.com/astral-sh/uv/pull/10446)) + +## 0.5.16 + +### Enhancements + +- Accept full requirements in `uv remove` ([#10338](https://github.com/astral-sh/uv/pull/10338)) + +### Performance + +- Avoid over-counting versions in batch prefetcher ([#10350](https://github.com/astral-sh/uv/pull/10350)) +- Deactivate tracing for version-choosing ([#10351](https://github.com/astral-sh/uv/pull/10351)) +- Force a niche into `VersionSmall` ([#10385](https://github.com/astral-sh/uv/pull/10385)) +- Optimize `requirements_for_extra` ([#10348](https://github.com/astral-sh/uv/pull/10348)) +- Re-enable `zlib-ng` on x86 platforms ([#10365](https://github.com/astral-sh/uv/pull/10365)) +- Re-enable zlib-ng on all platforms (except s390x, PowerPC, and FreeBSD) ([#10370](https://github.com/astral-sh/uv/pull/10370)) +- Remove `[u64; 4]` from small version to move `Arc` to full version ([#10345](https://github.com/astral-sh/uv/pull/10345)) +- Shrink `Dist` from 352 to 288 bytes ([#10389](https://github.com/astral-sh/uv/pull/10389)) +- Speed up file pins by removing nested hash map ([#10346](https://github.com/astral-sh/uv/pull/10346)) +- Buffer file reads in `serde_json::from_reader` ([#10341](https://github.com/astral-sh/uv/pull/10341)) + +### Bug fixes + +- Avoid enforcing project-level required version for `uv self` ([#10374](https://github.com/astral-sh/uv/pull/10374)) +- Fix Ruff linting warnings from generated template files for extension modules ([#10371](https://github.com/astral-sh/uv/pull/10371)) + +### Documentation + +- Add AWS Lambda integration guide ([#10278](https://github.com/astral-sh/uv/pull/10278)) + +## 0.5.15 + +### Python + +The managed Python distributions have been updated, including: + +- Python 3.14.0a3 support on macOS and Linux +- Performance improvements +- Fixes to SQLite feature detection + +See the [`python-build-standalone` release notes](https://github.com/astral-sh/python-build-standalone/releases/tag/20250106) for more details. + +### Enhancements + +- Respect `FORCE_COLOR` environment variable ([#10315](https://github.com/astral-sh/uv/pull/10315)) + +### Performance + +- Avoid generating unused hashes during `uv lock` ([#10307](https://github.com/astral-sh/uv/pull/10307)) +- Visit source distributions before wheels ([#10291](https://github.com/astral-sh/uv/pull/10291)) + +### Bug fixes + +- Avoid downgrading packages when `--upgrade` is provided ([#10097](https://github.com/astral-sh/uv/pull/10097)) +- Extract supported architectures from wheel tags ([#10179](https://github.com/astral-sh/uv/pull/10179)) +- Redact new index credentials in `uv add` ([#10329](https://github.com/astral-sh/uv/pull/10329)) + +### Documentation + +- Clarify `exclude-newer` only allows full timestamps in settings documentation ([#9135](https://github.com/astral-sh/uv/pull/9135)) +- Tweak script `--no-project` comment ([#10331](https://github.com/astral-sh/uv/pull/10331)) +- Update copyright year ([#10297](https://github.com/astral-sh/uv/pull/10297)) +- Add instructinos for installing with Scoop ([#10332](https://github.com/astral-sh/uv/pull/10332)) + +## 0.5.14 + +### Enhancements + +- Add `--exact` flag to `uv run` ([#10198](https://github.com/astral-sh/uv/pull/10198)) +- Add `--outdated` support to `uv pip tree` ([#10199](https://github.com/astral-sh/uv/pull/10199)) +- Add a required version setting to uv ([#10248](https://github.com/astral-sh/uv/pull/10248)) +- Add loongarch64 to supported Python platform tags ([#10223](https://github.com/astral-sh/uv/pull/10223)) +- Add manylinux2014 aliases for `--python-platform` ([#10217](https://github.com/astral-sh/uv/pull/10217)) +- Add support for Python interpreters on ARMv5TE platforms ([#10234](https://github.com/astral-sh/uv/pull/10234)) +- Add support for optional `--description` in `uv init` ([#10209](https://github.com/astral-sh/uv/pull/10209)) +- Ignore empty or missing hrefs in Simple HTML ([#10276](https://github.com/astral-sh/uv/pull/10276)) +- Patch pkgconfig files after Python install ([#10189](https://github.com/astral-sh/uv/pull/10189)) + +### Performance + +- Actually use jemalloc as alternative allocator ([#10269](https://github.com/astral-sh/uv/pull/10269)) +- Parse URLs lazily in resolver ([#10259](https://github.com/astral-sh/uv/pull/10259)) +- Use `BTreeMap::range` to avoid iterating over unnecessary versions ([#10266](https://github.com/astral-sh/uv/pull/10266)) + +### Bug fixes + +- Accept directories with space names in `uv init` ([#10246](https://github.com/astral-sh/uv/pull/10246)) +- Avoid forking on version in non-universal resolutions ([#10274](https://github.com/astral-sh/uv/pull/10274)) +- Avoid stripping query parameters from URLs ([#10253](https://github.com/astral-sh/uv/pull/10253)) +- Consider workspace dependencies to be 'direct' ([#10197](https://github.com/astral-sh/uv/pull/10197)) +- Detect cyclic dependencies during builds ([#10258](https://github.com/astral-sh/uv/pull/10258)) +- Guard against self-deletion in `uv venv` and `uv tool` ([#10206](https://github.com/astral-sh/uv/pull/10206)) +- Respect static metadata for already-installed distributions ([#10242](https://github.com/astral-sh/uv/pull/10242)) + ## 0.5.13 ### Bug fixes diff --git a/Cargo.lock b/Cargo.lock index 6c4dffcb2598..62c4e6c20591 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -93,6 +93,12 @@ version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +[[package]] +name = "arcstr" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03918c3dbd7701a85c6b9887732e2921175f26c350b4563841d0958c21d57e6d" + [[package]] name = "arrayref" version = "0.3.9" @@ -178,9 +184,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.83" +version = "0.1.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056" dependencies = [ "proc-macro2", "quote", @@ -247,7 +253,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "thiserror 2.0.9", + "thiserror 2.0.11", "url", "walkdir", ] @@ -289,22 +295,19 @@ dependencies = [ "self-replace", "serde", "tempfile", - "thiserror 2.0.9", + "thiserror 2.0.11", "tokio", "url", ] [[package]] -name = "backoff" -version = "0.4.0" +name = "backon" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" +checksum = "ba5289ec98f68f28dd809fd601059e6aa908bb8f6108620930828283d4ee23d7" dependencies = [ - "futures-core", - "getrandom", - "instant", - "pin-project-lite", - "rand", + "fastrand", + "gloo-timers", "tokio", ] @@ -349,9 +352,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be" [[package]] name = "block-buffer" @@ -473,9 +476,9 @@ dependencies = [ [[package]] name = "cargo-util" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b15bbe49616ee353fadadf6de5a24136f3fe8fdbd5eb0894be9f8a42c905674" +checksum = "7cccd15f96a29696e13e1d5fa10dd1dbed2e172f58b6e6124a9a4fa695363fdd" dependencies = [ "anyhow", "core-foundation", @@ -562,9 +565,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.23" +version = "4.5.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" +checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783" dependencies = [ "clap_builder", "clap_derive", @@ -572,9 +575,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.23" +version = "4.5.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" +checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121" dependencies = [ "anstream", "anstyle", @@ -615,9 +618,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.18" +version = "4.5.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" dependencies = [ "heck", "proc-macro2", @@ -631,6 +634,15 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +[[package]] +name = "cmake" +version = "0.1.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c682c223677e0e5b6b7f63a64b9351844c3f1b1678a68b7ee617e30fb082620e" +dependencies = [ + "cc", +] + [[package]] name = "codspeed" version = "2.7.2" @@ -1090,9 +1102,9 @@ dependencies = [ [[package]] name = "fixedbitset" -version = "0.4.2" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flate2" @@ -1101,6 +1113,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", + "libz-ng-sys", "libz-rs-sys", "miniz_oxide", ] @@ -1357,16 +1370,28 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.7.0", "ignore", "walkdir", ] +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "goblin" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53ab3f32d1d77146981dea5d6b1e8fe31eedcb7013e5e00d6ccd1259a4b4d923" +checksum = "daa0a64d21a7eb230583b4c5f4e23b7e4e57974f96620f42a7e75e08ae66d745" dependencies = [ "log", "plain", @@ -1788,13 +1813,13 @@ checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" [[package]] name = "insta" -version = "1.41.1" +version = "1.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e9ffc4d4892617c50a928c52b2961cb5174b6fc6ebf252b2fac9d21955c48b8" +checksum = "6513e4067e16e69ed1db5ab56048ed65db32d10ba5fc1217f5393f8f17d8b5a5" dependencies = [ "console", - "lazy_static", "linked-hash-map", + "once_cell", "pest", "pest_derive", "regex", @@ -1861,6 +1886,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.14" @@ -1869,11 +1903,14 @@ checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jiff" -version = "0.1.16" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a46169c7a10358cdccfb179910e8a5a392fc291bdb409da9aeece5b19786d8" +checksum = "5c258647f65892e500c2478ef2c71ba008e7dc1774a8289345adbbb502a4def1" dependencies = [ "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", "serde", "windows-sys 0.59.0", ] @@ -1990,11 +2027,21 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.7.0", "libc", "redox_syscall 0.5.8", ] +[[package]] +name = "libz-ng-sys" +version = "1.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4436751a01da56f1277f323c80d584ffad94a3d14aecd959dd0dff75aa73a438" +dependencies = [ + "cmake", + "libc", +] + [[package]] name = "libz-rs-sys" version = "0.4.1" @@ -2233,7 +2280,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.7.0", "cfg-if", "cfg_aliases", "libc", @@ -2428,7 +2475,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" dependencies = [ "memchr", - "thiserror 2.0.9", + "thiserror 2.0.11", "ucd-trie", ] @@ -2468,9 +2515,9 @@ dependencies = [ [[package]] name = "petgraph" -version = "0.6.5" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset", "indexmap", @@ -2570,6 +2617,15 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -2632,9 +2688,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] @@ -2645,7 +2701,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc5b72d8145275d844d4b5f6d4e1eef00c8cd889edb6035c21675d1bb1f45c9f" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.7.0", "flate2", "hex", "procfs-core", @@ -2658,7 +2714,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.7.0", "hex", ] @@ -2691,7 +2747,7 @@ dependencies = [ "log", "priority-queue", "rustc-hash", - "thiserror 2.0.9", + "thiserror 2.0.11", "version-ranges", ] @@ -2708,7 +2764,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2", - "thiserror 2.0.9", + "thiserror 2.0.11", "tokio", "tracing", ] @@ -2727,7 +2783,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.9", + "thiserror 2.0.11", "tinyvec", "tracing", "web-time", @@ -2851,7 +2907,7 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.7.0", ] [[package]] @@ -2931,9 +2987,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.11" +version = "0.12.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe060fe50f524be480214aba758c71f99f90ee8c83c5a36b5e9e1d568eb4eb3" +checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" dependencies = [ "async-compression", "base64 0.22.1", @@ -3173,11 +3229,11 @@ checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" [[package]] name = "rustix" -version = "0.38.42" +version = "0.38.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" +checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.7.0", "errno", "libc", "linux-raw-sys", @@ -3342,7 +3398,7 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81d3f8c9bfcc3cbb6b0179eb57042d75b1582bdc65c3cb95f3fa999509c03cbc" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.7.0", "core-foundation", "core-foundation-sys", "libc", @@ -3420,9 +3476,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.134" +version = "1.0.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" +checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9" dependencies = [ "itoa", "memchr", @@ -3569,9 +3625,9 @@ dependencies = [ [[package]] name = "spdx" -version = "0.10.7" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae30cc7bfe3656d60ee99bf6836f472b0c53dddcbf335e253329abb16e535a2" +checksum = "58b69356da67e2fc1f542c71ea7e654a361a79c938e4424392ecf4fa065d2193" dependencies = [ "smallvec", ] @@ -3668,9 +3724,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.93" +version = "2.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" dependencies = [ "proc-macro2", "quote", @@ -3741,12 +3797,13 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.14.0" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" dependencies = [ "cfg-if", "fastrand", + "getrandom", "once_cell", "rustix", "windows-sys 0.59.0", @@ -3844,11 +3901,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.9" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" dependencies = [ - "thiserror-impl 2.0.9", + "thiserror-impl 2.0.11", ] [[package]] @@ -3864,9 +3921,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.9" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ "proc-macro2", "quote", @@ -3970,9 +4027,9 @@ source = "git+https://github.com/astral-sh/tl.git?rev=6e25b2ee2513d75385101a8ff9 [[package]] name = "tokio" -version = "1.42.0" +version = "1.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" dependencies = [ "backtrace", "bytes", @@ -3988,9 +4045,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", @@ -4422,7 +4479,7 @@ checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" [[package]] name = "uv" -version = "0.5.13" +version = "0.5.18" dependencies = [ "anstream", "anyhow", @@ -4445,7 +4502,7 @@ dependencies = [ "indicatif", "indoc", "insta", - "itertools 0.13.0", + "itertools 0.14.0", "jiff", "miette", "nix", @@ -4463,7 +4520,7 @@ dependencies = [ "tar", "tempfile", "textwrap", - "thiserror 2.0.9", + "thiserror 2.0.11", "tokio", "toml", "toml_edit", @@ -4587,13 +4644,13 @@ dependencies = [ "globset", "indoc", "insta", - "itertools 0.13.0", + "itertools 0.14.0", "serde", "sha2", "spdx", "tar", "tempfile", - "thiserror 2.0.9", + "thiserror 2.0.11", "toml", "tracing", "uv-distribution-filename", @@ -4618,14 +4675,14 @@ dependencies = [ "fs-err 3.0.0", "indoc", "insta", - "itertools 0.13.0", + "itertools 0.14.0", "owo-colors", "regex", "rustc-hash", "serde", "serde_json", "tempfile", - "thiserror 2.0.9", + "thiserror 2.0.11", "tokio", "toml_edit", "tracing", @@ -4674,9 +4731,10 @@ dependencies = [ "globwalk", "schemars", "serde", - "thiserror 2.0.9", + "thiserror 2.0.11", "toml", "tracing", + "walkdir", ] [[package]] @@ -4732,7 +4790,7 @@ dependencies = [ "hyper", "hyper-util", "insta", - "itertools 0.13.0", + "itertools 0.14.0", "jiff", "reqwest", "reqwest-middleware", @@ -4742,7 +4800,7 @@ dependencies = [ "serde", "serde_json", "sys-info", - "thiserror 2.0.9", + "thiserror 2.0.11", "tl", "tokio", "tokio-util", @@ -4781,7 +4839,7 @@ dependencies = [ "serde", "serde-untagged", "serde_json", - "thiserror 2.0.9", + "thiserror 2.0.11", "tracing", "url", "uv-auth", @@ -4789,6 +4847,7 @@ dependencies = [ "uv-cache-info", "uv-cache-key", "uv-normalize", + "uv-pep440", "uv-pep508", "uv-platform-tags", "uv-pypi-types", @@ -4812,7 +4871,7 @@ dependencies = [ "anyhow", "clap", "fs-err 3.0.0", - "itertools 0.13.0", + "itertools 0.14.0", "markdown", "owo-colors", "poloto", @@ -4861,9 +4920,9 @@ version = "0.0.1" dependencies = [ "anyhow", "futures", - "itertools 0.13.0", + "itertools 0.14.0", "rustc-hash", - "thiserror 2.0.9", + "thiserror 2.0.11", "tokio", "tracing", "uv-build-backend", @@ -4903,7 +4962,7 @@ dependencies = [ "rustc-hash", "serde", "tempfile", - "thiserror 2.0.9", + "thiserror 2.0.11", "tokio", "tokio-util", "tracing", @@ -4938,7 +4997,7 @@ dependencies = [ "insta", "rkyv", "serde", - "thiserror 2.0.9", + "thiserror 2.0.11", "url", "uv-normalize", "uv-pep440", @@ -4950,9 +5009,10 @@ name = "uv-distribution-types" version = "0.0.1" dependencies = [ "anyhow", - "bitflags 2.6.0", + "arcstr", + "bitflags 2.7.0", "fs-err 3.0.0", - "itertools 0.13.0", + "itertools 0.14.0", "jiff", "petgraph", "rkyv", @@ -4960,7 +5020,7 @@ dependencies = [ "schemars", "serde", "serde_json", - "thiserror 2.0.9", + "thiserror 2.0.11", "tracing", "url", "urlencoding", @@ -4992,7 +5052,7 @@ dependencies = [ "reqwest", "rustc-hash", "sha2", - "thiserror 2.0.9", + "thiserror 2.0.11", "tokio", "tokio-util", "tracing", @@ -5007,7 +5067,7 @@ dependencies = [ name = "uv-fs" version = "0.0.1" dependencies = [ - "backoff", + "backon", "cachedir", "dunce", "either", @@ -5037,7 +5097,7 @@ dependencies = [ "reqwest", "reqwest-middleware", "serde", - "thiserror 2.0.9", + "thiserror 2.0.11", "tokio", "tracing", "url", @@ -5058,7 +5118,7 @@ dependencies = [ "regex", "regex-automata 0.4.9", "tempfile", - "thiserror 2.0.9", + "thiserror 2.0.11", "tracing", "walkdir", ] @@ -5088,7 +5148,7 @@ dependencies = [ "serde_json", "sha2", "tempfile", - "thiserror 2.0.9", + "thiserror 2.0.11", "tracing", "uv-cache-info", "uv-distribution-filename", @@ -5116,7 +5176,7 @@ dependencies = [ "rustc-hash", "same-file", "tempfile", - "thiserror 2.0.9", + "thiserror 2.0.11", "tokio", "tracing", "url", @@ -5157,7 +5217,7 @@ dependencies = [ "async_zip", "fs-err 3.0.0", "futures", - "thiserror 2.0.9", + "thiserror 2.0.11", "tokio", "tokio-util", "uv-distribution-filename", @@ -5173,6 +5233,7 @@ dependencies = [ "rkyv", "schemars", "serde", + "uv-small-str", ] [[package]] @@ -5208,17 +5269,18 @@ dependencies = [ name = "uv-pep508" version = "0.6.0" dependencies = [ + "arcstr", "boxcar", "indexmap", "insta", - "itertools 0.13.0", + "itertools 0.14.0", "regex", "rustc-hash", "schemars", "serde", "serde_json", "smallvec", - "thiserror 2.0.9", + "thiserror 2.0.11", "tracing", "tracing-test", "unicode-width 0.1.14", @@ -5234,6 +5296,7 @@ name = "uv-performance-flate2-backend" version = "0.1.0" dependencies = [ "flate2", + "libz-ng-sys", ] [[package]] @@ -5251,7 +5314,7 @@ dependencies = [ "insta", "rustc-hash", "serde", - "thiserror 2.0.9", + "thiserror 2.0.11", ] [[package]] @@ -5264,7 +5327,7 @@ dependencies = [ "futures", "glob", "insta", - "itertools 0.13.0", + "itertools 0.14.0", "krata-tokio-tar", "reqwest", "reqwest-middleware", @@ -5272,7 +5335,7 @@ dependencies = [ "rustc-hash", "serde", "serde_json", - "thiserror 2.0.9", + "thiserror 2.0.11", "tokio", "tokio-util", "tracing", @@ -5297,7 +5360,7 @@ dependencies = [ "anyhow", "hashbrown 0.15.2", "indexmap", - "itertools 0.13.0", + "itertools 0.14.0", "jiff", "mailparse", "regex", @@ -5305,7 +5368,7 @@ dependencies = [ "schemars", "serde", "serde-untagged", - "thiserror 2.0.9", + "thiserror 2.0.11", "toml", "toml_edit", "tracing", @@ -5331,7 +5394,7 @@ dependencies = [ "goblin", "indoc", "insta", - "itertools 0.13.0", + "itertools 0.14.0", "owo-colors", "procfs", "regex", @@ -5347,7 +5410,7 @@ dependencies = [ "temp-env", "tempfile", "test-log", - "thiserror 2.0.9", + "thiserror 2.0.11", "tokio", "tokio-util", "tracing", @@ -5386,7 +5449,7 @@ dependencies = [ "futures", "rustc-hash", "serde", - "thiserror 2.0.9", + "thiserror 2.0.11", "toml", "tracing", "url", @@ -5418,13 +5481,13 @@ dependencies = [ "fs-err 3.0.0", "indoc", "insta", - "itertools 0.13.0", + "itertools 0.14.0", "regex", "reqwest", "reqwest-middleware", "tempfile", "test-case", - "thiserror 2.0.9", + "thiserror 2.0.11", "tokio", "tracing", "unscanny", @@ -5436,13 +5499,14 @@ dependencies = [ "uv-normalize", "uv-pep508", "uv-pypi-types", + "uv-warnings", ] [[package]] name = "uv-resolver" version = "0.0.1" dependencies = [ - "anyhow", + "arcstr", "clap", "dashmap", "either", @@ -5450,7 +5514,7 @@ dependencies = [ "hashbrown 0.15.2", "indexmap", "insta", - "itertools 0.13.0", + "itertools 0.14.0", "jiff", "owo-colors", "petgraph", @@ -5461,7 +5525,7 @@ dependencies = [ "schemars", "serde", "textwrap", - "thiserror 2.0.9", + "thiserror 2.0.11", "tokio", "tokio-stream", "toml", @@ -5499,7 +5563,7 @@ dependencies = [ "indoc", "memchr", "serde", - "thiserror 2.0.9", + "thiserror 2.0.11", "toml", "uv-pep440", "uv-pep508", @@ -5520,7 +5584,7 @@ dependencies = [ "schemars", "serde", "textwrap", - "thiserror 2.0.9", + "thiserror 2.0.11", "toml", "tracing", "url", @@ -5553,6 +5617,16 @@ dependencies = [ "winreg", ] +[[package]] +name = "uv-small-str" +version = "0.0.1" +dependencies = [ + "arcstr", + "rkyv", + "schemars", + "serde", +] + [[package]] name = "uv-state" version = "0.0.1" @@ -5577,7 +5651,7 @@ dependencies = [ "pathdiff", "self-replace", "serde", - "thiserror 2.0.9", + "thiserror 2.0.11", "toml", "toml_edit", "tracing", @@ -5604,7 +5678,7 @@ dependencies = [ "assert_cmd", "assert_fs", "fs-err 3.0.0", - "thiserror 2.0.9", + "thiserror 2.0.11", "uv-fs", "which", "zip", @@ -5616,7 +5690,7 @@ version = "0.0.1" dependencies = [ "anyhow", "rustc-hash", - "thiserror 2.0.9", + "thiserror 2.0.11", "url", "uv-cache", "uv-configuration", @@ -5633,17 +5707,17 @@ dependencies = [ [[package]] name = "uv-version" -version = "0.5.13" +version = "0.5.18" [[package]] name = "uv-virtualenv" version = "0.0.4" dependencies = [ "fs-err 3.0.0", - "itertools 0.13.0", + "itertools 0.14.0", "pathdiff", "self-replace", - "thiserror 2.0.9", + "thiserror 2.0.11", "tracing", "uv-fs", "uv-platform-tags", @@ -5671,7 +5745,7 @@ dependencies = [ "fs-err 3.0.0", "glob", "insta", - "itertools 0.13.0", + "itertools 0.14.0", "owo-colors", "regex", "rustc-hash", @@ -5679,7 +5753,7 @@ dependencies = [ "schemars", "serde", "tempfile", - "thiserror 2.0.9", + "thiserror 2.0.11", "tokio", "toml", "toml_edit", @@ -6240,9 +6314,9 @@ dependencies = [ [[package]] name = "winreg" -version = "0.52.0" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +checksum = "89a47b489f8fc5b949477e89dca4d1617f162c6c53fbcbefde553ab17b342ff9" dependencies = [ "cfg-if", "windows-sys 0.48.0", diff --git a/Cargo.toml b/Cargo.toml index 706789b1157e..ad3717a20177 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,10 +59,11 @@ uv-resolver = { path = "crates/uv-resolver" } uv-scripts = { path = "crates/uv-scripts" } uv-settings = { path = "crates/uv-settings" } uv-shell = { path = "crates/uv-shell" } +uv-small-str = { path = "crates/uv-small-str" } uv-state = { path = "crates/uv-state" } uv-static = { path = "crates/uv-static" } -uv-trampoline-builder = { path = "crates/uv-trampoline-builder" } uv-tool = { path = "crates/uv-tool" } +uv-trampoline-builder = { path = "crates/uv-trampoline-builder" } uv-types = { path = "crates/uv-types" } uv-version = { path = "crates/uv-version" } uv-virtualenv = { path = "crates/uv-virtualenv" } @@ -71,13 +72,14 @@ uv-workspace = { path = "crates/uv-workspace" } anstream = { version = "0.6.15" } anyhow = { version = "1.0.89" } +arcstr = { version = "1.2.0" } async-channel = { version = "2.3.1" } async-compression = { version = "0.4.12", features = ["bzip2", "gzip", "xz", "zstd"] } async-trait = { version = "0.1.82" } async_http_range_reader = { version = "0.9.1" } async_zip = { git = "https://github.com/charliermarsh/rs-async-zip", rev = "c909fda63fcafe4af496a07bfda28a5aae97e58d", features = ["deflate", "tokio"] } axoupdater = { version = "0.9.0", default-features = false } -backoff = { version = "0.4.0" } +backon = { version = "1.3.0" } base64 = { version = "0.22.1" } bitflags = { version = "2.6.0" } boxcar = { version = "0.2.5" } @@ -113,7 +115,7 @@ http = { version = "1.1.0" } indexmap = { version = "2.5.0" } indicatif = { version = "0.17.8" } indoc = { version = "2.0.5" } -itertools = { version = "0.13.0" } +itertools = { version = "0.14.0" } jiff = { version = "0.1.14", features = ["serde"] } junction = { version = "1.2.0" } krata-tokio-tar = { version = "0.4.2" } @@ -126,7 +128,7 @@ nix = { version = "0.29.0" } owo-colors = { version = "4.1.0" } path-slash = { version = "0.2.1" } pathdiff = { version = "0.2.1" } -petgraph = { version = "0.6.5" } +petgraph = { version = "0.7.1" } platform-info = { version = "2.0.3" } proc-macro2 = { version = "1.0.86" } procfs = { version = "0.17.0", default-features = false, features = ["flate2"] } @@ -181,7 +183,7 @@ which = { version = "7.0.0", features = ["regex"] } windows-registry = { version = "0.3.0" } windows-result = { version = "0.2.0" } windows-sys = { version = "0.59.0", features = ["Win32_Foundation", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Ioctl", "Win32_System_IO"] } -winreg = { version = "0.52.0" } +winreg = { version = "0.53.0" } winsafe = { version = "0.0.22", features = ["kernel"] } wiremock = { version = "0.6.2" } xz2 = { version = "0.1.7" } @@ -224,6 +226,9 @@ rc_mutex = "warn" rest_pat_in_fully_bound_structs = "warn" if_not_else = "allow" +# Diagnostics are not actionable: Enable once https://github.com/rust-lang/rust-clippy/issues/13774 is resolved. +large_stack_arrays = "allow" + [profile.release] strip = true lto = "fat" diff --git a/LICENSE-APACHE b/LICENSE-APACHE index f49a4e16e68b..261eeb9e9f8b 100644 --- a/LICENSE-APACHE +++ b/LICENSE-APACHE @@ -198,4 +198,4 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file + limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT index ec2236bb8994..014835144877 100644 --- a/LICENSE-MIT +++ b/LICENSE-MIT @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Astral Software Inc. +Copyright (c) 2025 Astral Software Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/crates/uv-auth/src/middleware.rs b/crates/uv-auth/src/middleware.rs index 3c78600d865b..16c4e542b72b 100644 --- a/crates/uv-auth/src/middleware.rs +++ b/crates/uv-auth/src/middleware.rs @@ -741,7 +741,7 @@ mod tests { let mut netrc_file = NamedTempFile::new()?; writeln!( netrc_file, - r#"machine {} login {username} password {password}"#, + r"machine {} login {username} password {password}", base_url.host_str().unwrap() )?; @@ -788,7 +788,7 @@ mod tests { let mut netrc_file = NamedTempFile::new()?; writeln!( netrc_file, - r#"machine example.com login {username} password {password}"#, + r"machine example.com login {username} password {password}", )?; let client = test_client_builder() @@ -829,7 +829,7 @@ mod tests { let mut netrc_file = NamedTempFile::new()?; writeln!( netrc_file, - r#"machine {} login {username} password {password}"#, + r"machine {} login {username} password {password}", base_url.host_str().unwrap() )?; diff --git a/crates/uv-build-frontend/src/error.rs b/crates/uv-build-frontend/src/error.rs index 81fda9d877b5..e8d10d65f26f 100644 --- a/crates/uv-build-frontend/src/error.rs +++ b/crates/uv-build-frontend/src/error.rs @@ -84,10 +84,12 @@ pub enum Error { #[error("Failed to build PATH for build script")] BuildScriptPath(#[source] env::JoinPathsError), // For the convenience of typing `setup_build` properly. - #[error("Building source distributions for {0} is disabled")] + #[error("Building source distributions for `{0}` is disabled")] NoSourceDistBuild(PackageName), #[error("Building source distributions is disabled")] NoSourceDistBuilds, + #[error("Cyclic build dependency detected for `{0}`")] + CyclicBuildDependency(PackageName), } impl IsBuildBackendError for Error { @@ -103,7 +105,8 @@ impl IsBuildBackendError for Error { | Self::RequirementsInstall(_, _) | Self::Virtualenv(_) | Self::NoSourceDistBuild(_) - | Self::NoSourceDistBuilds => false, + | Self::NoSourceDistBuilds + | Self::CyclicBuildDependency(_) => false, Self::CommandFailed(_, _) | Self::BuildBackend(_) | Self::MissingHeader(_) diff --git a/crates/uv-build-frontend/src/lib.rs b/crates/uv-build-frontend/src/lib.rs index 75ea2f6382f2..d8952b55340f 100644 --- a/crates/uv-build-frontend/src/lib.rs +++ b/crates/uv-build-frontend/src/lib.rs @@ -35,7 +35,7 @@ use uv_pep508::PackageName; use uv_pypi_types::{Requirement, VerbatimParsedUrl}; use uv_python::{Interpreter, PythonEnvironment}; use uv_static::EnvVars; -use uv_types::{AnyErrorBuild, BuildContext, BuildIsolation, SourceBuildTrait}; +use uv_types::{AnyErrorBuild, BuildContext, BuildIsolation, BuildStack, SourceBuildTrait}; pub use crate::error::{Error, MissingHeaderCause}; @@ -255,6 +255,7 @@ impl SourceBuild { source_strategy: SourceStrategy, config_settings: ConfigSettings, build_isolation: BuildIsolation<'_>, + build_stack: &BuildStack, build_kind: BuildKind, mut environment_variables: FxHashMap, level: BuildOutput, @@ -318,11 +319,12 @@ impl SourceBuild { source_build_context, &default_backend, &pep517_backend, + build_stack, ) .await?; build_context - .install(&resolved_requirements, &venv) + .install(&resolved_requirements, &venv, build_stack) .await .map_err(|err| Error::RequirementsInstall("`build-system.requires`", err.into()))?; } else { @@ -378,6 +380,7 @@ impl SourceBuild { version_id, locations, source_strategy, + build_stack, build_kind, level, &config_settings, @@ -412,6 +415,7 @@ impl SourceBuild { source_build_context: SourceBuildContext, default_backend: &Pep517Backend, pep517_backend: &Pep517Backend, + build_stack: &BuildStack, ) -> Result { Ok( if pep517_backend.requirements == default_backend.requirements { @@ -420,7 +424,7 @@ impl SourceBuild { resolved_requirements.clone() } else { let resolved_requirements = build_context - .resolve(&default_backend.requirements) + .resolve(&default_backend.requirements, build_stack) .await .map_err(|err| { Error::RequirementsResolve("`setup.py` build", err.into()) @@ -430,7 +434,7 @@ impl SourceBuild { } } else { build_context - .resolve(&pep517_backend.requirements) + .resolve(&pep517_backend.requirements, build_stack) .await .map_err(|err| { Error::RequirementsResolve("`build-system.requires`", err.into()) @@ -806,6 +810,7 @@ async fn create_pep517_build_environment( version_id: Option<&str>, locations: &IndexLocations, source_strategy: SourceStrategy, + build_stack: &BuildStack, build_kind: BuildKind, level: BuildOutput, config_settings: &ConfigSettings, @@ -929,12 +934,15 @@ async fn create_pep517_build_environment( .cloned() .chain(extra_requires) .collect(); - let resolution = build_context.resolve(&requirements).await.map_err(|err| { - Error::RequirementsResolve("`build-system.requires`", AnyErrorBuild::from(err)) - })?; + let resolution = build_context + .resolve(&requirements, build_stack) + .await + .map_err(|err| { + Error::RequirementsResolve("`build-system.requires`", AnyErrorBuild::from(err)) + })?; build_context - .install(&resolution, venv) + .install(&resolution, venv, build_stack) .await .map_err(|err| { Error::RequirementsInstall("`build-system.requires`", AnyErrorBuild::from(err)) diff --git a/crates/uv-cache-info/Cargo.toml b/crates/uv-cache-info/Cargo.toml index ec17a4416a0d..6b10bbebe853 100644 --- a/crates/uv-cache-info/Cargo.toml +++ b/crates/uv-cache-info/Cargo.toml @@ -23,3 +23,4 @@ serde = { workspace = true, features = ["derive"] } thiserror = { workspace = true } toml = { workspace = true } tracing = { workspace = true } +walkdir = { workspace = true } diff --git a/crates/uv-cache-info/src/git_info.rs b/crates/uv-cache-info/src/git_info.rs index 9df098e6959e..ad566d13bdf9 100644 --- a/crates/uv-cache-info/src/git_info.rs +++ b/crates/uv-cache-info/src/git_info.rs @@ -1,6 +1,9 @@ use std::collections::BTreeMap; use std::path::{Path, PathBuf}; +use tracing::warn; +use walkdir::WalkDir; + #[derive(Debug, thiserror::Error)] pub(crate) enum GitInfoError { #[error("The repository at {0} is missing a `.git` directory")] @@ -80,24 +83,27 @@ impl Tags { .find(|git_dir| git_dir.exists()) .ok_or_else(|| GitInfoError::MissingGitDir(path.to_path_buf()))?; - let git_refs_path = - git_refs(&git_dir).ok_or_else(|| GitInfoError::MissingRefs(git_dir.clone()))?; + let git_tags_path = git_refs(&git_dir) + .ok_or_else(|| GitInfoError::MissingRefs(git_dir.clone()))? + .join("tags"); let mut tags = BTreeMap::new(); // Map each tag to its commit. - let read_dir = match fs_err::read_dir(git_refs_path.join("tags")) { - Ok(read_dir) => read_dir, - Err(err) if err.kind() == std::io::ErrorKind::NotFound => { - return Ok(Self(tags)); - } - Err(err) => return Err(err.into()), - }; - for entry in read_dir { - let entry = entry?; + for entry in WalkDir::new(&git_tags_path).contents_first(true) { + let entry = match entry { + Ok(entry) => entry, + Err(err) => { + warn!("Failed to read Git tags: {err}"); + continue; + } + }; let path = entry.path(); - if let Some(tag) = path.file_name().and_then(|name| name.to_str()) { - let commit = fs_err::read_to_string(&path)?.trim().to_string(); + if !entry.file_type().is_file() { + continue; + } + if let Ok(Some(tag)) = path.strip_prefix(&git_tags_path).map(|name| name.to_str()) { + let commit = fs_err::read_to_string(path)?.trim().to_string(); // The commit should be 40 hexadecimal characters. if commit.len() != 40 { diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 2de0546be85c..f6d6281bcee8 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -171,12 +171,13 @@ pub struct GlobalArgs { #[arg(global = true, long, hide = true, conflicts_with = "color")] pub no_color: bool, - /// Control colors in output. + /// Control the use of color in output. + /// + /// By default, uv will automatically detect support for colors when writing to a terminal. #[arg( global = true, long, value_enum, - default_value = "auto", conflicts_with = "no_color", value_name = "COLOR_CHOICE" )] @@ -613,7 +614,8 @@ pub enum PipCommand { /// List, in tabular format, packages installed in an environment. #[command( after_help = "Use `uv help pip list` for more details.", - after_long_help = "" + after_long_help = "", + alias = "ls" )] List(PipListArgs), /// Show information about one or more installed packages. @@ -3108,6 +3110,13 @@ pub struct LockArgs { #[arg(long, conflicts_with = "check_exists", conflicts_with = "check")] pub dry_run: bool, + /// Lock the specified Python script, rather than the current project. + /// + /// If provided, uv will lock the script (based on its inline metadata table, in adherence with + /// PEP 723) to a `.lock` file adjacent to the script itself. + #[arg(long)] + pub script: Option, + #[command(flatten)] pub resolver: ResolverArgs, @@ -3249,7 +3258,12 @@ pub struct AddArgs { /// a new one will be created and added to the script. When executed via `uv run`, /// uv will create a temporary environment for the script with all inline /// dependencies installed. - #[arg(long, conflicts_with = "dev", conflicts_with = "optional")] + #[arg( + long, + conflicts_with = "dev", + conflicts_with = "optional", + conflicts_with = "package" + )] pub script: Option, /// The Python interpreter to use for resolving and syncing. @@ -3272,7 +3286,7 @@ pub struct AddArgs { pub struct RemoveArgs { /// The names of the dependencies to remove (e.g., `ruff`). #[arg(required = true)] - pub packages: Vec, + pub packages: Vec>, /// Remove the packages from the development dependency group. /// @@ -3424,6 +3438,14 @@ pub struct TreeArgs { #[command(flatten)] pub resolver: ResolverArgs, + /// Show the dependency tree the specified PEP 723 Python script, rather than the current + /// project. + /// + /// If provided, uv will resolve the dependencies based on its inline metadata table, in + /// adherence with PEP 723. + #[arg(long)] + pub script: Option, + /// The Python version to use when filtering the tree. /// /// For example, pass `--python-version 3.10` to display the dependencies @@ -3630,6 +3652,14 @@ pub struct ExportArgs { #[command(flatten)] pub refresh: RefreshArgs, + /// Export the dependencies for the specified PEP 723 Python script, rather than the current + /// project. + /// + /// If provided, uv will resolve the dependencies based on its inline metadata table, in + /// adherence with PEP 723. + #[arg(long, conflicts_with_all = ["all_packages", "package", "no_emit_project", "no_emit_workspace"])] + pub script: Option, + /// The Python interpreter to use during resolution. /// /// A Python interpreter is required for building source distributions to @@ -3717,6 +3747,7 @@ pub enum ToolCommand { #[command(alias = "update")] Upgrade(ToolUpgradeArgs), /// List installed tools. + #[command(alias = "ls")] List(ToolListArgs), /// Uninstall a tool. Uninstall(ToolUninstallArgs), @@ -4196,6 +4227,7 @@ pub enum PythonCommand { /// Use `--all-versions` to view all available patch versions. /// /// Use `--only-installed` to omit available downloads. + #[command(alias = "ls")] List(PythonListArgs), /// Download and install Python versions. @@ -4487,8 +4519,8 @@ pub struct GenerateShellCompletionArgs { pub quiet: bool, #[arg(long, short, action = clap::ArgAction::Count, conflicts_with = "quiet", hide = true)] pub verbose: u8, - #[arg(long, default_value = "auto", conflicts_with = "no_color", hide = true)] - pub color: ColorChoice, + #[arg(long, conflicts_with = "no_color", hide = true)] + pub color: Option, #[arg(long, hide = true)] pub native_tls: bool, #[arg(long, hide = true)] @@ -4562,6 +4594,7 @@ pub struct IndexArgs { long, short, env = EnvVars::UV_FIND_LINKS, + value_delimiter = ',', value_parser = parse_find_links, help_heading = "Index options" )] diff --git a/crates/uv-client/src/cached_client.rs b/crates/uv-client/src/cached_client.rs index dc521f5cf3db..3ee9e1cfc25c 100644 --- a/crates/uv-client/src/cached_client.rs +++ b/crates/uv-client/src/cached_client.rs @@ -40,7 +40,7 @@ pub trait Cacheable: Sized { /// /// Typical use of this is for wrapper types used to provide blanket trait /// impls without hitting overlapping impl problems. - type Target; + type Target: Send + 'static; /// Deserialize a value from bytes aligned to a 16-byte boundary. fn from_aligned_bytes(bytes: AlignedVec) -> Result; @@ -58,7 +58,7 @@ pub(crate) struct SerdeCacheable { inner: T, } -impl Cacheable for SerdeCacheable { +impl Cacheable for SerdeCacheable { type Target = T; fn from_aligned_bytes(bytes: AlignedVec) -> Result { @@ -79,7 +79,7 @@ impl Cacheable for SerdeCacheable { /// All `OwnedArchive` values are cacheable. impl Cacheable for OwnedArchive where - A: rkyv::Archive + for<'a> rkyv::Serialize>, + A: rkyv::Archive + for<'a> rkyv::Serialize> + Send + 'static, A::Archived: rkyv::Portable + rkyv::Deserialize + for<'a> rkyv::bytecheck::CheckBytes>, @@ -214,7 +214,7 @@ impl CachedClient { /// allowed to make subsequent requests, e.g. through the uncached client. #[instrument(skip_all)] pub async fn get_serde< - Payload: Serialize + DeserializeOwned + 'static, + Payload: Serialize + DeserializeOwned + Send + 'static, CallBackError: std::error::Error + 'static, Callback, CallbackReturn, @@ -342,7 +342,7 @@ impl CachedClient { /// Make a request without checking whether the cache is fresh. pub async fn skip_cache< - Payload: Serialize + DeserializeOwned + 'static, + Payload: Serialize + DeserializeOwned + Send + 'static, CallBackError: std::error::Error + 'static, Callback, CallbackReturn, @@ -569,7 +569,7 @@ impl CachedClient { /// Perform a [`CachedClient::get_serde`] request with a default retry strategy. #[instrument(skip_all)] pub async fn get_serde_with_retry< - Payload: Serialize + DeserializeOwned + 'static, + Payload: Serialize + DeserializeOwned + Send + 'static, CallBackError: std::error::Error + 'static, Callback, CallbackReturn, @@ -648,7 +648,7 @@ impl CachedClient { /// /// See: pub async fn skip_cache_with_retry< - Payload: Serialize + DeserializeOwned + 'static, + Payload: Serialize + DeserializeOwned + Send + 'static, CallBackError: std::error::Error + 'static, Callback, CallbackReturn, diff --git a/crates/uv-client/src/error.rs b/crates/uv-client/src/error.rs index 30df2fe0d769..b9b5a81c761f 100644 --- a/crates/uv-client/src/error.rs +++ b/crates/uv-client/src/error.rs @@ -139,7 +139,7 @@ impl From for Error { #[derive(Debug, thiserror::Error)] pub enum ErrorKind { #[error(transparent)] - UrlParse(#[from] url::ParseError), + InvalidUrl(#[from] uv_distribution_types::ToUrlError), #[error(transparent)] JoinRelativeUrl(#[from] uv_pypi_types::JoinRelativeError), diff --git a/crates/uv-client/src/html.rs b/crates/uv-client/src/html.rs index 648c5165a7bb..9ea6580134c2 100644 --- a/crates/uv-client/src/html.rs +++ b/crates/uv-client/src/html.rs @@ -1,6 +1,6 @@ use std::str::FromStr; -use tl::{HTMLTag, Parser}; +use tl::HTMLTag; use tracing::{instrument, warn}; use url::Url; @@ -44,7 +44,12 @@ impl SimpleHtml { .iter() .filter_map(|node| node.as_tag()) .filter(|link| link.name().as_bytes() == b"a") - .map(|link| Self::parse_anchor(link, dom.parser())) + .map(|link| Self::parse_anchor(link)) + .filter_map(|result| match result { + Ok(None) => None, + Ok(Some(file)) => Some(Ok(file)), + Err(err) => Some(Err(err)), + }) .collect::, _>>()?; // While it has not been positively observed, we sort the files // to ensure we have a defined ordering. Otherwise, if we rely on @@ -70,14 +75,18 @@ impl SimpleHtml { } /// Parse a [`File`] from an `` tag. - fn parse_anchor(link: &HTMLTag, parser: &Parser) -> Result { + /// + /// Returns `None` if the `` don't doesn't have an `href` attribute. + fn parse_anchor(link: &HTMLTag) -> Result, Error> { // Extract the href. - let href = link + let Some(href) = link .attributes() .get("href") .flatten() .filter(|bytes| !bytes.as_bytes().is_empty()) - .ok_or(Error::MissingHref(link.inner_text(parser).to_string()))?; + else { + return Ok(None); + }; let href = std::str::from_utf8(href.as_bytes())?; // Extract the hash, which should be in the fragment. @@ -158,7 +167,7 @@ impl SimpleHtml { None }; - Ok(File { + Ok(Some(File { core_metadata, dist_info_metadata: None, data_dist_info_metadata: None, @@ -169,7 +178,7 @@ impl SimpleHtml { url: decoded.to_string(), size: None, upload_time: None, - }) + })) } } @@ -628,8 +637,29 @@ mod tests { "; let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap(); - let result = SimpleHtml::parse(text, &base).unwrap_err(); - insta::assert_snapshot!(result, @"Missing href attribute on anchor link: `Jinja2-3.1.2-py3-none-any.whl`"); + let result = SimpleHtml::parse(text, &base).unwrap(); + insta::assert_debug_snapshot!(result, @r###" + SimpleHtml { + base: BaseUrl( + Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "download.pytorch.org", + ), + ), + port: None, + path: "/whl/jinja2/", + query: None, + fragment: None, + }, + ), + files: [], + } + "###); } #[test] @@ -645,8 +675,29 @@ mod tests { "#; let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap(); - let result = SimpleHtml::parse(text, &base).unwrap_err(); - insta::assert_snapshot!(result, @"Missing href attribute on anchor link: `Jinja2-3.1.2-py3-none-any.whl`"); + let result = SimpleHtml::parse(text, &base).unwrap(); + insta::assert_debug_snapshot!(result, @r###" + SimpleHtml { + base: BaseUrl( + Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "download.pytorch.org", + ), + ), + port: None, + path: "/whl/jinja2/", + query: None, + fragment: None, + }, + ), + files: [], + } + "###); } #[test] diff --git a/crates/uv-client/src/registry_client.rs b/crates/uv-client/src/registry_client.rs index 0510b42c0386..6901123abe07 100644 --- a/crates/uv-client/src/registry_client.rs +++ b/crates/uv-client/src/registry_client.rs @@ -1,14 +1,15 @@ -use async_http_range_reader::AsyncHttpRangeReader; -use futures::{FutureExt, TryStreamExt}; -use http::HeaderMap; -use itertools::Either; -use reqwest::{Client, Response, StatusCode}; -use reqwest_middleware::ClientWithMiddleware; use std::collections::BTreeMap; use std::fmt::Debug; use std::path::PathBuf; use std::str::FromStr; use std::time::Duration; + +use async_http_range_reader::AsyncHttpRangeReader; +use futures::{FutureExt, StreamExt, TryStreamExt}; +use http::HeaderMap; +use itertools::Either; +use reqwest::{Client, Response, StatusCode}; +use reqwest_middleware::ClientWithMiddleware; use tracing::{info_span, instrument, trace, warn, Instrument}; use url::Url; @@ -247,38 +248,42 @@ impl RegistryClient { } let mut results = Vec::new(); - for index in it { - match self.simple_single_index(package_name, index).await { - Ok(metadata) => { - results.push((index, metadata)); - // If we're only using the first match, we can stop here. - if self.index_strategy == IndexStrategy::FirstIndex { + match self.index_strategy { + // If we're searching for the first index that contains the package, fetch serially. + IndexStrategy::FirstIndex => { + for index in it { + if let Some(metadata) = self + .simple_single_index(package_name, index, capabilities) + .await? + { + results.push((index, metadata)); break; } } - Err(err) => match err.into_kind() { - // The package could not be found in the remote index. - ErrorKind::WrappedReqwestError(url, err) => match err.status() { - Some(StatusCode::NOT_FOUND) => {} - Some(StatusCode::UNAUTHORIZED) => { - capabilities.set_unauthorized(index.clone()); - } - Some(StatusCode::FORBIDDEN) => { - capabilities.set_forbidden(index.clone()); - } - _ => return Err(ErrorKind::WrappedReqwestError(url, err).into()), - }, - - // The package is unavailable due to a lack of connectivity. - ErrorKind::Offline(_) => {} - - // The package could not be found in the local index. - ErrorKind::FileNotFound(_) => {} + } - other => return Err(other.into()), - }, - }; + // Otherwise, fetch concurrently. + IndexStrategy::UnsafeBestMatch | IndexStrategy::UnsafeFirstMatch => { + // TODO(charlie): Respect concurrency limits. + results = futures::stream::iter(it) + .map(|index| async move { + let metadata = self + .simple_single_index(package_name, index, capabilities) + .await?; + Ok((index, metadata)) + }) + .buffered(8) + .filter_map(|result: Result<_, Error>| async move { + match result { + Ok((index, Some(metadata))) => Some(Ok((index, metadata))), + Ok((_, None)) => None, + Err(err) => Some(Err(err)), + } + }) + .try_collect::>() + .await?; + } } if results.is_empty() { @@ -297,11 +302,14 @@ impl RegistryClient { /// /// The index can either be a PEP 503-compatible remote repository, or a local directory laid /// out in the same format. + /// + /// Returns `Ok(None)` if the package is not found in the index. async fn simple_single_index( &self, package_name: &PackageName, index: &IndexUrl, - ) -> Result, Error> { + capabilities: &IndexCapabilities, + ) -> Result>, Error> { // Format the URL for PyPI. let mut url: Url = index.clone().into(); url.path_segments_mut() @@ -328,11 +336,38 @@ impl RegistryClient { Connectivity::Offline => CacheControl::AllowStale, }; - if matches!(index, IndexUrl::Path(_)) { + let result = if matches!(index, IndexUrl::Path(_)) { self.fetch_local_index(package_name, &url).await } else { self.fetch_remote_index(package_name, &url, &cache_entry, cache_control) .await + }; + + match result { + Ok(metadata) => Ok(Some(metadata)), + Err(err) => match err.into_kind() { + // The package could not be found in the remote index. + ErrorKind::WrappedReqwestError(url, err) => match err.status() { + Some(StatusCode::NOT_FOUND) => Ok(None), + Some(StatusCode::UNAUTHORIZED) => { + capabilities.set_unauthorized(index.clone()); + Ok(None) + } + Some(StatusCode::FORBIDDEN) => { + capabilities.set_forbidden(index.clone()); + Ok(None) + } + _ => Err(ErrorKind::WrappedReqwestError(url, err).into()), + }, + + // The package is unavailable due to a lack of connectivity. + ErrorKind::Offline(_) => Ok(None), + + // The package could not be found in the local index. + ErrorKind::FileNotFound(_) => Ok(None), + + err => Err(err.into()), + }, } } @@ -474,7 +509,7 @@ impl RegistryClient { } } FileLocation::AbsoluteUrl(url) => { - let url = url.to_url(); + let url = url.to_url().map_err(ErrorKind::InvalidUrl)?; if url.scheme() == "file" { let path = url .to_file_path() @@ -786,15 +821,13 @@ impl VersionFiles { } pub fn all(self) -> impl Iterator { - self.wheels + self.source_dists .into_iter() - .map(|VersionWheel { name, file }| (DistFilename::WheelFilename(name), file)) + .map(|VersionSourceDist { name, file }| (DistFilename::SourceDistFilename(name), file)) .chain( - self.source_dists + self.wheels .into_iter() - .map(|VersionSourceDist { name, file }| { - (DistFilename::SourceDistFilename(name), file) - }), + .map(|VersionWheel { name, file }| (DistFilename::WheelFilename(name), file)), ) } } diff --git a/crates/uv-client/tests/it/remote_metadata.rs b/crates/uv-client/tests/it/remote_metadata.rs index 0570bba4d720..72bc7322238a 100644 --- a/crates/uv-client/tests/it/remote_metadata.rs +++ b/crates/uv-client/tests/it/remote_metadata.rs @@ -21,7 +21,7 @@ async fn remote_metadata_with_and_without_cache() -> Result<()> { let filename = WheelFilename::from_str(url.rsplit_once('/').unwrap().1)?; let dist = BuiltDist::DirectUrl(DirectUrlBuiltDist { filename, - location: Url::parse(url).unwrap(), + location: Box::new(Url::parse(url).unwrap()), url: VerbatimUrl::from_str(url).unwrap(), }); let capabilities = IndexCapabilities::default(); diff --git a/crates/uv-configuration/Cargo.toml b/crates/uv-configuration/Cargo.toml index 83de06f5cc35..cc8b00757a4e 100644 --- a/crates/uv-configuration/Cargo.toml +++ b/crates/uv-configuration/Cargo.toml @@ -21,6 +21,7 @@ uv-cache = { workspace = true } uv-cache-info = { workspace = true } uv-cache-key = { workspace = true } uv-normalize = { workspace = true } +uv-pep440 = { workspace = true } uv-pep508 = { workspace = true, features = ["schemars"] } uv-platform-tags = { workspace = true } uv-pypi-types = { workspace = true } diff --git a/crates/uv-configuration/src/dev.rs b/crates/uv-configuration/src/dev.rs index 80ae4f0640e4..3a94c6f4be16 100644 --- a/crates/uv-configuration/src/dev.rs +++ b/crates/uv-configuration/src/dev.rs @@ -292,7 +292,7 @@ impl DevGroupsSpecification { self.groups .as_ref() - .map_or(false, |groups| groups.contains(group)) + .is_some_and(|groups| groups.contains(group)) } } @@ -316,7 +316,7 @@ impl From for DevGroupsSpecification { /// The manifest of `dependency-groups` to include, taking into account the user-provided /// [`DevGroupsSpecification`] and the project-specific default groups. -#[derive(Debug, Clone)] +#[derive(Debug, Default, Clone)] pub struct DevGroupsManifest { /// The specification for the development dependencies. pub(crate) spec: DevGroupsSpecification, @@ -347,7 +347,7 @@ impl DevGroupsManifest { } /// Returns `true` if the group was enabled by default. - pub fn default(&self, group: &GroupName) -> bool { + pub fn is_default(&self, group: &GroupName) -> bool { if self.spec.contains(group) { // If the group was explicitly requested, then it wasn't enabled by default. false diff --git a/crates/uv-configuration/src/lib.rs b/crates/uv-configuration/src/lib.rs index 6eb47a89a82a..62dfcb4a1e78 100644 --- a/crates/uv-configuration/src/lib.rs +++ b/crates/uv-configuration/src/lib.rs @@ -16,6 +16,7 @@ pub use package_options::*; pub use preview::*; pub use project_build_backend::*; pub use rayon::*; +pub use required_version::*; pub use sources::*; pub use target_triple::*; pub use trusted_host::*; @@ -40,6 +41,7 @@ mod package_options; mod preview; mod project_build_backend; mod rayon; +mod required_version; mod sources; mod target_triple; mod trusted_host; diff --git a/crates/uv-configuration/src/overrides.rs b/crates/uv-configuration/src/overrides.rs index e690a66cbf2d..8a84188febee 100644 --- a/crates/uv-configuration/src/overrides.rs +++ b/crates/uv-configuration/src/overrides.rs @@ -42,7 +42,12 @@ impl Overrides { &'a self, requirements: impl IntoIterator, ) -> impl Iterator> { - requirements.into_iter().flat_map(|requirement| { + if self.0.is_empty() { + // Fast path: There are no overrides. + return Either::Left(requirements.into_iter().map(Cow::Borrowed)); + } + + Either::Right(requirements.into_iter().flat_map(|requirement| { let Some(overrides) = self.get(&requirement.name) else { // Case 1: No override(s). return Either::Left(std::iter::once(Cow::Borrowed(requirement))); @@ -70,6 +75,6 @@ impl Overrides { }) }, ))) - }) + })) } } diff --git a/crates/uv-configuration/src/package_options.rs b/crates/uv-configuration/src/package_options.rs index 5215425811ef..ac39383862b6 100644 --- a/crates/uv-configuration/src/package_options.rs +++ b/crates/uv-configuration/src/package_options.rs @@ -47,6 +47,15 @@ impl Reinstall { matches!(self, Self::All) } + /// Returns `true` if the specified package should be reinstalled. + pub fn contains(&self, package_name: &PackageName) -> bool { + match &self { + Self::None => false, + Self::All => true, + Self::Packages(packages) => packages.contains(package_name), + } + } + /// Combine a set of [`Reinstall`] values. #[must_use] pub fn combine(self, other: Self) -> Self { diff --git a/crates/uv-configuration/src/required_version.rs b/crates/uv-configuration/src/required_version.rs new file mode 100644 index 000000000000..339abb1cfdb4 --- /dev/null +++ b/crates/uv-configuration/src/required_version.rs @@ -0,0 +1,61 @@ +use std::str::FromStr; + +use uv_pep440::{Version, VersionSpecifier, VersionSpecifiers, VersionSpecifiersParseError}; + +/// A required version of uv, represented as a version specifier (e.g. `>=0.5.0`). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RequiredVersion(VersionSpecifiers); + +impl RequiredVersion { + /// Return `true` if the given version is required. + pub fn contains(&self, version: &Version) -> bool { + self.0.contains(version) + } +} + +impl FromStr for RequiredVersion { + type Err = VersionSpecifiersParseError; + + fn from_str(s: &str) -> Result { + // Treat `0.5.0` as `==0.5.0`, for backwards compatibility. + if let Ok(version) = Version::from_str(s) { + Ok(Self(VersionSpecifiers::from( + VersionSpecifier::equals_version(version), + ))) + } else { + Ok(Self(VersionSpecifiers::from_str(s)?)) + } + } +} + +#[cfg(feature = "schemars")] +impl schemars::JsonSchema for RequiredVersion { + fn schema_name() -> String { + String::from("RequiredVersion") + } + + fn json_schema(_gen: &mut schemars::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() + } +} + +impl<'de> serde::Deserialize<'de> for RequiredVersion { + fn deserialize>(deserializer: D) -> Result { + let s = String::deserialize(deserializer)?; + Self::from_str(&s).map_err(serde::de::Error::custom) + } +} + +impl std::fmt::Display for RequiredVersion { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } +} diff --git a/crates/uv-dev/src/wheel_metadata.rs b/crates/uv-dev/src/wheel_metadata.rs index 421709810237..9ef55dae16a2 100644 --- a/crates/uv-dev/src/wheel_metadata.rs +++ b/crates/uv-dev/src/wheel_metadata.rs @@ -33,7 +33,7 @@ pub(crate) async fn wheel_metadata(args: WheelMetadataArgs) -> Result<()> { .wheel_metadata( &BuiltDist::DirectUrl(DirectUrlBuiltDist { filename, - location: archive.url, + location: Box::new(archive.url), url: args.url, }), &capabilities, diff --git a/crates/uv-dispatch/src/lib.rs b/crates/uv-dispatch/src/lib.rs index 77e8c237d9f6..28a0a5ba0564 100644 --- a/crates/uv-dispatch/src/lib.rs +++ b/crates/uv-dispatch/src/lib.rs @@ -23,8 +23,8 @@ use uv_configuration::{BuildOutput, Concurrency}; use uv_distribution::DistributionDatabase; use uv_distribution_filename::DistFilename; use uv_distribution_types::{ - CachedDist, DependencyMetadata, IndexCapabilities, IndexLocations, IsBuildBackendError, Name, - Resolution, SourceDist, VersionOrUrlRef, + CachedDist, DependencyMetadata, Identifier, IndexCapabilities, IndexLocations, + IsBuildBackendError, Name, Resolution, SourceDist, VersionOrUrlRef, }; use uv_git::GitResolver; use uv_installer::{Installer, Plan, Planner, Preparer, SitePackages}; @@ -35,7 +35,8 @@ use uv_resolver::{ PythonRequirement, Resolver, ResolverEnvironment, }; use uv_types::{ - AnyErrorBuild, BuildContext, BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight, + AnyErrorBuild, BuildContext, BuildIsolation, BuildStack, EmptyInstalledPackages, HashStrategy, + InFlight, }; #[derive(Debug, Error)] @@ -162,7 +163,7 @@ impl<'a> BuildDispatch<'a> { } #[allow(refining_impl_trait)] -impl<'a> BuildContext for BuildDispatch<'a> { +impl BuildContext for BuildDispatch<'_> { type SourceDistBuilder = SourceBuild; fn interpreter(&self) -> &Interpreter { @@ -208,6 +209,7 @@ impl<'a> BuildContext for BuildDispatch<'a> { async fn resolve<'data>( &'data self, requirements: &'data [Requirement], + build_stack: &'data BuildStack, ) -> Result { let python_requirement = PythonRequirement::from_interpreter(self.interpreter); let marker_env = self.interpreter.resolver_marker_environment(); @@ -223,8 +225,7 @@ impl<'a> BuildContext for BuildDispatch<'a> { .build(), &python_requirement, ResolverEnvironment::specific(marker_env), - // Conflicting groups only make sense when doing - // universal resolution. + // Conflicting groups only make sense when doing universal resolution. Conflicts::empty(), Some(tags), self.flat_index, @@ -232,7 +233,8 @@ impl<'a> BuildContext for BuildDispatch<'a> { self.hasher, self, EmptyInstalledPackages, - DistributionDatabase::new(self.client, self, self.concurrency.downloads), + DistributionDatabase::new(self.client, self, self.concurrency.downloads) + .with_build_stack(build_stack), )?; let resolution = Resolution::from(resolver.resolve().await.with_context(|| { format!( @@ -257,6 +259,7 @@ impl<'a> BuildContext for BuildDispatch<'a> { &'data self, resolution: &'data Resolution, venv: &'data PythonEnvironment, + build_stack: &'data BuildStack, ) -> Result, BuildDispatchError> { debug!( "Installing in {} in {}", @@ -296,17 +299,27 @@ impl<'a> BuildContext for BuildDispatch<'a> { return Ok(vec![]); } + // Verify that none of the missing distributions are already in the build stack. + for dist in &remote { + let id = dist.distribution_id(); + if build_stack.contains(&id) { + return Err(BuildDispatchError::BuildFrontend( + uv_build_frontend::Error::CyclicBuildDependency(dist.name().clone()).into(), + )); + } + } + // Download any missing distributions. let wheels = if remote.is_empty() { vec![] } else { - // TODO(konstin): Check that there is no endless recursion. let preparer = Preparer::new( self.cache, tags, self.hasher, self.build_options, - DistributionDatabase::new(self.client, self, self.concurrency.downloads), + DistributionDatabase::new(self.client, self, self.concurrency.downloads) + .with_build_stack(build_stack), ); debug!( @@ -367,6 +380,7 @@ impl<'a> BuildContext for BuildDispatch<'a> { sources: SourceStrategy, build_kind: BuildKind, build_output: BuildOutput, + mut build_stack: BuildStack, ) -> Result { let dist_name = dist.map(uv_distribution_types::Name::name); let dist_version = dist @@ -392,6 +406,11 @@ impl<'a> BuildContext for BuildDispatch<'a> { return Err(err); } + // Push the current distribution onto the build stack, to prevent cyclic dependencies. + if let Some(dist) = dist { + build_stack.insert(dist.distribution_id()); + } + let builder = SourceBuild::setup( source, subdirectory, @@ -406,6 +425,7 @@ impl<'a> BuildContext for BuildDispatch<'a> { sources, self.config_settings.clone(), self.build_isolation, + &build_stack, build_kind, self.build_extra_env_vars.clone(), build_output, diff --git a/crates/uv-distribution-filename/src/wheel.rs b/crates/uv-distribution-filename/src/wheel.rs index 0d476213578b..fda2de853d4b 100644 --- a/crates/uv-distribution-filename/src/wheel.rs +++ b/crates/uv-distribution-filename/src/wheel.rs @@ -102,12 +102,6 @@ impl WheelFilename { // The wheel filename should contain either five or six entries. If six, then the third // entry is the build tag. If five, then the third entry is the Python tag. // https://www.python.org/dev/peps/pep-0427/#file-name-convention - // - // 2023-11-08(burntsushi): It looks like the code below actually drops - // the build tag if one is found. According to PEP 0427, the build tag - // is used to break ties. This might mean that we generate identical - // `WheelName` values for multiple distinct wheels, but it's not clear - // if this is a problem in practice. let mut parts = stem.split('-'); let name = parts diff --git a/crates/uv-distribution-types/Cargo.toml b/crates/uv-distribution-types/Cargo.toml index 4a501926b7ec..d4c5309f5e05 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 } anyhow = { workspace = true } +arcstr = { workspace = true } bitflags = { workspace = true } fs-err = { workspace = true } itertools = { workspace = true } diff --git a/crates/uv-distribution-types/src/dist_error.rs b/crates/uv-distribution-types/src/dist_error.rs index 9bfcf8e974bb..fcbb13dde83b 100644 --- a/crates/uv-distribution-types/src/dist_error.rs +++ b/crates/uv-distribution-types/src/dist_error.rs @@ -1,12 +1,17 @@ -use crate::{BuiltDist, Dist, DistRef, Edge, Name, Node, Resolution, ResolvedDist, SourceDist}; +use std::collections::VecDeque; +use std::fmt::{Debug, Display, Formatter}; + use petgraph::prelude::EdgeRef; use petgraph::Direction; use rustc_hash::FxHashSet; -use std::collections::VecDeque; -use std::fmt::{Debug, Display, Formatter}; +use version_ranges::Ranges; + use uv_normalize::{ExtraName, GroupName, PackageName}; use uv_pep440::Version; -use version_ranges::Ranges; + +use crate::{ + BuiltDist, Dist, DistRef, Edge, Name, Node, RequestedDist, Resolution, ResolvedDist, SourceDist, +}; /// Inspect whether an error type is a build error. pub trait IsBuildBackendError: std::error::Error + Send + Sync + 'static { @@ -25,7 +30,14 @@ pub enum DistErrorKind { } impl DistErrorKind { - pub fn from_dist_and_err(dist: &Dist, err: &impl IsBuildBackendError) -> Self { + pub fn from_requested_dist(dist: &RequestedDist, err: &impl IsBuildBackendError) -> Self { + match dist { + RequestedDist::Installed(_) => DistErrorKind::Read, + RequestedDist::Installable(dist) => Self::from_dist(dist, err), + } + } + + pub fn from_dist(dist: &Dist, err: &impl IsBuildBackendError) -> Self { if err.is_build_backend_error() { DistErrorKind::BuildBackend } else { diff --git a/crates/uv-distribution-types/src/file.rs b/crates/uv-distribution-types/src/file.rs index bfbbbf7e49b5..081cab1107d6 100644 --- a/crates/uv-distribution-types/src/file.rs +++ b/crates/uv-distribution-types/src/file.rs @@ -1,6 +1,4 @@ -use std::borrow::Cow; use std::fmt::{self, Display, Formatter}; -use std::path::PathBuf; use std::str::FromStr; use jiff::Timestamp; @@ -8,7 +6,7 @@ use serde::{Deserialize, Serialize}; use url::Url; use uv_pep440::{VersionSpecifiers, VersionSpecifiersParseError}; -use uv_pep508::VerbatimUrl; +use uv_pep508::split_scheme; use uv_pypi_types::{CoreMetadata, HashDigest, Yanked}; /// Error converting [`uv_pypi_types::File`] to [`distribution_type::File`]. @@ -56,9 +54,9 @@ impl File { .map_err(|err| FileConversionError::RequiresPython(err.line().clone(), err))?, size: file.size, upload_time_utc_ms: file.upload_time.map(Timestamp::as_millisecond), - url: match Url::parse(&file.url) { - Ok(url) => FileLocation::AbsoluteUrl(url.into()), - Err(_) => FileLocation::RelativeUrl(base.to_string(), file.url), + url: match split_scheme(&file.url) { + Some(..) => FileLocation::AbsoluteUrl(UrlString::new(file.url)), + None => FileLocation::RelativeUrl(base.to_string(), file.url), }, yanked: file.yanked, }) @@ -101,7 +99,7 @@ impl FileLocation { })?; Ok(joined) } - FileLocation::AbsoluteUrl(ref absolute) => Ok(absolute.to_url()), + FileLocation::AbsoluteUrl(ref absolute) => absolute.to_url(), } } @@ -128,7 +126,7 @@ impl Display for FileLocation { /// A [`Url`] represented as a `String`. /// -/// This type is guaranteed to be a valid URL but avoids being parsed into the [`Url`] type. +/// This type is not guaranteed to be a valid URL, and may error on conversion. #[derive( Debug, Clone, @@ -148,24 +146,38 @@ impl Display for FileLocation { pub struct UrlString(String); impl UrlString { + /// Create a new [`UrlString`] from a [`String`]. + pub fn new(url: String) -> Self { + Self(url) + } + /// Converts a [`UrlString`] to a [`Url`]. - pub fn to_url(&self) -> Url { - // This conversion can never fail as the only way to construct a `UrlString` is from a `Url`. - Url::from_str(&self.0).unwrap() + pub fn to_url(&self) -> Result { + Url::from_str(&self.0).map_err(|err| ToUrlError::InvalidAbsolute { + absolute: self.0.clone(), + err, + }) } /// Return the [`UrlString`] with any query parameters and fragments removed. pub fn base_str(&self) -> &str { self.as_ref() - .split_once(['#', '?']) + .split_once('?') + .or_else(|| self.as_ref().split_once('#')) .map(|(path, _)| path) .unwrap_or(self.as_ref()) } - /// Return the [`UrlString`] with any query parameters and fragments removed. + /// Return the [`UrlString`] with any fragments removed. #[must_use] - pub fn as_base_url(&self) -> Self { - Self(self.base_str().to_string()) + pub fn without_fragment(&self) -> Self { + Self( + self.as_ref() + .split_once('#') + .map(|(path, _)| path) + .unwrap_or(self.as_ref()) + .to_string(), + ) } } @@ -187,42 +199,18 @@ impl From<&Url> for UrlString { } } -impl From> for UrlString { - fn from(value: Cow<'_, Url>) -> Self { - UrlString(value.to_string()) - } -} - -impl From for UrlString { - fn from(value: VerbatimUrl) -> Self { - UrlString(value.raw().to_string()) - } -} - -impl From<&VerbatimUrl> for UrlString { - fn from(value: &VerbatimUrl) -> Self { - UrlString(value.raw().to_string()) - } -} - -impl From for String { - fn from(value: UrlString) -> Self { - value.0 - } -} - impl Display for UrlString { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.0, f) } } -/// An error that occurs when a `FileLocation` is not a valid URL. +/// An error that occurs when a [`FileLocation`] is not a valid URL. #[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)] pub enum ToUrlError { - /// An error that occurs when the base URL in `FileLocation::Relative` + /// An error that occurs when the base URL in [`FileLocation::Relative`] /// could not be parsed as a valid URL. - #[error("could not parse base URL `{base}` as a valid URL")] + #[error("Could not parse base URL `{base}` as a valid URL")] InvalidBase { /// The base URL that could not be parsed as a valid URL. base: String, @@ -231,8 +219,8 @@ pub enum ToUrlError { err: url::ParseError, }, /// An error that occurs when the base URL could not be joined with - /// the relative path in a `FileLocation::Relative`. - #[error("could not join base URL `{base}` to relative path `{path}`")] + /// the relative path in a [`FileLocation::Relative`]. + #[error("Could not join base URL `{base}` to relative path `{path}`")] InvalidJoin { /// The base URL that could not be parsed as a valid URL. base: String, @@ -242,9 +230,9 @@ pub enum ToUrlError { #[source] err: url::ParseError, }, - /// An error that occurs when the absolute URL in `FileLocation::Absolute` + /// An error that occurs when the absolute URL in [`FileLocation::Absolute`] /// could not be parsed as a valid URL. - #[error("could not parse absolute URL `{absolute}` as a valid URL")] + #[error("Could not parse absolute URL `{absolute}` as a valid URL")] InvalidAbsolute { /// The absolute URL that could not be parsed as a valid URL. absolute: String, @@ -252,19 +240,36 @@ pub enum ToUrlError { #[source] err: url::ParseError, }, - /// An error that occurs when the file path in `FileLocation::Path` is - /// not valid UTF-8. We need paths to be valid UTF-8 to be transformed - /// into URLs, which must also be UTF-8. - #[error("could not build URL from file path `{path}` because it is not valid UTF-8")] - PathNotUtf8 { - /// The original path that was not valid UTF-8. - path: PathBuf, - }, - /// An error that occurs when the file URL created from a file path is not - /// a valid URL. - #[error("could not parse file path `{path}` as a valid URL")] - InvalidPath { - /// The file path URL that could not be parsed as a valid URL. - path: String, - }, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn base_str() { + let url = UrlString("https://example.com/path?query#fragment".to_string()); + assert_eq!(url.base_str(), "https://example.com/path"); + + let url = UrlString("https://example.com/path#fragment".to_string()); + assert_eq!(url.base_str(), "https://example.com/path"); + + let url = UrlString("https://example.com/path".to_string()); + assert_eq!(url.base_str(), "https://example.com/path"); + } + + #[test] + fn without_fragment() { + let url = UrlString("https://example.com/path?query#fragment".to_string()); + assert_eq!( + url.without_fragment(), + UrlString("https://example.com/path?query".to_string()) + ); + + let url = UrlString("https://example.com/path#fragment".to_string()); + assert_eq!(url.base_str(), "https://example.com/path"); + + let url = UrlString("https://example.com/path".to_string()); + assert_eq!(url.base_str(), "https://example.com/path"); + } } diff --git a/crates/uv-distribution-types/src/hash.rs b/crates/uv-distribution-types/src/hash.rs index ff668b6a1e7a..e3bb4aa8445f 100644 --- a/crates/uv-distribution-types/src/hash.rs +++ b/crates/uv-distribution-types/src/hash.rs @@ -5,7 +5,7 @@ pub enum HashPolicy<'a> { /// No hash policy is specified. None, /// Hashes should be generated (specifically, a SHA-256 hash), but not validated. - Generate, + Generate(HashGeneration), /// Hashes should be validated against a pre-defined list of hashes. If necessary, hashes should /// be generated so as to ensure that the archive is valid. Validate(&'a [HashDigest]), @@ -17,21 +17,28 @@ impl HashPolicy<'_> { matches!(self, Self::None) } - /// Returns `true` if the hash policy is `Generate`. - pub fn is_generate(&self) -> bool { - matches!(self, Self::Generate) - } - /// Returns `true` if the hash policy is `Validate`. pub fn is_validate(&self) -> bool { matches!(self, Self::Validate(_)) } + /// Returns `true` if the hash policy indicates that hashes should be generated. + pub fn is_generate(&self, dist: &crate::BuiltDist) -> bool { + match self { + HashPolicy::Generate(HashGeneration::Url) => dist.file().is_none(), + HashPolicy::Generate(HashGeneration::All) => { + dist.file().map_or(true, |file| file.hashes.is_empty()) + } + HashPolicy::Validate(_) => false, + HashPolicy::None => false, + } + } + /// Return the algorithms used in the hash policy. pub fn algorithms(&self) -> Vec { match self { Self::None => vec![], - Self::Generate => vec![HashAlgorithm::Sha256], + Self::Generate(_) => vec![HashAlgorithm::Sha256], Self::Validate(hashes) => { let mut algorithms = hashes.iter().map(HashDigest::algorithm).collect::>(); algorithms.sort(); @@ -45,12 +52,22 @@ impl HashPolicy<'_> { pub fn digests(&self) -> &[HashDigest] { match self { Self::None => &[], - Self::Generate => &[], + Self::Generate(_) => &[], Self::Validate(hashes) => hashes, } } } +/// The context in which hashes should be generated. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum HashGeneration { + /// Generate hashes for direct URL distributions. + Url, + /// Generate hashes for direct URL distributions, along with any distributions that are hosted + /// on a registry that does _not_ provide hashes. + All, +} + pub trait Hashed { /// Return the [`HashDigest`]s for the archive. fn hashes(&self) -> &[HashDigest]; @@ -59,7 +76,7 @@ pub trait Hashed { fn satisfies(&self, hashes: HashPolicy) -> bool { match hashes { HashPolicy::None => true, - HashPolicy::Generate => self + HashPolicy::Generate(_) => self .hashes() .iter() .any(|hash| hash.algorithm == HashAlgorithm::Sha256), @@ -71,7 +88,7 @@ pub trait Hashed { fn has_digests(&self, hashes: HashPolicy) -> bool { match hashes { HashPolicy::None => true, - HashPolicy::Generate => self + HashPolicy::Generate(_) => self .hashes() .iter() .any(|hash| hash.algorithm == HashAlgorithm::Sha256), diff --git a/crates/uv-distribution-types/src/installed.rs b/crates/uv-distribution-types/src/installed.rs index 1d4cf966216b..4bd528643a5a 100644 --- a/crates/uv-distribution-types/src/installed.rs +++ b/crates/uv-distribution-types/src/installed.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::io::BufReader; use std::path::{Path, PathBuf}; use std::str::FromStr; @@ -309,7 +310,8 @@ impl InstalledDist { Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None), Err(err) => return Err(err.into()), }; - let direct_url = serde_json::from_reader::(file)?; + let direct_url = + serde_json::from_reader::, DirectUrl>(BufReader::new(file))?; Ok(Some(direct_url)) } @@ -321,7 +323,8 @@ impl InstalledDist { Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None), Err(err) => return Err(err.into()), }; - let cache_info = serde_json::from_reader::(file)?; + let cache_info = + serde_json::from_reader::, CacheInfo>(BufReader::new(file))?; Ok(Some(cache_info)) } diff --git a/crates/uv-distribution-types/src/lib.rs b/crates/uv-distribution-types/src/lib.rs index 9aba9e5c5f14..81e9d53a697e 100644 --- a/crates/uv-distribution-types/src/lib.rs +++ b/crates/uv-distribution-types/src/lib.rs @@ -67,6 +67,7 @@ pub use crate::installed::*; pub use crate::origin::*; pub use crate::pip_index::*; pub use crate::prioritized_distribution::*; +pub use crate::requested::*; pub use crate::resolution::*; pub use crate::resolved::*; pub use crate::specified_requirement::*; @@ -90,6 +91,7 @@ mod installed; mod origin; mod pip_index; mod prioritized_distribution; +mod requested; mod resolution; mod resolved; mod specified_requirement; @@ -248,7 +250,7 @@ pub struct DirectUrlBuiltDist { /// `https://example.org/packages/flask-3.0.0-py3-none-any.whl` pub filename: WheelFilename, /// The URL without the subdirectory fragment. - pub location: Url, + pub location: Box, /// The URL as it was provided by the user. pub url: VerbatimUrl, } @@ -361,7 +363,7 @@ impl Dist { Ok(Self::Built(BuiltDist::DirectUrl(DirectUrlBuiltDist { filename, - location, + location: Box::new(location), url, }))) } @@ -1341,8 +1343,8 @@ mod test { /// Ensure that we don't accidentally grow the `Dist` sizes. #[test] fn dist_size() { - assert!(size_of::() <= 336, "{}", size_of::()); - assert!(size_of::() <= 336, "{}", size_of::()); + assert!(size_of::() <= 288, "{}", size_of::()); + assert!(size_of::() <= 288, "{}", size_of::()); assert!( size_of::() <= 264, "{}", diff --git a/crates/uv-distribution-types/src/prioritized_distribution.rs b/crates/uv-distribution-types/src/prioritized_distribution.rs index 029514cff651..d5bfc4d5ecd2 100644 --- a/crates/uv-distribution-types/src/prioritized_distribution.rs +++ b/crates/uv-distribution-types/src/prioritized_distribution.rs @@ -1,6 +1,9 @@ use std::fmt::{Display, Formatter}; -use uv_distribution_filename::{BuildTag, WheelFilename}; +use arcstr::ArcStr; +use tracing::debug; + +use uv_distribution_filename::{BuildTag, WheelFilename}; use uv_pep440::VersionSpecifiers; use uv_pep508::{MarkerExpression, MarkerOperator, MarkerTree, MarkerValueString}; use uv_platform_tags::{IncompatibleTag, TagPriority}; @@ -43,7 +46,7 @@ impl Default for PrioritizedDistInner { } /// A distribution that can be used for both resolution and installation. -#[derive(Debug, Clone)] +#[derive(Debug, Copy, Clone)] pub enum CompatibleDist<'a> { /// The distribution is already installed and can be used. InstalledDist(&'a InstalledDist), @@ -220,7 +223,7 @@ impl Display for IncompatibleDist { } } -#[derive(Debug, PartialEq, Eq, Clone)] +#[derive(Debug, PartialEq, Eq, Copy, Clone)] pub enum PythonRequirementKind { /// The installed version of Python. Installed, @@ -264,7 +267,7 @@ pub enum IncompatibleSource { NoBuild, } -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum HashComparison { /// The hash is present, but does not match the expected value. Mismatched, @@ -312,6 +315,12 @@ impl PrioritizedDist { hashes: impl IntoIterator, compatibility: WheelCompatibility, ) { + // Track the implied markers. + if compatibility.is_compatible() { + if !self.0.markers.is_true() { + self.0.markers.or(implied_markers(&dist.filename)); + } + } // Track the highest-priority wheel. if let Some((.., existing_compatibility)) = self.best_wheel() { if compatibility.is_more_compatible(existing_compatibility) { @@ -321,9 +330,6 @@ impl PrioritizedDist { self.0.best_wheel_index = Some(self.0.wheels.len()); } self.0.hashes.extend(hashes); - if !self.0.markers.is_true() { - self.0.markers.or(implied_markers(&dist.filename)); - } self.0.wheels.push((dist, compatibility)); } @@ -334,6 +340,10 @@ impl PrioritizedDist { hashes: impl IntoIterator, compatibility: SourceDistCompatibility, ) { + // Track the implied markers. + if compatibility.is_compatible() { + self.0.markers = MarkerTree::TRUE; + } // Track the highest-priority source. if let Some((.., existing_compatibility)) = &self.0.source { if compatibility.is_more_compatible(existing_compatibility) { @@ -342,9 +352,6 @@ impl PrioritizedDist { } else { self.0.source = Some((dist, compatibility)); } - if !self.0.markers.is_true() { - self.0.markers.or(MarkerTree::TRUE); - } self.0.hashes.extend(hashes); } @@ -524,6 +531,7 @@ impl<'a> CompatibleDist<'a> { } impl WheelCompatibility { + /// Return `true` if the distribution is compatible. pub fn is_compatible(&self) -> bool { matches!(self, Self::Compatible(_, _, _)) } @@ -550,6 +558,11 @@ impl WheelCompatibility { } impl SourceDistCompatibility { + /// Return `true` if the distribution is compatible. + pub fn is_compatible(&self) -> bool { + matches!(self, Self::Compatible(_)) + } + /// Return the higher priority compatibility. /// /// Compatible source distributions are always higher priority than incompatible source distributions. @@ -634,37 +647,291 @@ impl IncompatibleWheel { } /// Given a wheel filename, determine the set of supported platforms, in terms of their markers. +/// +/// This is roughly the inverse of platform tag generation: given a tag, we want to infer the +/// supported platforms (rather than generating the supported tags from a given platform). pub fn implied_markers(filename: &WheelFilename) -> MarkerTree { let mut marker = MarkerTree::FALSE; for platform_tag in &filename.platform_tag { match platform_tag.as_str() { - "any" => marker.or(MarkerTree::TRUE), - tag if tag.starts_with("win") => { - marker.or(MarkerTree::expression(MarkerExpression::String { + "any" => { + return MarkerTree::TRUE; + } + + // Windows + "win32" => { + let mut tag_marker = MarkerTree::expression(MarkerExpression::String { key: MarkerValueString::SysPlatform, operator: MarkerOperator::Equal, - value: "win32".to_string(), + value: arcstr::literal!("win32"), + }); + tag_marker.and(MarkerTree::expression(MarkerExpression::String { + key: MarkerValueString::PlatformMachine, + operator: MarkerOperator::Equal, + value: arcstr::literal!("x86"), })); + marker.or(tag_marker); } - tag if tag.starts_with("macosx") => { - marker.or(MarkerTree::expression(MarkerExpression::String { + "win_amd64" => { + let mut tag_marker = MarkerTree::expression(MarkerExpression::String { key: MarkerValueString::SysPlatform, operator: MarkerOperator::Equal, - value: "darwin".to_string(), + value: arcstr::literal!("win32"), + }); + tag_marker.and(MarkerTree::expression(MarkerExpression::String { + key: MarkerValueString::PlatformMachine, + operator: MarkerOperator::Equal, + value: arcstr::literal!("x86_64"), })); + marker.or(tag_marker); } - tag if tag.starts_with("manylinux") - || tag.starts_with("musllinux") - || tag.starts_with("linux") => - { - marker.or(MarkerTree::expression(MarkerExpression::String { + "win_arm64" => { + let mut tag_marker = MarkerTree::expression(MarkerExpression::String { key: MarkerValueString::SysPlatform, operator: MarkerOperator::Equal, - value: "linux".to_string(), + value: arcstr::literal!("win32"), + }); + tag_marker.and(MarkerTree::expression(MarkerExpression::String { + key: MarkerValueString::PlatformMachine, + operator: MarkerOperator::Equal, + value: arcstr::literal!("arm64"), })); + marker.or(tag_marker); + } + + // macOS + tag if tag.starts_with("macosx_") => { + let mut tag_marker = MarkerTree::expression(MarkerExpression::String { + key: MarkerValueString::SysPlatform, + operator: MarkerOperator::Equal, + value: arcstr::literal!("darwin"), + }); + + // Parse the macOS version from the tag. + // + // For example, given `macosx_10_9_x86_64`, infer `10.9`, followed by `x86_64`. + // + // If at any point we fail to parse, we assume the tag is invalid and skip it. + let mut parts = tag.splitn(4, '_'); + + // Skip the "macosx_" prefix. + if parts.next().is_none_or(|part| part != "macosx") { + debug!("Failed to parse macOS prefix from tag: {tag}"); + continue; + } + + // Skip the major and minor version numbers. + if parts + .next() + .and_then(|part| part.parse::().ok()) + .is_none() + { + debug!("Failed to parse macOS major version from tag: {tag}"); + continue; + }; + if parts + .next() + .and_then(|part| part.parse::().ok()) + .is_none() + { + debug!("Failed to parse macOS minor version from tag: {tag}"); + continue; + }; + + // Extract the architecture from the end of the tag. + let Some(arch) = parts.next() else { + debug!("Failed to parse macOS architecture from tag: {tag}"); + continue; + }; + + // Extract the architecture from the end of the tag. + let mut arch_marker = MarkerTree::FALSE; + let supported_architectures = match arch { + "universal" => { + // Allow any of: "x86_64", "i386", "ppc64", "ppc", "intel" + ["x86_64", "i386", "ppc64", "ppc", "intel"].iter() + } + "universal2" => { + // Allow any of: "x86_64", "arm64" + ["x86_64", "arm64"].iter() + } + "intel" => { + // Allow any of: "x86_64", "i386" + ["x86_64", "i386"].iter() + } + "x86_64" => { + // Allow only "x86_64" + ["x86_64"].iter() + } + "arm64" => { + // Allow only "arm64" + ["arm64"].iter() + } + "ppc64" => { + // Allow only "ppc64" + ["ppc64"].iter() + } + "ppc" => { + // Allow only "ppc" + ["ppc"].iter() + } + "i386" => { + // Allow only "i386" + ["i386"].iter() + } + _ => { + debug!("Unknown macOS architecture in wheel tag: {tag}"); + continue; + } + }; + for arch in supported_architectures { + arch_marker.or(MarkerTree::expression(MarkerExpression::String { + key: MarkerValueString::PlatformMachine, + operator: MarkerOperator::Equal, + value: ArcStr::from(*arch), + })); + } + tag_marker.and(arch_marker); + + marker.or(tag_marker); + } + + // Linux + tag => { + let mut tag_marker = MarkerTree::expression(MarkerExpression::String { + key: MarkerValueString::SysPlatform, + operator: MarkerOperator::Equal, + value: arcstr::literal!("linux"), + }); + + // Parse the architecture from the tag. + let arch = if let Some(arch) = tag.strip_prefix("linux_") { + arch + } else if let Some(arch) = tag.strip_prefix("manylinux1_") { + arch + } else if let Some(arch) = tag.strip_prefix("manylinux2010_") { + arch + } else if let Some(arch) = tag.strip_prefix("manylinux2014_") { + arch + } else if let Some(arch) = tag.strip_prefix("musllinux_") { + // Skip over the version tags (e.g., given `musllinux_1_2`, skip over `1` and `2`). + let mut parts = arch.splitn(3, '_'); + if parts + .next() + .and_then(|part| part.parse::().ok()) + .is_none() + { + debug!("Failed to parse musllinux major version from tag: {tag}"); + continue; + }; + if parts + .next() + .and_then(|part| part.parse::().ok()) + .is_none() + { + debug!("Failed to parse musllinux minor version from tag: {tag}"); + continue; + }; + let Some(arch) = parts.next() else { + debug!("Failed to parse musllinux architecture from tag: {tag}"); + continue; + }; + arch + } else if let Some(arch) = tag.strip_prefix("manylinux_") { + // Skip over the version tags (e.g., given `manylinux_2_17`, skip over `2` and `17`). + let mut parts = arch.splitn(3, '_'); + if parts + .next() + .and_then(|part| part.parse::().ok()) + .is_none() + { + debug!("Failed to parse manylinux major version from tag: {tag}"); + continue; + }; + if parts + .next() + .and_then(|part| part.parse::().ok()) + .is_none() + { + debug!("Failed to parse manylinux minor version from tag: {tag}"); + continue; + }; + let Some(arch) = parts.next() else { + debug!("Failed to parse manylinux architecture from tag: {tag}"); + continue; + }; + arch + } else { + continue; + }; + tag_marker.and(MarkerTree::expression(MarkerExpression::String { + key: MarkerValueString::PlatformMachine, + operator: MarkerOperator::Equal, + value: ArcStr::from(arch), + })); + + marker.or(tag_marker); } - _ => {} } } marker } + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use super::*; + + #[track_caller] + fn assert_markers(filename: &str, expected: &str) { + let filename = WheelFilename::from_str(filename).unwrap(); + assert_eq!( + implied_markers(&filename), + expected.parse::().unwrap() + ); + } + + #[test] + fn test_implied_markers() { + let filename = WheelFilename::from_str("example-1.0-py3-none-any.whl").unwrap(); + assert_eq!(implied_markers(&filename), MarkerTree::TRUE); + + assert_markers( + "example-1.0-cp310-cp310-win32.whl", + "sys_platform == 'win32' and platform_machine == 'x86'", + ); + assert_markers( + "numpy-2.2.1-cp313-cp313t-win_amd64.whl", + "sys_platform == 'win32' and platform_machine == 'x86_64'", + ); + assert_markers( + "numpy-2.2.1-cp313-cp313t-win_arm64.whl", + "sys_platform == 'win32' and platform_machine == 'arm64'", + ); + assert_markers( + "numpy-2.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", + "sys_platform == 'linux' and platform_machine == 'aarch64'", + ); + assert_markers( + "numpy-2.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", + "sys_platform == 'linux' and platform_machine == 'x86_64'", + ); + assert_markers( + "numpy-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", + "sys_platform == 'linux' and platform_machine == 'aarch64'", + ); + assert_markers( + "numpy-2.2.1-cp310-cp310-macosx_14_0_x86_64.whl", + "sys_platform == 'darwin' and platform_machine == 'x86_64'", + ); + assert_markers( + "numpy-2.2.1-cp310-cp310-macosx_10_9_x86_64.whl", + "sys_platform == 'darwin' and platform_machine == 'x86_64'", + ); + assert_markers( + "numpy-2.2.1-cp310-cp310-macosx_11_0_arm64.whl", + "sys_platform == 'darwin' and platform_machine == 'arm64'", + ); + } +} diff --git a/crates/uv-distribution-types/src/requested.rs b/crates/uv-distribution-types/src/requested.rs new file mode 100644 index 000000000000..b804a16adb88 --- /dev/null +++ b/crates/uv-distribution-types/src/requested.rs @@ -0,0 +1,71 @@ +use std::fmt::{Display, Formatter}; + +use crate::{ + Dist, DistributionId, DistributionMetadata, Identifier, InstalledDist, Name, ResourceId, + VersionOrUrlRef, +}; +use uv_normalize::PackageName; +use uv_pep440::Version; + +/// A distribution that can be requested during resolution. +/// +/// Either an already-installed distribution or a distribution that can be installed. +#[derive(Debug, Clone)] +#[allow(clippy::large_enum_variant)] +pub enum RequestedDist { + Installed(InstalledDist), + Installable(Dist), +} + +impl RequestedDist { + /// Returns the version of the distribution, if it is known. + pub fn version(&self) -> Option<&Version> { + match self { + Self::Installed(dist) => Some(dist.version()), + Self::Installable(dist) => dist.version(), + } + } +} + +impl Name for RequestedDist { + fn name(&self) -> &PackageName { + match self { + Self::Installable(dist) => dist.name(), + Self::Installed(dist) => dist.name(), + } + } +} + +impl DistributionMetadata for RequestedDist { + fn version_or_url(&self) -> VersionOrUrlRef { + match self { + Self::Installed(dist) => dist.version_or_url(), + Self::Installable(dist) => dist.version_or_url(), + } + } +} + +impl Identifier for RequestedDist { + fn distribution_id(&self) -> DistributionId { + match self { + Self::Installed(dist) => dist.distribution_id(), + Self::Installable(dist) => dist.distribution_id(), + } + } + + fn resource_id(&self) -> ResourceId { + match self { + Self::Installed(dist) => dist.resource_id(), + Self::Installable(dist) => dist.resource_id(), + } + } +} + +impl Display for RequestedDist { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Installed(dist) => dist.fmt(f), + Self::Installable(dist) => dist.fmt(f), + } + } +} diff --git a/crates/uv-distribution/src/distribution_database.rs b/crates/uv-distribution/src/distribution_database.rs index 95b52b516051..79dc85187090 100644 --- a/crates/uv-distribution/src/distribution_database.rs +++ b/crates/uv-distribution/src/distribution_database.rs @@ -21,13 +21,14 @@ use uv_client::{ }; use uv_distribution_filename::WheelFilename; use uv_distribution_types::{ - BuildableSource, BuiltDist, Dist, FileLocation, HashPolicy, Hashed, Name, SourceDist, + BuildableSource, BuiltDist, Dist, FileLocation, HashPolicy, Hashed, InstalledDist, Name, + SourceDist, }; use uv_extract::hash::Hasher; use uv_fs::write_atomic; use uv_platform_tags::Tags; use uv_pypi_types::HashDigest; -use uv_types::BuildContext; +use uv_types::{BuildContext, BuildStack}; use crate::archive::Archive; use crate::locks::Locks; @@ -70,13 +71,21 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> { } } - /// Set the [`Reporter`] to use for this source distribution fetcher. + /// Set the build stack to use for the [`DistributionDatabase`]. #[must_use] - pub fn with_reporter(self, reporter: impl Reporter + 'static) -> Self { - let reporter = Arc::new(reporter); + pub fn with_build_stack(self, build_stack: &'a BuildStack) -> Self { Self { - reporter: Some(reporter.clone()), - builder: self.builder.with_reporter(reporter), + builder: self.builder.with_build_stack(build_stack), + ..self + } + } + + /// Set the [`Reporter`] to use for the [`DistributionDatabase`]. + #[must_use] + pub fn with_reporter(self, reporter: Arc) -> Self { + Self { + builder: self.builder.with_reporter(reporter.clone()), + reporter: Some(reporter), ..self } } @@ -115,6 +124,32 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> { } } + /// Either fetch the only wheel metadata (directly from the index or with range requests) or + /// fetch and build the source distribution. + /// + /// While hashes will be generated in some cases, hash-checking is only enforced for source + /// distributions, and should be enforced by the caller for wheels. + #[instrument(skip_all, fields(%dist))] + pub async fn get_installed_metadata( + &self, + dist: &InstalledDist, + ) -> Result { + // If the metadata was provided by the user directly, prefer it. + if let Some(metadata) = self + .build_context + .dependency_metadata() + .get(dist.name(), Some(dist.version())) + { + return Ok(ArchiveMetadata::from_metadata23(metadata.clone())); + } + + let metadata = dist + .metadata() + .map_err(|err| Error::ReadInstalled(Box::new(dist.clone()), err))?; + + Ok(ArchiveMetadata::from_metadata23(metadata)) + } + /// Either fetch the only wheel metadata (directly from the index or with range requests) or /// fetch and build the source distribution. /// @@ -151,7 +186,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> { FileLocation::RelativeUrl(base, url) => { uv_pypi_types::base_url_join_relative(base, url)? } - FileLocation::AbsoluteUrl(url) => url.to_url(), + FileLocation::AbsoluteUrl(url) => url.to_url()?, }; // Create a cache entry for the wheel. @@ -369,22 +404,28 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> { return Ok(ArchiveMetadata::from_metadata23(metadata.clone())); } - // If hash generation is enabled, and the distribution isn't hosted on an index, get the + // If hash generation is enabled, and the distribution isn't hosted on a registry, get the // entire wheel to ensure that the hashes are included in the response. If the distribution // is hosted on an index, the hashes will be included in the simple metadata response. // For hash _validation_, callers are expected to enforce the policy when retrieving the // wheel. + // + // Historically, for `uv pip compile --universal`, we also generate hashes for + // registry-based distributions when the relevant registry doesn't provide them. This was + // motivated by `--find-links`. We continue that behavior (under `HashGeneration::All`) for + // backwards compatibility, but it's a little dubious, since we're only hashing _one_ + // distribution here (as opposed to hashing all distributions for the version), and it may + // not even be a compatible distribution! + // // TODO(charlie): Request the hashes via a separate method, to reduce the coupling in this API. - if hashes.is_generate() { - if dist.file().map_or(true, |file| file.hashes.is_empty()) { - let wheel = self.get_wheel(dist, hashes).await?; - let metadata = wheel.metadata()?; - let hashes = wheel.hashes; - return Ok(ArchiveMetadata { - metadata: Metadata::from_metadata23(metadata), - hashes, - }); - } + if hashes.is_generate(dist) { + let wheel = self.get_wheel(dist, hashes).await?; + let metadata = wheel.metadata()?; + let hashes = wheel.hashes; + return Ok(ArchiveMetadata { + metadata: Metadata::from_metadata23(metadata), + hashes, + }); } let result = self diff --git a/crates/uv-distribution/src/error.rs b/crates/uv-distribution/src/error.rs index 217682501bd8..0727b8896927 100644 --- a/crates/uv-distribution/src/error.rs +++ b/crates/uv-distribution/src/error.rs @@ -8,7 +8,7 @@ use zip::result::ZipError; use crate::metadata::MetadataError; use uv_client::WrappedReqwestError; use uv_distribution_filename::WheelFilenameError; -use uv_distribution_types::IsBuildBackendError; +use uv_distribution_types::{InstalledDist, InstalledDistError, IsBuildBackendError}; use uv_fs::Simplified; use uv_normalize::PackageName; use uv_pep440::{Version, VersionSpecifiers}; @@ -21,11 +21,11 @@ pub enum Error { NoBuild, // Network error - #[error("Failed to parse URL: {0}")] - Url(String, #[source] url::ParseError), #[error("Expected an absolute path, but received: {}", _0.user_display())] RelativePath(PathBuf), #[error(transparent)] + InvalidUrl(#[from] uv_distribution_types::ToUrlError), + #[error(transparent)] ParsedUrl(#[from] ParsedUrlError), #[error(transparent)] JoinRelativeUrl(#[from] uv_pypi_types::JoinRelativeError), @@ -82,6 +82,8 @@ pub enum Error { Metadata(#[from] uv_pypi_types::MetadataError), #[error("Failed to read metadata: `{}`", _0.user_display())] WheelMetadata(PathBuf, #[source] Box), + #[error("Failed to read metadata from installed package `{0}`")] + ReadInstalled(Box, #[source] InstalledDistError), #[error("Failed to read zip archive from built wheel")] Zip(#[from] ZipError), #[error("Source distribution directory contains neither readable `pyproject.toml` nor `setup.py`: `{}`", _0.user_display())] diff --git a/crates/uv-distribution/src/reporter.rs b/crates/uv-distribution/src/reporter.rs index e8e8b40d5237..9be3a5fa4355 100644 --- a/crates/uv-distribution/src/reporter.rs +++ b/crates/uv-distribution/src/reporter.rs @@ -29,15 +29,18 @@ pub trait Reporter: Send + Sync { fn on_download_complete(&self, name: &PackageName, id: usize); } -/// A facade for converting from [`Reporter`] to [`uv_git::Reporter`]. -pub(crate) struct Facade { - reporter: Arc, +impl dyn Reporter { + /// Converts this reporter to a [`uv_git::Reporter`]. + pub(crate) fn into_git_reporter(self: Arc) -> Arc { + Arc::new(Facade { + reporter: self.clone(), + }) + } } -impl From> for Facade { - fn from(reporter: Arc) -> Self { - Self { reporter } - } +/// A facade for converting from [`Reporter`] to [`uv_git::Reporter`]. +struct Facade { + reporter: Arc, } impl uv_git::Reporter for Facade { diff --git a/crates/uv-distribution/src/source/mod.rs b/crates/uv-distribution/src/source/mod.rs index 2beae3ca46ef..95d3f89d14bb 100644 --- a/crates/uv-distribution/src/source/mod.rs +++ b/crates/uv-distribution/src/source/mod.rs @@ -1,5 +1,13 @@ //! Fetch and build source distributions from remote sources. +// This is to squash warnings about `|r| r.into_git_reporter()`. Clippy wants +// me to eta-reduce that and write it as +// `<(dyn reporter::Reporter + 'static)>::into_git_reporter` +// instead. But that's a monster. On the other hand, applying this suppression +// instruction more granularly is annoying. So we just slap it on the module +// for now. ---AG +#![allow(clippy::redundant_closure_for_method_calls)] + use std::borrow::Cow; use std::ops::Bound; use std::path::{Path, PathBuf}; @@ -9,7 +17,6 @@ use std::sync::Arc; use crate::distribution_database::ManagedClient; use crate::error::Error; use crate::metadata::{ArchiveMetadata, GitWorkspaceMember, Metadata}; -use crate::reporter::Facade; use crate::source::built_wheel_metadata::BuiltWheelMetadata; use crate::source::revision::Revision; use crate::{Reporter, RequiresDist}; @@ -38,7 +45,7 @@ use uv_normalize::PackageName; use uv_pep440::{release_specifiers_to_ranges, Version}; use uv_platform_tags::Tags; use uv_pypi_types::{HashAlgorithm, HashDigest, Metadata12, RequiresTxt, ResolutionMetadata}; -use uv_types::{BuildContext, SourceBuildTrait}; +use uv_types::{BuildContext, BuildStack, SourceBuildTrait}; use zip::ZipArchive; mod built_wheel_metadata; @@ -47,6 +54,7 @@ mod revision; /// Fetch and build a source distribution from a remote source, or from a local cache. pub(crate) struct SourceDistributionBuilder<'a, T: BuildContext> { build_context: &'a T, + build_stack: Option<&'a BuildStack>, reporter: Option>, } @@ -67,11 +75,21 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { pub(crate) fn new(build_context: &'a T) -> Self { Self { build_context, + build_stack: None, reporter: None, } } - /// Set the [`Reporter`] to use for this source distribution fetcher. + /// Set the [`BuildStack`] to use for the [`SourceDistributionBuilder`]. + #[must_use] + pub(crate) fn with_build_stack(self, build_stack: &'a BuildStack) -> Self { + Self { + build_stack: Some(build_stack), + ..self + } + } + + /// Set the [`Reporter`] to use for the [`SourceDistributionBuilder`]. #[must_use] pub(crate) fn with_reporter(self, reporter: Arc) -> Self { Self { @@ -103,7 +121,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { FileLocation::RelativeUrl(base, url) => { uv_pypi_types::base_url_join_relative(base, url)? } - FileLocation::AbsoluteUrl(url) => url.to_url(), + FileLocation::AbsoluteUrl(url) => url.to_url()?, }; // If the URL is a file URL, use the local path directly. @@ -252,7 +270,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { FileLocation::RelativeUrl(base, url) => { uv_pypi_types::base_url_join_relative(base, url)? } - FileLocation::AbsoluteUrl(url) => url.to_url(), + FileLocation::AbsoluteUrl(url) => url.to_url()?, }; // If the URL is a file URL, use the local path directly. @@ -1319,7 +1337,9 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { resource.git, client.unmanaged.uncached_client(resource.url).clone(), self.build_context.cache().bucket(CacheBucket::Git), - self.reporter.clone().map(Facade::from), + self.reporter + .clone() + .map(|reporter| reporter.into_git_reporter()), ) .await?; @@ -1416,7 +1436,9 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { resource.git, client.unmanaged.uncached_client(resource.url).clone(), self.build_context.cache().bucket(CacheBucket::Git), - self.reporter.clone().map(Facade::from), + self.reporter + .clone() + .map(|reporter| reporter.into_git_reporter()), ) .await?; @@ -1592,7 +1614,9 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { &source.git, client.unmanaged.uncached_client(&source.url).clone(), self.build_context.cache().bucket(CacheBucket::Git), - self.reporter.clone().map(Facade::from), + self.reporter + .clone() + .map(|reporter| reporter.into_git_reporter()), ) .await?; } @@ -1603,7 +1627,9 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { source.git, client.unmanaged.uncached_client(source.url).clone(), self.build_context.cache().bucket(CacheBucket::Git), - self.reporter.clone().map(Facade::from), + self.reporter + .clone() + .map(|reporter| reporter.into_git_reporter()), ) .await?; } @@ -1898,6 +1924,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { BuildKind::Wheel }, BuildOutput::Debug, + self.build_stack.cloned().unwrap_or_default(), ) .await .map_err(|err| Error::Build(err.into()))? @@ -1974,6 +2001,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { BuildKind::Wheel }, BuildOutput::Debug, + self.build_stack.cloned().unwrap_or_default(), ) .await .map_err(|err| Error::Build(err.into()))?; diff --git a/crates/uv-extract/src/stream.rs b/crates/uv-extract/src/stream.rs index 483be2f50588..49ae138c451d 100644 --- a/crates/uv-extract/src/stream.rs +++ b/crates/uv-extract/src/stream.rs @@ -139,8 +139,8 @@ pub async fn unzip( /// Unpack the given tar archive into the destination directory. /// /// This is equivalent to `archive.unpack_in(dst)`, but it also preserves the executable bit. -async fn untar_in<'a>( - mut archive: tokio_tar::Archive<&'a mut (dyn tokio::io::AsyncRead + Unpin)>, +async fn untar_in( + mut archive: tokio_tar::Archive<&'_ mut (dyn tokio::io::AsyncRead + Unpin)>, dst: &Path, ) -> std::io::Result<()> { let mut entries = archive.entries()?; diff --git a/crates/uv-fs/Cargo.toml b/crates/uv-fs/Cargo.toml index a77c71e10907..8c58022ac12c 100644 --- a/crates/uv-fs/Cargo.toml +++ b/crates/uv-fs/Cargo.toml @@ -38,9 +38,9 @@ winsafe = { workspace = true } rustix = { workspace = true } [target.'cfg(windows)'.dependencies] -backoff = { workspace = true } +backon = { workspace = true } junction = { workspace = true } [features] default = [] -tokio = ["dep:tokio", "fs-err/tokio", "backoff/tokio"] +tokio = ["dep:tokio", "fs-err/tokio"] diff --git a/crates/uv-fs/src/lib.rs b/crates/uv-fs/src/lib.rs index 31abae0e3ce1..8f6cbc9377e4 100644 --- a/crates/uv-fs/src/lib.rs +++ b/crates/uv-fs/src/lib.rs @@ -187,10 +187,15 @@ pub fn copy_atomic_sync(from: impl AsRef, to: impl AsRef) -> std::io } #[cfg(windows)] -fn backoff_file_move() -> backoff::ExponentialBackoff { - backoff::ExponentialBackoffBuilder::default() - .with_initial_interval(std::time::Duration::from_millis(10)) - .with_max_elapsed_time(Some(std::time::Duration::from_secs(10))) +fn backoff_file_move() -> backon::ExponentialBackoff { + use backon::BackoffBuilder; + // This amounts to 10 total seconds of trying the operation. + // We start at 10 milliseconds and try 9 times, doubling each time, so the last try will take + // about 10*(2^9) milliseconds ~= 5 seconds. All other attempts combined should equal + // the length of the last attempt (because it's a sum of powers of 2), so 10 seconds overall. + backon::ExponentialBuilder::default() + .with_min_delay(std::time::Duration::from_millis(10)) + .with_max_times(9) .build() } @@ -202,6 +207,7 @@ pub async fn rename_with_retry( ) -> Result<(), std::io::Error> { #[cfg(windows)] { + use backon::Retryable; // On Windows, antivirus software can lock files temporarily, making them inaccessible. // This is most common for DLLs, and the common suggestion is to retry the operation with // some backoff. @@ -210,23 +216,21 @@ pub async fn rename_with_retry( let from = from.as_ref(); let to = to.as_ref(); - let backoff = backoff_file_move(); - backoff::future::retry(backoff, || async move { - match fs_err::rename(from, to) { - Ok(()) => Ok(()), - Err(err) if err.kind() == std::io::ErrorKind::PermissionDenied => { - warn!( - "Retrying rename from {} to {} due to transient error: {}", - from.display(), - to.display(), - err - ); - Err(backoff::Error::transient(err)) - } - Err(err) => Err(backoff::Error::permanent(err)), - } - }) - .await + let rename = || async { fs_err::rename(from, to) }; + + rename + .retry(backoff_file_move()) + .sleep(tokio::time::sleep) + .when(|e| e.kind() == std::io::ErrorKind::PermissionDenied) + .notify(|err, _dur| { + warn!( + "Retrying rename from {} to {} due to transient error: {}", + from.display(), + to.display(), + err + ); + }) + .await } #[cfg(not(windows))] { @@ -241,6 +245,7 @@ pub fn rename_with_retry_sync( ) -> Result<(), std::io::Error> { #[cfg(windows)] { + use backon::BlockingRetryable; // On Windows, antivirus software can lock files temporarily, making them inaccessible. // This is most common for DLLs, and the common suggestion is to retry the operation with // some backoff. @@ -248,32 +253,32 @@ pub fn rename_with_retry_sync( // See: & let from = from.as_ref(); let to = to.as_ref(); + let rename = || fs_err::rename(from, to); - let backoff = backoff_file_move(); - backoff::retry(backoff, || match fs_err::rename(from, to) { - Ok(()) => Ok(()), - Err(err) if err.kind() == std::io::ErrorKind::PermissionDenied => { + rename + .retry(backoff_file_move()) + .sleep(std::thread::sleep) + .when(|err| err.kind() == std::io::ErrorKind::PermissionDenied) + .notify(|err, _dur| { warn!( "Retrying rename from {} to {} due to transient error: {}", from.display(), to.display(), err ); - Err(backoff::Error::transient(err)) - } - Err(err) => Err(backoff::Error::permanent(err)), - }) - .map_err(|err| { - std::io::Error::new( - std::io::ErrorKind::Other, - format!( - "Failed to rename {} to {}: {}", - from.display(), - to.display(), - err - ), - ) - }) + }) + .call() + .map_err(|err| { + std::io::Error::new( + std::io::ErrorKind::Other, + format!( + "Failed to rename {} to {}: {}", + from.display(), + to.display(), + err + ), + ) + }) } #[cfg(not(windows))] { @@ -281,6 +286,15 @@ pub fn rename_with_retry_sync( } } +/// Why a file persist failed +#[cfg(windows)] +enum PersistRetryError { + /// Something went wrong while persisting, maybe retry (contains error message) + Persist(String), + /// Something went wrong trying to retrieve the file to persist, we must bail + LostState, +} + /// Persist a `NamedTempFile`, retrying (on Windows) if it fails due to transient operating system errors, in a synchronous context. pub async fn persist_with_retry( from: NamedTempFile, @@ -288,6 +302,7 @@ pub async fn persist_with_retry( ) -> Result<(), std::io::Error> { #[cfg(windows)] { + use backon::Retryable; // On Windows, antivirus software can lock files temporarily, making them inaccessible. // This is most common for DLLs, and the common suggestion is to retry the operation with // some backoff. @@ -300,52 +315,55 @@ pub async fn persist_with_retry( // So we will update the `from` optional value in safe and borrow-checker friendly way every retry // Allows us to use the NamedTempFile inside a FnMut closure used for backoff::retry let mut from = Some(from); - - let backoff = backoff_file_move(); - let persisted = backoff::future::retry(backoff, move || { + let persist = move || { // Needed because we cannot move out of `from`, a captured variable in an `FnMut` closure, and then pass it to the async move block - let mut from = from.take(); + let mut from: Option = from.take(); async move { if let Some(file) = from.take() { file.persist(to).map_err(|err| { let error_message = err.to_string(); - warn!( - "Retrying to persist temporary file to {}: {}", - to.display(), - error_message - ); - // Set back the NamedTempFile returned back by the Error from = Some(err.file); - - backoff::Error::transient(std::io::Error::new( - std::io::ErrorKind::Other, - format!( - "Failed to persist temporary file to {}: {}", - to.display(), - error_message - ), - )) + PersistRetryError::Persist(error_message) }) } else { - Err(backoff::Error::permanent(std::io::Error::new( - std::io::ErrorKind::Other, - format!( - "Failed to retrieve temporary file while trying to persist to {}", - to.display() - ), - ))) + Err(PersistRetryError::LostState) } } - }) - .await; + }; + + let persisted = persist + .retry(backoff_file_move()) + .sleep(tokio::time::sleep) + .when(|err| matches!(err, PersistRetryError::Persist(_))) + .notify(|err, _dur| { + if let PersistRetryError::Persist(error_message) = err { + warn!( + "Retrying to persist temporary file to {}: {}", + to.display(), + error_message, + ); + }; + }) + .await; match persisted { Ok(_) => Ok(()), - Err(err) => Err(std::io::Error::new( + Err(PersistRetryError::Persist(error_message)) => Err(std::io::Error::new( std::io::ErrorKind::Other, - err.to_string(), + format!( + "Failed to persist temporary file to {}: {}", + to.display(), + error_message, + ), + )), + Err(PersistRetryError::LostState) => Err(std::io::Error::new( + std::io::ErrorKind::Other, + format!( + "Failed to retrieve temporary file while trying to persist to {}", + to.display() + ), )), } } @@ -362,6 +380,7 @@ pub fn persist_with_retry_sync( ) -> Result<(), std::io::Error> { #[cfg(windows)] { + use backon::BlockingRetryable; // On Windows, antivirus software can lock files temporarily, making them inaccessible. // This is most common for DLLs, and the common suggestion is to retry the operation with // some backoff. @@ -374,46 +393,51 @@ pub fn persist_with_retry_sync( // So we will update the `from` optional value in safe and borrow-checker friendly way every retry // Allows us to use the NamedTempFile inside a FnMut closure used for backoff::retry let mut from = Some(from); - - let backoff = backoff_file_move(); - let persisted = backoff::retry(backoff, move || { + let persist = || { + // Needed because we cannot move out of `from`, a captured variable in an `FnMut` closure, and then pass it to the async move block if let Some(file) = from.take() { file.persist(to).map_err(|err| { let error_message = err.to_string(); - warn!( - "Retrying to persist temporary file to {}: {}", - to.display(), - error_message - ); - // Set back the NamedTempFile returned back by the Error from = Some(err.file); - - backoff::Error::transient(std::io::Error::new( - std::io::ErrorKind::Other, - format!( - "Failed to persist temporary file to {}: {}", - to.display(), - error_message - ), - )) + PersistRetryError::Persist(error_message) }) } else { - Err(backoff::Error::permanent(std::io::Error::new( - std::io::ErrorKind::Other, - format!( - "Failed to retrieve temporary file while trying to persist to {}", - to.display() - ), - ))) + Err(PersistRetryError::LostState) } - }); + }; + + let persisted = persist + .retry(backoff_file_move()) + .sleep(std::thread::sleep) + .when(|err| matches!(err, PersistRetryError::Persist(_))) + .notify(|err, _dur| { + if let PersistRetryError::Persist(error_message) = err { + warn!( + "Retrying to persist temporary file to {}: {}", + to.display(), + error_message, + ); + }; + }) + .call(); match persisted { Ok(_) => Ok(()), - Err(err) => Err(std::io::Error::new( + Err(PersistRetryError::Persist(error_message)) => Err(std::io::Error::new( + std::io::ErrorKind::Other, + format!( + "Failed to persist temporary file to {}: {}", + to.display(), + error_message, + ), + )), + Err(PersistRetryError::LostState) => Err(std::io::Error::new( std::io::ErrorKind::Other, - err.to_string(), + format!( + "Failed to retrieve temporary file while trying to persist to {}", + to.display() + ), )), } } @@ -492,7 +516,7 @@ pub fn is_temporary(path: impl AsRef) -> bool { path.as_ref() .file_name() .and_then(|name| name.to_str()) - .map_or(false, |name| name.starts_with(".tmp")) + .is_some_and(|name| name.starts_with(".tmp")) } /// A file lock that is automatically released when dropped. @@ -564,7 +588,7 @@ impl LockedFile { impl Drop for LockedFile { fn drop(&mut self) { - if let Err(err) = self.0.file().unlock() { + if let Err(err) = fs2::FileExt::unlock(self.0.file()) { error!( "Failed to unlock {}; program may be stuck: {}", self.0.path().display(), diff --git a/crates/uv-git/src/resolver.rs b/crates/uv-git/src/resolver.rs index e0636ec243d3..741ee7acb9d2 100644 --- a/crates/uv-git/src/resolver.rs +++ b/crates/uv-git/src/resolver.rs @@ -44,7 +44,7 @@ impl GitResolver { url: &GitUrl, client: ClientWithMiddleware, cache: PathBuf, - reporter: Option, + reporter: Option>, ) -> Result { debug!("Resolving source distribution from Git: {url}"); @@ -88,7 +88,7 @@ impl GitResolver { url: &GitUrl, client: ClientWithMiddleware, cache: PathBuf, - reporter: Option, + reporter: Option>, ) -> Result { debug!("Fetching source distribution from Git: {url}"); @@ -138,7 +138,7 @@ impl GitResolver { /// For example, given a Git dependency with a reference to a branch or tag, return a URL /// with a precise reference to the current commit of that branch or tag. /// - /// This method takes into account various normalizations that are independent from the Git + /// This method takes into account various normalizations that are independent of the Git /// layer. For example: removing `#subdirectory=pkg_dir`-like fragments, and removing `git+` /// prefix kinds. /// diff --git a/crates/uv-git/src/source.rs b/crates/uv-git/src/source.rs index 5b475af90fd6..eff21786e53c 100644 --- a/crates/uv-git/src/source.rs +++ b/crates/uv-git/src/source.rs @@ -4,6 +4,7 @@ use std::borrow::Cow; use std::path::{Path, PathBuf}; +use std::sync::Arc; use anyhow::Result; use reqwest_middleware::ClientWithMiddleware; @@ -24,11 +25,11 @@ pub struct GitSource { /// The path to the Git source database. cache: PathBuf, /// The reporter to use for this source. - reporter: Option>, + reporter: Option>, } impl GitSource { - /// Initialize a new Git source. + /// Initialize a [`GitSource`] with the given Git URL, HTTP client, and cache path. pub fn new( git: GitUrl, client: impl Into, @@ -42,11 +43,11 @@ impl GitSource { } } - /// Set the [`Reporter`] to use for this `GIt` source. + /// Set the [`Reporter`] to use for the [`GitSource`]. #[must_use] - pub fn with_reporter(self, reporter: impl Reporter + 'static) -> Self { + pub fn with_reporter(self, reporter: Arc) -> Self { Self { - reporter: Some(Box::new(reporter)), + reporter: Some(reporter), ..self } } diff --git a/crates/uv-install-wheel/src/wheel.rs b/crates/uv-install-wheel/src/wheel.rs index 8218f75ae758..9e5698e0db8a 100644 --- a/crates/uv-install-wheel/src/wheel.rs +++ b/crates/uv-install-wheel/src/wheel.rs @@ -34,7 +34,7 @@ fn get_script_launcher(entry_point: &Script, shebang: &str) -> String { let import_name = entry_point.import_name(); format!( - r##"{shebang} + r#"{shebang} # -*- coding: utf-8 -*- import re import sys @@ -42,7 +42,7 @@ from {module} import {import_name} if __name__ == "__main__": sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0]) sys.exit({function}()) -"## +"# ) } diff --git a/crates/uv-installer/src/installer.rs b/crates/uv-installer/src/installer.rs index f8fa0cbc0980..e2db41b4ce98 100644 --- a/crates/uv-installer/src/installer.rs +++ b/crates/uv-installer/src/installer.rs @@ -1,21 +1,22 @@ +use std::convert; +use std::sync::{Arc, LazyLock}; + use anyhow::{Context, Error, Result}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; -use std::convert; -use std::sync::LazyLock; use tokio::sync::oneshot; use tracing::instrument; -use uv_install_wheel::{linker::LinkMode, Layout}; use uv_cache::Cache; use uv_configuration::RAYON_INITIALIZE; use uv_distribution_types::CachedDist; +use uv_install_wheel::{linker::LinkMode, Layout}; use uv_python::PythonEnvironment; pub struct Installer<'a> { venv: &'a PythonEnvironment, link_mode: LinkMode, cache: Option<&'a Cache>, - reporter: Option>, + reporter: Option>, installer_name: Option, installer_metadata: bool, } @@ -50,9 +51,9 @@ impl<'a> Installer<'a> { /// Set the [`Reporter`] to use for this installer. #[must_use] - pub fn with_reporter(self, reporter: impl Reporter + 'static) -> Self { + pub fn with_reporter(self, reporter: Arc) -> Self { Self { - reporter: Some(Box::new(reporter)), + reporter: Some(reporter), ..self } } @@ -66,7 +67,7 @@ impl<'a> Installer<'a> { } } - /// Set the whether to link Uv specific files in dist-info + /// Set whether to install uv-specifier files in the dist-info directory. #[must_use] pub fn with_installer_metadata(self, installer_metadata: bool) -> Self { Self { @@ -151,7 +152,7 @@ fn install( layout: Layout, installer_name: Option, link_mode: LinkMode, - reporter: Option>, + reporter: Option>, relocatable: bool, installer_metadata: bool, ) -> Result> { diff --git a/crates/uv-installer/src/plan.rs b/crates/uv-installer/src/plan.rs index bbbd227fdd82..4207e3bc1711 100644 --- a/crates/uv-installer/src/plan.rs +++ b/crates/uv-installer/src/plan.rs @@ -66,11 +66,7 @@ impl<'a> Planner<'a> { for dist in self.resolution.distributions() { // Check if the package should be reinstalled. - let reinstall = match reinstall { - Reinstall::None => false, - Reinstall::All => true, - Reinstall::Packages(packages) => packages.contains(dist.name()), - }; + let reinstall = reinstall.contains(dist.name()); // Check if installation of a binary version of the package should be allowed. let no_binary = build_options.no_binary_package(dist.name()); diff --git a/crates/uv-installer/src/preparer.rs b/crates/uv-installer/src/preparer.rs index fac49057025c..4b53654d0fb2 100644 --- a/crates/uv-installer/src/preparer.rs +++ b/crates/uv-installer/src/preparer.rs @@ -48,15 +48,16 @@ impl<'a, Context: BuildContext> Preparer<'a, Context> { /// Set the [`Reporter`] to use for operations. #[must_use] - pub fn with_reporter(self, reporter: impl Reporter + 'static) -> Self { - let reporter: Arc = Arc::new(reporter); + pub fn with_reporter(self, reporter: Arc) -> Self { Self { tags: self.tags, cache: self.cache, hashes: self.hashes, build_options: self.build_options, - database: self.database.with_reporter(Facade::from(reporter.clone())), - reporter: Some(reporter.clone()), + database: self + .database + .with_reporter(reporter.clone().into_distribution_reporter()), + reporter: Some(reporter), } } @@ -220,6 +221,8 @@ pub enum Error { DerivationChain, #[source] uv_distribution::Error, ), + #[error("Cyclic build dependency detected for `{0}`")] + CyclicBuildDependency(PackageName), #[error("Unzip failed in another thread: {0}")] Thread(String), } @@ -230,7 +233,7 @@ impl Error { let chain = DerivationChain::from_resolution(resolution, (&dist).into()).unwrap_or_default(); Self::Dist( - DistErrorKind::from_dist_and_err(&dist, &err), + DistErrorKind::from_dist(&dist, &err), Box::new(dist), chain, err, @@ -269,15 +272,20 @@ pub trait Reporter: Send + Sync { fn on_checkout_complete(&self, url: &Url, rev: &str, index: usize); } -/// A facade for converting from [`Reporter`] to [`uv_git::Reporter`]. -struct Facade { - reporter: Arc, +impl dyn Reporter { + /// Converts this reporter to a [`uv_distribution::Reporter`]. + pub(crate) fn into_distribution_reporter( + self: Arc, + ) -> Arc { + Arc::new(Facade { + reporter: self.clone(), + }) + } } -impl From> for Facade { - fn from(reporter: Arc) -> Self { - Self { reporter } - } +/// A facade for converting from [`Reporter`] to [`uv_distribution::Reporter`]. +struct Facade { + reporter: Arc, } impl uv_distribution::Reporter for Facade { diff --git a/crates/uv-normalize/Cargo.toml b/crates/uv-normalize/Cargo.toml index 8f4db6a15d82..d40faf3d72b9 100644 --- a/crates/uv-normalize/Cargo.toml +++ b/crates/uv-normalize/Cargo.toml @@ -11,6 +11,11 @@ doctest = false workspace = true [dependencies] +uv-small-str = { workspace = true } + rkyv = { workspace = true } schemars = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"] } + +[features] +schemars = ["dep:schemars", "uv-small-str/schemars"] diff --git a/crates/uv-normalize/src/extra_name.rs b/crates/uv-normalize/src/extra_name.rs index f23431178d2c..11a6707ef127 100644 --- a/crates/uv-normalize/src/extra_name.rs +++ b/crates/uv-normalize/src/extra_name.rs @@ -4,6 +4,8 @@ use std::str::FromStr; use serde::{Deserialize, Deserializer, Serialize}; +use uv_small_str::SmallString; + use crate::{validate_and_normalize_owned, validate_and_normalize_ref, InvalidNameError}; /// The normalized name of an extra dependency. @@ -14,9 +16,9 @@ use crate::{validate_and_normalize_owned, validate_and_normalize_ref, InvalidNam /// See: /// - /// - -#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -pub struct ExtraName(String); +pub struct ExtraName(SmallString); impl ExtraName { /// Create a validated, normalized extra name. diff --git a/crates/uv-normalize/src/group_name.rs b/crates/uv-normalize/src/group_name.rs index 72aa898ec729..a11ed01bb73a 100644 --- a/crates/uv-normalize/src/group_name.rs +++ b/crates/uv-normalize/src/group_name.rs @@ -5,6 +5,8 @@ use std::sync::LazyLock; use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use uv_small_str::SmallString; + use crate::{validate_and_normalize_owned, validate_and_normalize_ref, InvalidNameError}; /// The normalized name of a dependency group. @@ -12,9 +14,9 @@ use crate::{validate_and_normalize_owned, validate_and_normalize_ref, InvalidNam /// See: /// - /// - -#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -pub struct GroupName(String); +pub struct GroupName(SmallString); impl GroupName { /// Create a validated, normalized group name. diff --git a/crates/uv-normalize/src/lib.rs b/crates/uv-normalize/src/lib.rs index 583b17bcc6af..06234db96a71 100644 --- a/crates/uv-normalize/src/lib.rs +++ b/crates/uv-normalize/src/lib.rs @@ -6,25 +6,36 @@ pub use extra_name::ExtraName; pub use group_name::{GroupName, DEV_DEPENDENCIES}; pub use package_name::PackageName; +use uv_small_str::SmallString; + mod dist_info_name; mod extra_name; mod group_name; mod package_name; /// Validate and normalize an owned package or extra name. -pub(crate) fn validate_and_normalize_owned(name: String) -> Result { +pub(crate) fn validate_and_normalize_owned(name: String) -> Result { if is_normalized(&name)? { - Ok(name) + Ok(SmallString::from(name)) } else { - validate_and_normalize_ref(name) + Ok(SmallString::from(normalize(&name)?)) } } /// Validate and normalize an unowned package or extra name. pub(crate) fn validate_and_normalize_ref( name: impl AsRef, -) -> Result { +) -> Result { let name = name.as_ref(); + if is_normalized(name)? { + Ok(SmallString::from(name)) + } else { + Ok(SmallString::from(normalize(name)?)) + } +} + +/// Normalize an unowned package or extra name. +fn normalize(name: &str) -> Result { let mut normalized = String::with_capacity(name.len()); let mut last = None; @@ -136,9 +147,14 @@ mod tests { "FrIeNdLy-._.-bArD", ]; for input in inputs { - assert_eq!(validate_and_normalize_ref(input).unwrap(), "friendly-bard"); assert_eq!( - validate_and_normalize_owned(input.to_string()).unwrap(), + validate_and_normalize_ref(input).unwrap().as_ref(), + "friendly-bard" + ); + assert_eq!( + validate_and_normalize_owned(input.to_string()) + .unwrap() + .as_ref(), "friendly-bard" ); } @@ -169,9 +185,11 @@ mod tests { // Unchanged let unchanged = ["friendly-bard", "1okay", "okay2"]; for input in unchanged { - assert_eq!(validate_and_normalize_ref(input).unwrap(), input); + assert_eq!(validate_and_normalize_ref(input).unwrap().as_ref(), input); assert_eq!( - validate_and_normalize_owned(input.to_string()).unwrap(), + validate_and_normalize_owned(input.to_string()) + .unwrap() + .as_ref(), input ); assert!(is_normalized(input).unwrap()); diff --git a/crates/uv-normalize/src/package_name.rs b/crates/uv-normalize/src/package_name.rs index d3067f17aec7..59d7ea912e5f 100644 --- a/crates/uv-normalize/src/package_name.rs +++ b/crates/uv-normalize/src/package_name.rs @@ -1,8 +1,11 @@ use std::borrow::Cow; +use std::cmp::PartialEq; use std::str::FromStr; use serde::{Deserialize, Deserializer, Serialize}; +use uv_small_str::SmallString; + use crate::{validate_and_normalize_owned, validate_and_normalize_ref, InvalidNameError}; /// The normalized name of a package. @@ -13,7 +16,6 @@ use crate::{validate_and_normalize_owned, validate_and_normalize_ref, InvalidNam /// See: #[derive( Debug, - Default, Clone, PartialEq, Eq, @@ -27,7 +29,7 @@ use crate::{validate_and_normalize_owned, validate_and_normalize_ref, InvalidNam )] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[rkyv(derive(Debug))] -pub struct PackageName(String); +pub struct PackageName(SmallString); impl PackageName { /// Create a validated, normalized package name. @@ -56,7 +58,7 @@ impl PackageName { Cow::Owned(owned_string) } else { - Cow::Borrowed(self.0.as_str()) + Cow::Borrowed(self.0.as_ref()) } } diff --git a/crates/uv-pep440/src/version.rs b/crates/uv-pep440/src/version.rs index ca8919c19330..aaf8dfc1eb18 100644 --- a/crates/uv-pep440/src/version.rs +++ b/crates/uv-pep440/src/version.rs @@ -1,4 +1,6 @@ use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; +use std::num::NonZero; +use std::ops::Deref; use std::sync::LazyLock; use std::{ borrow::Borrow, @@ -267,7 +269,7 @@ impl std::fmt::Display for OperatorParseError { )] #[cfg_attr(feature = "rkyv", rkyv(derive(Debug, Eq, PartialEq, PartialOrd, Ord)))] pub struct Version { - inner: Arc, + inner: VersionInner, } #[derive(Clone, Debug)] @@ -278,7 +280,7 @@ pub struct Version { #[cfg_attr(feature = "rkyv", rkyv(derive(Debug, Eq, PartialEq, PartialOrd, Ord)))] enum VersionInner { Small { small: VersionSmall }, - Full { full: VersionFull }, + Full { full: Arc }, } impl Version { @@ -295,9 +297,9 @@ impl Version { R: Borrow, { Self { - inner: Arc::new(VersionInner::Small { + inner: VersionInner::Small { small: VersionSmall::new(), - }), + }, } .with_release(release_numbers) } @@ -344,7 +346,7 @@ impl Version { /// Returns the epoch of this version. #[inline] pub fn epoch(&self) -> u64 { - match *self.inner { + match self.inner { VersionInner::Small { ref small } => small.epoch(), VersionInner::Full { ref full } => full.epoch, } @@ -352,17 +354,44 @@ impl Version { /// Returns the release number part of the version. #[inline] - pub fn release(&self) -> &[u64] { - match *self.inner { - VersionInner::Small { ref small } => small.release(), - VersionInner::Full { ref full, .. } => &full.release, - } + pub fn release(&self) -> Release { + let inner = match &self.inner { + VersionInner::Small { small } => { + // Parse out the version digits. + // * Bytes 6 and 7 correspond to the first release segment as a `u16`. + // * Bytes 5, 4 and 3 correspond to the second, third and fourth release + // segments, respectively. + match small.len { + 0 => ReleaseInner::Small0([]), + 1 => ReleaseInner::Small1([(small.repr >> 0o60) & 0xFFFF]), + 2 => ReleaseInner::Small2([ + (small.repr >> 0o60) & 0xFFFF, + (small.repr >> 0o50) & 0xFF, + ]), + 3 => ReleaseInner::Small3([ + (small.repr >> 0o60) & 0xFFFF, + (small.repr >> 0o50) & 0xFF, + (small.repr >> 0o40) & 0xFF, + ]), + 4 => ReleaseInner::Small4([ + (small.repr >> 0o60) & 0xFFFF, + (small.repr >> 0o50) & 0xFF, + (small.repr >> 0o40) & 0xFF, + (small.repr >> 0o30) & 0xFF, + ]), + _ => unreachable!("{}", small.len), + } + } + VersionInner::Full { full } => ReleaseInner::Full(&full.release), + }; + + Release { inner } } /// Returns the pre-release part of this version, if it exists. #[inline] pub fn pre(&self) -> Option { - match *self.inner { + match self.inner { VersionInner::Small { ref small } => small.pre(), VersionInner::Full { ref full } => full.pre, } @@ -371,7 +400,7 @@ impl Version { /// Returns the post-release part of this version, if it exists. #[inline] pub fn post(&self) -> Option { - match *self.inner { + match self.inner { VersionInner::Small { ref small } => small.post(), VersionInner::Full { ref full } => full.post, } @@ -380,7 +409,7 @@ impl Version { /// Returns the dev-release part of this version, if it exists. #[inline] pub fn dev(&self) -> Option { - match *self.inner { + match self.inner { VersionInner::Small { ref small } => small.dev(), VersionInner::Full { ref full } => full.dev, } @@ -389,7 +418,7 @@ impl Version { /// Returns the local segments in this version, if any exist. #[inline] pub fn local(&self) -> LocalVersionSlice { - match *self.inner { + match self.inner { VersionInner::Small { ref small } => small.local_slice(), VersionInner::Full { ref full } => full.local.as_slice(), } @@ -402,7 +431,7 @@ impl Version { /// like `1.0a1`, `1.0dev0`, etc. #[inline] pub fn min(&self) -> Option { - match *self.inner { + match self.inner { VersionInner::Small { ref small } => small.min(), VersionInner::Full { ref full } => full.min, } @@ -415,7 +444,7 @@ impl Version { /// like `1.0.post1`, `1.0+local`, etc. #[inline] pub fn max(&self) -> Option { - match *self.inner { + match self.inner { VersionInner::Small { ref small } => small.max(), VersionInner::Full { ref full } => full.max, } @@ -453,7 +482,7 @@ impl Version { /// last number in the release component. #[inline] fn push_release(&mut self, n: u64) { - if let VersionInner::Small { ref mut small } = Arc::make_mut(&mut self.inner) { + if let VersionInner::Small { ref mut small } = &mut self.inner { if small.push_release(n) { return; } @@ -467,10 +496,10 @@ impl Version { /// since all versions should have at least one release number. #[inline] fn clear_release(&mut self) { - match Arc::make_mut(&mut self.inner) { + match &mut self.inner { VersionInner::Small { ref mut small } => small.clear_release(), VersionInner::Full { ref mut full } => { - full.release.clear(); + Arc::make_mut(full).release.clear(); } } } @@ -479,7 +508,7 @@ impl Version { #[inline] #[must_use] pub fn with_epoch(mut self, value: u64) -> Self { - if let VersionInner::Small { ref mut small } = Arc::make_mut(&mut self.inner) { + if let VersionInner::Small { ref mut small } = &mut self.inner { if small.set_epoch(value) { return self; } @@ -492,7 +521,7 @@ impl Version { #[inline] #[must_use] pub fn with_pre(mut self, value: Option) -> Self { - if let VersionInner::Small { ref mut small } = Arc::make_mut(&mut self.inner) { + if let VersionInner::Small { ref mut small } = &mut self.inner { if small.set_pre(value) { return self; } @@ -505,7 +534,7 @@ impl Version { #[inline] #[must_use] pub fn with_post(mut self, value: Option) -> Self { - if let VersionInner::Small { ref mut small } = Arc::make_mut(&mut self.inner) { + if let VersionInner::Small { ref mut small } = &mut self.inner { if small.set_post(value) { return self; } @@ -518,7 +547,7 @@ impl Version { #[inline] #[must_use] pub fn with_dev(mut self, value: Option) -> Self { - if let VersionInner::Small { ref mut small } = Arc::make_mut(&mut self.inner) { + if let VersionInner::Small { ref mut small } = &mut self.inner { if small.set_dev(value) { return self; } @@ -546,7 +575,7 @@ impl Version { match value { LocalVersion::Segments(segments) => self.with_local_segments(segments), LocalVersion::Max => { - if let VersionInner::Small { ref mut small } = Arc::make_mut(&mut self.inner) { + if let VersionInner::Small { ref mut small } = &mut self.inner { if small.set_local(LocalVersion::Max) { return self; } @@ -564,7 +593,7 @@ impl Version { #[inline] #[must_use] pub fn without_local(mut self) -> Self { - if let VersionInner::Small { ref mut small } = Arc::make_mut(&mut self.inner) { + if let VersionInner::Small { ref mut small } = &mut self.inner { if small.set_local(LocalVersion::empty()) { return self; } @@ -605,7 +634,7 @@ impl Version { pub fn with_min(mut self, value: Option) -> Self { debug_assert!(!self.is_pre(), "min is not allowed on pre-release versions"); debug_assert!(!self.is_dev(), "min is not allowed on dev versions"); - if let VersionInner::Small { ref mut small } = Arc::make_mut(&mut self.inner) { + if let VersionInner::Small { ref mut small } = &mut self.inner { if small.set_min(value) { return self; } @@ -627,7 +656,7 @@ impl Version { "max is not allowed on post-release versions" ); debug_assert!(!self.is_dev(), "max is not allowed on dev versions"); - if let VersionInner::Small { ref mut small } = Arc::make_mut(&mut self.inner) { + if let VersionInner::Small { ref mut small } = &mut self.inner { if small.set_max(value) { return self; } @@ -639,10 +668,10 @@ impl Version { /// Convert this version to a "full" representation in-place and return a /// mutable borrow to the full type. fn make_full(&mut self) -> &mut VersionFull { - if let VersionInner::Small { ref small } = *self.inner { + if let VersionInner::Small { ref small } = self.inner { let full = VersionFull { epoch: small.epoch(), - release: small.release().to_vec(), + release: self.release().to_vec(), min: small.min(), max: small.max(), pre: small.pre(), @@ -651,11 +680,13 @@ impl Version { local: small.local(), }; *self = Self { - inner: Arc::new(VersionInner::Full { full }), + inner: VersionInner::Full { + full: Arc::new(full), + }, }; } - match Arc::make_mut(&mut self.inner) { - VersionInner::Full { ref mut full } => full, + match &mut self.inner { + VersionInner::Full { ref mut full } => Arc::make_mut(full), VersionInner::Small { .. } => unreachable!(), } } @@ -679,7 +710,7 @@ impl Version { } } - match compare_release(self.release(), other.release()) { + match compare_release(&self.release(), &other.release()) { Ordering::Less => { return Ordering::Less; } @@ -800,7 +831,7 @@ impl Ord for Version { /// < 1.0 < 1.0.post456.dev34 < 1.0.post456 #[inline] fn cmp(&self, other: &Self) -> Ordering { - match (&*self.inner, &*other.inner) { + match (&self.inner, &other.inner) { (VersionInner::Small { small: small1 }, VersionInner::Small { small: small2 }) => { small1.repr.cmp(&small2.repr) } @@ -820,7 +851,7 @@ impl FromStr for Version { } } -/// A "small" representation of a version. +/// A small representation of a version. /// /// This representation is used for a (very common) subset of versions: the /// set of all versions with ~small numbers and no local component. The @@ -901,35 +932,15 @@ impl FromStr for Version { )] #[cfg_attr(feature = "rkyv", rkyv(derive(Debug, Eq, PartialEq, PartialOrd, Ord)))] struct VersionSmall { - /// The representation discussed above. - repr: u64, - /// The `u64` numbers in the release component. - /// - /// These are *only* used to implement the public API `Version::release` - /// method. This is necessary in order to provide a `&[u64]` to the caller. - /// If we didn't need the public API, or could re-work it, then we could - /// get rid of this extra storage. (Which is indeed duplicative of what is - /// stored in `repr`.) Note that this uses `u64` not because it can store - /// bigger numbers than what's in `repr` (it can't), but so that it permits - /// us to return a `&[u64]`. - /// - /// I believe there is only one way to get rid of this extra storage: - /// change the public API so that it doesn't return a `&[u64]`. Instead, - /// we'd return a new type that conceptually represents a `&[u64]`, but may - /// use a different representation based on what kind of `Version` it came - /// from. The downside of this approach is that one loses the flexibility - /// of a simple `&[u64]`. (Which, at time of writing, is taken advantage of - /// in several places via slice patterns.) But, if we needed to change it, - /// we could do it without losing expressivity, but losing convenience. - release: [u64; 4], /// The number of segments in the release component. /// - /// Strictly speaking, this isn't necessary since `1.2` is considered - /// equivalent to `1.2.0.0`. But in practice it's nice to be able - /// to truncate the zero components. And always filling out to 4 - /// places somewhat exposes internal details, since the "full" version - /// representation would not do that. + /// PEP 440 considers `1.2` equivalent to `1.2.0.0`, but we want to preserve trailing zeroes + /// in roundtrips, as the "full" version representation also does. len: u8, + /// The representation discussed above. + repr: u64, + /// Force a niche into the aligned type so the [`Version`] enum is two words instead of three. + _force_niche: NonZero, } impl VersionSmall { @@ -978,8 +989,8 @@ impl VersionSmall { #[inline] fn new() -> Self { Self { + _force_niche: NonZero::::MIN, repr: Self::SUFFIX_NONE << Self::SUFFIX_VERSION_BIT_LEN, - release: [0, 0, 0, 0], len: 0, } } @@ -999,15 +1010,9 @@ impl VersionSmall { true } - #[inline] - fn release(&self) -> &[u64] { - &self.release[..usize::from(self.len)] - } - #[inline] fn clear_release(&mut self) { self.repr &= !Self::SUFFIX_RELEASE_MASK; - self.release = [0, 0, 0, 0]; self.len = 0; } @@ -1018,7 +1023,6 @@ impl VersionSmall { return false; } self.repr |= n << 48; - self.release[0] = n; self.len = 1; true } else { @@ -1030,7 +1034,6 @@ impl VersionSmall { } let shift = 48 - (usize::from(self.len) * 8); self.repr |= n << shift; - self.release[usize::from(self.len)] = n; self.len += 1; true } @@ -1416,6 +1419,41 @@ impl FromStr for VersionPattern { } } +/// Release digits of a [`Version`]. +/// +/// Lifetime and indexing workaround to allow accessing the release as `&[u64]` even though the +/// digits may be stored in a compressed representation. +pub struct Release<'a> { + inner: ReleaseInner<'a>, +} + +enum ReleaseInner<'a> { + // The small versions unpacked into larger u64 values. + // We're storing at most 4 u64 plus determinant for the duration of the release call on the + // stack, without heap allocation. + Small0([u64; 0]), + Small1([u64; 1]), + Small2([u64; 2]), + Small3([u64; 3]), + Small4([u64; 4]), + Full(&'a [u64]), +} + +impl Deref for Release<'_> { + type Target = [u64]; + + fn deref(&self) -> &Self::Target { + match &self.inner { + ReleaseInner::Small0(v) => v, + ReleaseInner::Small1(v) => v, + ReleaseInner::Small2(v) => v, + ReleaseInner::Small3(v) => v, + ReleaseInner::Small4(v) => v, + ReleaseInner::Full(v) => v, + } + } +} + /// An optional pre-release modifier and number applied to a version. #[derive(PartialEq, Eq, Debug, Hash, Clone, Copy, Ord, PartialOrd)] #[cfg_attr( @@ -1578,7 +1616,7 @@ impl LocalVersionSlice<'_> { /// > should be considered an integer for comparison purposes and if a segment contains any ASCII /// > letters then that segment is compared lexicographically with case insensitivity. When /// > comparing a numeric and lexicographic segment, the numeric section always compares as greater -/// > than the lexicographic segment. Additionally a local version with a great number of segments +/// > than the lexicographic segment. Additionally, a local version with a great number of segments /// > will always compare as greater than a local version with fewer segments, as long as the /// > shorter local version’s segments match the beginning of the longer local version’s segments /// > exactly. @@ -1763,20 +1801,16 @@ impl<'a> Parser<'a> { *release.get_mut(usize::from(len))? = cur; len += 1; let small = VersionSmall { + _force_niche: NonZero::::MIN, repr: (u64::from(release[0]) << 48) | (u64::from(release[1]) << 40) | (u64::from(release[2]) << 32) | (u64::from(release[3]) << 24) | (VersionSmall::SUFFIX_NONE << VersionSmall::SUFFIX_VERSION_BIT_LEN), - release: [ - u64::from(release[0]), - u64::from(release[1]), - u64::from(release[2]), - u64::from(release[3]), - ], + len, }; - let inner = Arc::new(VersionInner::Small { small }); + let inner = VersionInner::Small { small }; let version = Version { inner }; Some(VersionPattern { version, @@ -3945,7 +3979,7 @@ mod tests { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Version") .field("epoch", &self.0.epoch()) - .field("release", &self.0.release()) + .field("release", &&*self.0.release()) .field("pre", &self.0.pre()) .field("post", &self.0.post()) .field("dev", &self.0.dev()) @@ -3961,4 +3995,24 @@ mod tests { VersionBloatedDebug(self) } } + + /// This explicitly tests that we preserve trailing zeros in a version + /// string. i.e., Both `1.2` and `1.2.0` round-trip, with the former + /// lacking a trailing zero and the latter including it. + #[test] + fn preserve_trailing_zeros() { + let v1: Version = "1.2.0".parse().unwrap(); + assert_eq!(&*v1.release(), &[1, 2, 0]); + assert_eq!(v1.to_string(), "1.2.0"); + + let v2: Version = "1.2".parse().unwrap(); + assert_eq!(&*v2.release(), &[1, 2]); + assert_eq!(v2.to_string(), "1.2"); + } + + #[test] + fn type_size() { + assert_eq!(size_of::(), size_of::() * 2); + assert_eq!(size_of::(), size_of::() * 2); + } } diff --git a/crates/uv-pep440/src/version_ranges.rs b/crates/uv-pep440/src/version_ranges.rs index 7a9a4b055eed..526dce967e9a 100644 --- a/crates/uv-pep440/src/version_ranges.rs +++ b/crates/uv-pep440/src/version_ranges.rs @@ -43,7 +43,8 @@ impl From for Ranges { }) .complement(), Operator::TildeEqual => { - let [rest @ .., last, _] = version.release() else { + 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)])) @@ -160,7 +161,8 @@ pub fn release_specifier_to_range(specifier: VersionSpecifier) -> Ranges { - let [rest @ .., last, _] = version.release() else { + 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)])); diff --git a/crates/uv-pep440/src/version_specifier.rs b/crates/uv-pep440/src/version_specifier.rs index 30cdc76243b8..91e7171e56f4 100644 --- a/crates/uv-pep440/src/version_specifier.rs +++ b/crates/uv-pep440/src/version_specifier.rs @@ -86,7 +86,7 @@ impl VersionSpecifiers { // 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] => + && *lower.release() == [prev.release()[0], prev.release()[1] + 1] => { specifiers.push(VersionSpecifier::not_equals_star_version(prev.clone())); } @@ -415,7 +415,7 @@ impl VersionSpecifier { // `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] => + && *v2.release() == [v1.release()[0], v1.release()[1] + 1] => { ( Some(VersionSpecifier::equals_star_version(v1.clone())), @@ -484,7 +484,7 @@ impl VersionSpecifier { .version .release() .iter() - .zip(other.release()) + .zip(&*other.release()) .all(|(this, other)| this == other) } #[allow(deprecated)] @@ -501,7 +501,7 @@ impl VersionSpecifier { || !this .release() .iter() - .zip(version.release()) + .zip(&*version.release()) .all(|(this, other)| this == other) } Operator::TildeEqual => { @@ -516,7 +516,7 @@ impl VersionSpecifier { if !this.release()[..this.release().len() - 1] .iter() - .zip(other.release()) + .zip(&*other.release()) .all(|(this, other)| this == other) { return false; @@ -530,7 +530,7 @@ impl VersionSpecifier { Operator::GreaterThanEqual => Self::greater_than(&this, &other) || other >= this, Operator::LessThan => { Self::less_than(&this, &other) - && !(version::compare_release(this.release(), other.release()) + && !(version::compare_release(&this.release(), &other.release()) == Ordering::Equal && other.any_prerelease()) } @@ -549,7 +549,7 @@ impl VersionSpecifier { // not match 3.1.dev0, but should match 3.0.dev0). if !this.any_prerelease() && other.is_pre() - && version::compare_release(this.release(), other.release()) == Ordering::Equal + && version::compare_release(&this.release(), &other.release()) == Ordering::Equal { return false; } @@ -562,7 +562,7 @@ impl VersionSpecifier { return true; } - if version::compare_release(this.release(), other.release()) == Ordering::Equal { + if version::compare_release(&this.release(), &other.release()) == Ordering::Equal { // This special case is here so that, unless the specifier itself // includes is a post-release version, that we do not accept // post-release versions for the version mentioned in the specifier diff --git a/crates/uv-pep508/Cargo.toml b/crates/uv-pep508/Cargo.toml index 15e0e0095b2c..1fff96287684 100644 --- a/crates/uv-pep508/Cargo.toml +++ b/crates/uv-pep508/Cargo.toml @@ -13,8 +13,6 @@ repository = { workspace = true } authors = { workspace = true } [lib] -name = "uv_pep508" -crate-type = ["cdylib", "rlib"] doctest = false [lints] @@ -25,6 +23,7 @@ uv-fs = { workspace = true } uv-normalize = { workspace = true } uv-pep440 = { workspace = true } +arcstr = { workspace = true} boxcar = { workspace = true } indexmap = { workspace = true } itertools = { workspace = true } diff --git a/crates/uv-pep508/src/lib.rs b/crates/uv-pep508/src/lib.rs index 95f4a4774c4e..b30bbc670a97 100644 --- a/crates/uv-pep508/src/lib.rs +++ b/crates/uv-pep508/src/lib.rs @@ -394,12 +394,9 @@ fn parse_name(cursor: &mut Cursor) -> Result(cursor: &mut Cursor) -> Result { - name.push(char); - cursor.next(); - // [.-_] can't be the final character - if cursor.peek().is_none() && matches!(char, '.' | '-' | '_') { - return Err(Pep508Error { - message: Pep508ErrorSource::String(format!( - "Package name must end with an alphanumeric character, not '{char}'" - )), - start: index, - len: char.len_utf8(), - input: cursor.to_string(), - }); - } - } - Some(_) | None => { - return Ok(PackageName::new(name) - .expect("`PackageName` validation should match PEP 508 parsing")); + if let Some((index, char @ ('A'..='Z' | 'a'..='z' | '0'..='9' | '.' | '-' | '_'))) = + cursor.peek() + { + cursor.next(); + // [.-_] can't be the final character + if cursor.peek().is_none() && matches!(char, '.' | '-' | '_') { + return Err(Pep508Error { + message: Pep508ErrorSource::String(format!( + "Package name must end with an alphanumeric character, not `{char}`" + )), + start: index, + len: char.len_utf8(), + input: cursor.to_string(), + }); } + } else { + let len = cursor.pos() - start; + return Ok(PackageName::from_str(cursor.slice(start, len)).unwrap()); } } } @@ -935,12 +930,12 @@ fn parse_pep508_requirement( if let Some((pos, char)) = cursor.next().filter(|(_, c)| *c != '#') { let message = if char == '#' { format!( - r#"Expected end of input or `;`, found `{char}`; comments must be preceded by a leading space"# + r"Expected end of input or `;`, found `{char}`; comments must be preceded by a leading space" ) } else if marker.is_none() { - format!(r#"Expected end of input or `;`, found `{char}`"#) + format!(r"Expected end of input or `;`, found `{char}`") } else { - format!(r#"Expected end of input, found `{char}`"#) + format!(r"Expected end of input, found `{char}`") }; return Err(Pep508Error { message: Pep508ErrorSource::String(message), @@ -1033,7 +1028,7 @@ mod tests { assert_snapshot!( parse_pep508_err("name_"), @" - Package name must end with an alphanumeric character, not '_' + Package name must end with an alphanumeric character, not `_` name_ ^" ); @@ -1339,17 +1334,17 @@ mod tests { let mut b = MarkerTree::expression(MarkerExpression::String { key: MarkerValueString::SysPlatform, operator: MarkerOperator::Equal, - value: "win32".to_string(), + value: arcstr::literal!("win32"), }); let mut c = MarkerTree::expression(MarkerExpression::String { key: MarkerValueString::OsName, operator: MarkerOperator::Equal, - value: "linux".to_string(), + value: arcstr::literal!("linux"), }); let d = MarkerTree::expression(MarkerExpression::String { key: MarkerValueString::ImplementationName, operator: MarkerOperator::Equal, - value: "cpython".to_string(), + value: arcstr::literal!("cpython"), }); c.and(d); diff --git a/crates/uv-pep508/src/marker/algebra.rs b/crates/uv-pep508/src/marker/algebra.rs index 0c1bdbce200e..303d2696f494 100644 --- a/crates/uv-pep508/src/marker/algebra.rs +++ b/crates/uv-pep508/src/marker/algebra.rs @@ -51,6 +51,7 @@ use std::ops::Bound; use std::sync::Mutex; use std::sync::MutexGuard; +use arcstr::ArcStr; use itertools::{Either, Itertools}; use rustc_hash::FxHashMap; use std::sync::LazyLock; @@ -287,28 +288,31 @@ impl InternerGuard<'_> { // values in `exclusions`. // // See: https://discuss.python.org/t/clarify-usage-of-platform-system/70900 - let (key, value) = match (key, value.as_str()) { - (MarkerValueString::PlatformSystem, "Windows") => { - (CanonicalMarkerValueString::SysPlatform, "win32".to_string()) - } + let (key, value) = match (key, value.as_ref()) { + (MarkerValueString::PlatformSystem, "Windows") => ( + CanonicalMarkerValueString::SysPlatform, + arcstr::literal!("win32"), + ), (MarkerValueString::PlatformSystem, "Darwin") => ( CanonicalMarkerValueString::SysPlatform, - "darwin".to_string(), + arcstr::literal!("darwin"), + ), + (MarkerValueString::PlatformSystem, "Linux") => ( + CanonicalMarkerValueString::SysPlatform, + arcstr::literal!("linux"), + ), + (MarkerValueString::PlatformSystem, "AIX") => ( + CanonicalMarkerValueString::SysPlatform, + arcstr::literal!("aix"), ), - (MarkerValueString::PlatformSystem, "Linux") => { - (CanonicalMarkerValueString::SysPlatform, "linux".to_string()) - } - (MarkerValueString::PlatformSystem, "AIX") => { - (CanonicalMarkerValueString::SysPlatform, "aix".to_string()) - } (MarkerValueString::PlatformSystem, "Emscripten") => ( CanonicalMarkerValueString::SysPlatform, - "emscripten".to_string(), + arcstr::literal!("emscripten"), ), // See: https://peps.python.org/pep-0738/#sys (MarkerValueString::PlatformSystem, "Android") => ( CanonicalMarkerValueString::SysPlatform, - "android".to_string(), + arcstr::literal!("android"), ), _ => (key.into(), value), }; @@ -869,48 +873,48 @@ impl InternerGuard<'_> { MarkerExpression::String { key: MarkerValueString::OsName, operator: MarkerOperator::Equal, - value: "nt".to_string(), + value: arcstr::literal!("nt"), }, MarkerExpression::String { key: MarkerValueString::SysPlatform, operator: MarkerOperator::Equal, - value: "linux".to_string(), + value: arcstr::literal!("linux"), }, ), ( MarkerExpression::String { key: MarkerValueString::OsName, operator: MarkerOperator::Equal, - value: "nt".to_string(), + value: arcstr::literal!("nt"), }, MarkerExpression::String { key: MarkerValueString::SysPlatform, operator: MarkerOperator::Equal, - value: "darwin".to_string(), + value: arcstr::literal!("darwin"), }, ), ( MarkerExpression::String { key: MarkerValueString::OsName, operator: MarkerOperator::Equal, - value: "nt".to_string(), + value: arcstr::literal!("nt"), }, MarkerExpression::String { key: MarkerValueString::SysPlatform, operator: MarkerOperator::Equal, - value: "ios".to_string(), + value: arcstr::literal!("ios"), }, ), ( MarkerExpression::String { key: MarkerValueString::OsName, operator: MarkerOperator::Equal, - value: "posix".to_string(), + value: arcstr::literal!("posix"), }, MarkerExpression::String { key: MarkerValueString::SysPlatform, operator: MarkerOperator::Equal, - value: "win32".to_string(), + value: arcstr::literal!("win32"), }, ), ]; @@ -950,12 +954,12 @@ impl InternerGuard<'_> { MarkerExpression::String { key: MarkerValueString::PlatformSystem, operator: MarkerOperator::Equal, - value: platform_system.to_string(), + value: ArcStr::from(platform_system), }, MarkerExpression::String { key: MarkerValueString::SysPlatform, operator: MarkerOperator::Equal, - value: sys_platform.to_string(), + value: ArcStr::from(sys_platform), }, )); } @@ -996,13 +1000,13 @@ pub(crate) enum Variable { /// string marker and value. In { key: CanonicalMarkerValueString, - value: String, + value: ArcStr, }, /// A variable representing a ` in ` expression for a particular /// string marker and value. Contains { key: CanonicalMarkerValueString, - value: String, + value: ArcStr, }, /// A variable representing the existence or absence of a given extra. /// @@ -1128,7 +1132,7 @@ pub(crate) enum Edges { // Invariant: All ranges are simple, meaning they can be represented by a bounded // interval without gaps. Additionally, there are at least two edges in the set. String { - edges: SmallVec<(Ranges, NodeId)>, + edges: SmallVec<(Ranges, NodeId)>, }, // The edges of a boolean variable, representing the values `true` (the `high` child) // and `false` (the `low` child). @@ -1158,8 +1162,8 @@ impl Edges { /// /// This function will panic for the `In` and `Contains` marker operators, which /// should be represented as separate boolean variables. - fn from_string(operator: MarkerOperator, value: String) -> Edges { - let range: Ranges = match operator { + fn from_string(operator: MarkerOperator, value: ArcStr) -> Edges { + let range: Ranges = match operator { MarkerOperator::Equal => Ranges::singleton(value), MarkerOperator::NotEqual => Ranges::singleton(value).complement(), MarkerOperator::GreaterThan => Ranges::strictly_higher_than(value), @@ -1412,8 +1416,7 @@ impl Edges { // not the resulting edges. for (left_range, left_child) in left_edges { for (right_range, right_child) in right_edges { - let intersection = right_range.intersection(left_range); - if intersection.is_empty() { + if right_range.is_disjoint(left_range) { continue; } @@ -1508,7 +1511,7 @@ fn normalize_specifier(specifier: VersionSpecifier) -> VersionSpecifier { // 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(); + let mut release = &*version.release(); // Strip any trailing `0`s. // @@ -1583,7 +1586,7 @@ fn python_version_to_full_version(specifier: VersionSpecifier) -> Result specifier, }) } else { - let &[major, minor, ..] = specifier.version().release() else { + let [major, minor, ..] = *specifier.version().release() else { unreachable!() }; diff --git a/crates/uv-pep508/src/marker/parse.rs b/crates/uv-pep508/src/marker/parse.rs index d85af429f8b2..bae032d9b270 100644 --- a/crates/uv-pep508/src/marker/parse.rs +++ b/crates/uv-pep508/src/marker/parse.rs @@ -1,5 +1,5 @@ +use arcstr::ArcStr; use std::str::FromStr; - use uv_normalize::ExtraName; use uv_pep440::{Version, VersionPattern, VersionSpecifier}; @@ -92,7 +92,7 @@ pub(crate) fn parse_marker_value( Some((start_pos, quotation_mark @ ('"' | '\''))) => { cursor.next(); let (start, len) = cursor.take_while(|c| c != quotation_mark); - let value = cursor.slice(start, len).to_string(); + let value = ArcStr::from(cursor.slice(start, len)); cursor.next_expect_char(quotation_mark, start_pos)?; Ok(MarkerValue::QuotedString(value)) } diff --git a/crates/uv-pep508/src/marker/simplify.rs b/crates/uv-pep508/src/marker/simplify.rs index 35a33b6ce4b8..34c095b09efc 100644 --- a/crates/uv-pep508/src/marker/simplify.rs +++ b/crates/uv-pep508/src/marker/simplify.rs @@ -1,12 +1,14 @@ use std::fmt; use std::ops::Bound; +use arcstr::ArcStr; use indexmap::IndexMap; use itertools::Itertools; use rustc_hash::FxBuildHasher; -use uv_pep440::{Version, VersionSpecifier}; use version_ranges::Ranges; +use uv_pep440::{Version, VersionSpecifier}; + use crate::{ExtraOperator, MarkerExpression, MarkerOperator, MarkerTree, MarkerTreeKind}; /// Returns a simplified DNF expression for a given marker tree. @@ -131,7 +133,7 @@ fn collect_dnf( let expr = MarkerExpression::String { key: marker.key().into(), - value: marker.value().to_owned(), + value: ArcStr::from(marker.value()), operator, }; @@ -150,7 +152,7 @@ fn collect_dnf( let expr = MarkerExpression::String { key: marker.key().into(), - value: marker.value().to_owned(), + value: ArcStr::from(marker.value()), operator, }; @@ -352,7 +354,7 @@ fn star_range_inequality(range: &Ranges) -> Option { 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] => + && *v2.release() == [v1.release()[0], v1.release()[1] + 1] => { Some(VersionSpecifier::not_equals_star_version(v1.clone())) } diff --git a/crates/uv-pep508/src/marker/tree.rs b/crates/uv-pep508/src/marker/tree.rs index 55e8b5d7d06c..099fdb7e66d9 100644 --- a/crates/uv-pep508/src/marker/tree.rs +++ b/crates/uv-pep508/src/marker/tree.rs @@ -3,6 +3,7 @@ use std::fmt::{self, Display, Formatter}; use std::ops::{Bound, Deref}; use std::str::FromStr; +use arcstr::ArcStr; use itertools::Itertools; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use version_ranges::Ranges; @@ -129,7 +130,7 @@ pub enum MarkerValue { /// `extra`. This one is special because it's a list and not env but user given Extra, /// Not a constant, but a user given quoted string with a value inside such as '3.8' or "windows" - QuotedString(String), + QuotedString(ArcStr), } impl FromStr for MarkerValue { @@ -272,8 +273,8 @@ impl MarkerOperator { /// Returns the marker operator and value whose union represents the given range. pub fn from_bounds( - bounds: (&Bound, &Bound), - ) -> impl Iterator { + bounds: (&Bound, &Bound), + ) -> impl Iterator { let (b1, b2) = match bounds { (Bound::Included(v1), Bound::Included(v2)) if v1 == v2 => { (Some((MarkerOperator::Equal, v1.clone())), None) @@ -291,7 +292,7 @@ impl MarkerOperator { } /// Returns a value specifier representing the given lower bound. - pub fn from_lower_bound(bound: &Bound) -> Option<(MarkerOperator, String)> { + pub fn from_lower_bound(bound: &Bound) -> Option<(MarkerOperator, ArcStr)> { match bound { Bound::Included(value) => Some((MarkerOperator::GreaterEqual, value.clone())), Bound::Excluded(value) => Some((MarkerOperator::GreaterThan, value.clone())), @@ -300,7 +301,7 @@ impl MarkerOperator { } /// Returns a value specifier representing the given upper bound. - pub fn from_upper_bound(bound: &Bound) -> Option<(MarkerOperator, String)> { + pub fn from_upper_bound(bound: &Bound) -> Option<(MarkerOperator, ArcStr)> { match bound { Bound::Included(value) => Some((MarkerOperator::LessEqual, value.clone())), Bound::Excluded(value) => Some((MarkerOperator::LessThan, value.clone())), @@ -485,7 +486,7 @@ pub enum MarkerExpression { String { key: MarkerValueString, operator: MarkerOperator, - value: String, + value: ArcStr, }, /// `extra '...'` or `'...' extra`. Extra { @@ -1383,7 +1384,7 @@ impl Ord for VersionMarkerTree<'_> { pub struct StringMarkerTree<'a> { id: NodeId, key: CanonicalMarkerValueString, - map: &'a [(Ranges, NodeId)], + map: &'a [(Ranges, NodeId)], } impl StringMarkerTree<'_> { @@ -1393,7 +1394,7 @@ impl StringMarkerTree<'_> { } /// The edges of this node, corresponding to possible output ranges of the given variable. - pub fn children(&self) -> impl ExactSizeIterator, MarkerTree)> { + pub fn children(&self) -> impl ExactSizeIterator, MarkerTree)> { self.map .iter() .map(|(range, node)| (range, MarkerTree(node.negate(self.id)))) @@ -1418,7 +1419,7 @@ impl Ord for StringMarkerTree<'_> { #[derive(PartialEq, Eq, Clone, Debug)] pub struct InMarkerTree<'a> { key: CanonicalMarkerValueString, - value: &'a str, + value: &'a ArcStr, high: NodeId, low: NodeId, } @@ -1430,7 +1431,7 @@ impl InMarkerTree<'_> { } /// The value (RHS) for this expression. - pub fn value(&self) -> &str { + pub fn value(&self) -> &ArcStr { self.value } @@ -1654,6 +1655,7 @@ mod test { use std::str::FromStr; use insta::assert_snapshot; + use uv_normalize::ExtraName; use uv_pep440::Version; @@ -2041,7 +2043,7 @@ mod test { MarkerExpression::String { key: MarkerValueString::OsName, operator: MarkerOperator::Equal, - value: "nt".to_string(), + value: arcstr::literal!("nt") } ); } diff --git a/crates/uv-pep508/src/unnamed.rs b/crates/uv-pep508/src/unnamed.rs index 2e4361b940cc..0893cc989b04 100644 --- a/crates/uv-pep508/src/unnamed.rs +++ b/crates/uv-pep508/src/unnamed.rs @@ -176,12 +176,12 @@ fn parse_unnamed_requirement( if let Some((pos, char)) = cursor.next() { let message = if char == '#' { format!( - r#"Expected end of input or `;`, found `{char}`; comments must be preceded by a leading space"# + r"Expected end of input or `;`, found `{char}`; comments must be preceded by a leading space" ) } else if marker.is_none() { - format!(r#"Expected end of input or `;`, found `{char}`"#) + format!(r"Expected end of input or `;`, found `{char}`") } else { - format!(r#"Expected end of input, found `{char}`"#) + format!(r"Expected end of input, found `{char}`") }; return Err(Pep508Error { message: Pep508ErrorSource::String(message), diff --git a/crates/uv-pep508/src/verbatim_url.rs b/crates/uv-pep508/src/verbatim_url.rs index 73b06158a916..4d715b389989 100644 --- a/crates/uv-pep508/src/verbatim_url.rs +++ b/crates/uv-pep508/src/verbatim_url.rs @@ -406,7 +406,7 @@ pub fn looks_like_git_repository(url: &Url) -> bool { .map_or(true, |ext| ext.eq_ignore_ascii_case("git")) && url .path_segments() - .map_or(false, |segments| segments.count() == 2) + .is_some_and(|segments| segments.count() == 2) } /// Split the fragment from a URL. diff --git a/crates/uv-performance-flate2-backend/Cargo.lock b/crates/uv-performance-flate2-backend/Cargo.lock index 718b75bef8b4..3799c814f998 100644 --- a/crates/uv-performance-flate2-backend/Cargo.lock +++ b/crates/uv-performance-flate2-backend/Cargo.lock @@ -8,12 +8,30 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "cc" +version = "1.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7" +dependencies = [ + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cmake" +version = "0.1.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c682c223677e0e5b6b7f63a64b9351844c3f1b1678a68b7ee617e30fb082620e" +dependencies = [ + "cc", +] + [[package]] name = "crc32fast" version = "1.4.2" @@ -30,10 +48,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", + "libz-ng-sys", "libz-rs-sys", "miniz_oxide", ] +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "libz-ng-sys" +version = "1.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4436751a01da56f1277f323c80d584ffad94a3d14aecd959dd0dff75aa73a438" +dependencies = [ + "cmake", + "libc", +] + [[package]] name = "libz-rs-sys" version = "0.4.1" @@ -52,11 +87,18 @@ dependencies = [ "adler2", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "uv-performance-flate2-backend" version = "0.1.0" dependencies = [ "flate2", + "libz-ng-sys", ] [[package]] diff --git a/crates/uv-performance-flate2-backend/Cargo.toml b/crates/uv-performance-flate2-backend/Cargo.toml index b82083d39081..61249be4e328 100644 --- a/crates/uv-performance-flate2-backend/Cargo.toml +++ b/crates/uv-performance-flate2-backend/Cargo.toml @@ -7,5 +7,12 @@ edition = "2021" [lib] doctest = false -[dependencies] +# Use `zlib-ng` on all supported platforms. +[target.'cfg(not(any(target_arch = "s390x", target_arch = "powerpc64", target_os = "freebsd")))'.dependencies] +flate2 = { version = "1.0.28", default-features = false, features = ["zlib-ng"] } +# See: https://github.com/rust-lang/libz-sys/issues/225 +libz-ng-sys = { version = "<1.1.20" } + +# Use `zlib-rs` everywhere else. +[target.'cfg(any(target_arch = "s390x", target_arch = "powerpc64", target_os = "freebsd"))'.dependencies] flate2 = { version = "1.0.28", default-features = false, features = ["zlib-rs"] } diff --git a/crates/uv-platform-tags/src/platform.rs b/crates/uv-platform-tags/src/platform.rs index 12876bb0831a..b82335036286 100644 --- a/crates/uv-platform-tags/src/platform.rs +++ b/crates/uv-platform-tags/src/platform.rs @@ -56,17 +56,17 @@ pub enum Os { impl fmt::Display for Os { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { - Self::Manylinux { .. } => write!(f, "Manylinux"), - Self::Musllinux { .. } => write!(f, "Musllinux"), - Self::Windows => write!(f, "Windows"), - Self::Macos { .. } => write!(f, "MacOS"), - Self::FreeBsd { .. } => write!(f, "FreeBSD"), - Self::NetBsd { .. } => write!(f, "NetBSD"), - Self::OpenBsd { .. } => write!(f, "OpenBSD"), - Self::Dragonfly { .. } => write!(f, "DragonFly"), - Self::Illumos { .. } => write!(f, "Illumos"), - Self::Haiku { .. } => write!(f, "Haiku"), - Self::Android { .. } => write!(f, "Android"), + Self::Manylinux { .. } => write!(f, "manylinux"), + Self::Musllinux { .. } => write!(f, "musllinux"), + Self::Windows => write!(f, "windows"), + Self::Macos { .. } => write!(f, "macos"), + Self::FreeBsd { .. } => write!(f, "freebsd"), + Self::NetBsd { .. } => write!(f, "netbsd"), + Self::OpenBsd { .. } => write!(f, "openbsd"), + Self::Dragonfly { .. } => write!(f, "dragonfly"), + Self::Illumos { .. } => write!(f, "illumos"), + Self::Haiku { .. } => write!(f, "haiku"), + Self::Android { .. } => write!(f, "android"), } } } @@ -77,6 +77,7 @@ impl fmt::Display for Os { pub enum Arch { #[serde(alias = "arm64")] Aarch64, + Armv5TEL, Armv6L, #[serde(alias = "armv8l")] Armv7L, @@ -89,6 +90,7 @@ pub enum Arch { #[serde(alias = "amd64")] X86_64, S390X, + LoongArch64, Riscv64, } @@ -96,6 +98,7 @@ impl fmt::Display for Arch { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Self::Aarch64 => write!(f, "aarch64"), + Self::Armv5TEL => write!(f, "armv5tel"), Self::Armv6L => write!(f, "armv6l"), Self::Armv7L => write!(f, "armv7l"), Self::Powerpc64Le => write!(f, "ppc64le"), @@ -103,6 +106,7 @@ impl fmt::Display for Arch { Self::X86 => write!(f, "i686"), Self::X86_64 => write!(f, "x86_64"), Self::S390X => write!(f, "s390x"), + Self::LoongArch64 => write!(f, "loongarch64"), Self::Riscv64 => write!(f, "riscv64"), } } @@ -122,7 +126,7 @@ impl Arch { // manylinux_2_31 Self::Riscv64 => Some(31), // unsupported - Self::Armv6L => None, + Self::Armv5TEL | Self::Armv6L | Self::LoongArch64 => None, } } } diff --git a/crates/uv-platform-tags/src/tags.rs b/crates/uv-platform-tags/src/tags.rs index 6cfe3709db8d..8a15f6f13f09 100644 --- a/crates/uv-platform-tags/src/tags.rs +++ b/crates/uv-platform-tags/src/tags.rs @@ -21,7 +21,7 @@ pub enum TagsError { GilIsACPythonProblem(String), } -#[derive(Debug, Eq, Ord, PartialEq, PartialOrd, Clone)] +#[derive(Debug, Eq, Ord, PartialEq, PartialOrd, Copy, Clone)] pub enum IncompatibleTag { /// The tag is invalid and cannot be used. Invalid, @@ -35,7 +35,7 @@ pub enum IncompatibleTag { Platform, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Eq, PartialEq, Copy, Clone)] pub enum TagCompatibility { Incompatible(IncompatibleTag), Compatible(TagPriority), @@ -59,6 +59,7 @@ impl PartialOrd for TagCompatibility { } impl TagCompatibility { + /// Returns `true` if the tag is compatible. pub fn is_compatible(&self) -> bool { matches!(self, Self::Compatible(_)) } @@ -83,11 +84,11 @@ impl Tags { pub fn new(tags: Vec<(String, String, String)>) -> Self { let mut map = FxHashMap::default(); for (index, (py, abi, platform)) in tags.into_iter().rev().enumerate() { - map.entry(py.to_string()) + map.entry(py) .or_insert(FxHashMap::default()) - .entry(abi.to_string()) + .entry(abi) .or_insert(FxHashMap::default()) - .entry(platform.to_string()) + .entry(platform) .or_insert(TagPriority::try_from(index).expect("valid tag priority")); } Self { map: Arc::new(map) } @@ -128,7 +129,7 @@ impl Tags { if let Implementation::CPython { gil_disabled } = implementation { // For some reason 3.2 is the minimum python for the cp abi for minor in (2..=python_version.1).rev() { - // No abi3 for freethreading python + // No abi3 for free-threading python if !gil_disabled { for platform_tag in &platform_tags { tags.push(( @@ -406,8 +407,6 @@ impl Implementation { /// /// We have two cases: Actual platform specific tags (including "merged" tags such as universal2) /// and "any". -/// -/// Bit of a mess, needs to be cleaned up. fn compatible_tags(platform: &Platform) -> Result, PlatformError> { let os = platform.os(); let arch = platform.arch(); @@ -431,13 +430,13 @@ fn compatible_tags(platform: &Platform) -> Result, PlatformError> { } } } - // Non-manylinux is lowest priority + // Non-manylinux is given lowest priority. // platform_tags.push(format!("linux_{arch}")); platform_tags } (Os::Musllinux { major, minor }, _) => { - let mut platform_tags = vec![format!("linux_{}", arch)]; + let mut platform_tags = vec![format!("linux_{arch}")]; // musl 1.1 is the lowest supported version in musllinux platform_tags .extend((1..=*minor).map(|minor| format!("musllinux_{major}_{minor}_{arch}"))); @@ -516,12 +515,7 @@ fn compatible_tags(platform: &Platform) -> Result, PlatformError> { _, ) => { let release = release.replace(['.', '-'], "_"); - vec![format!( - "{}_{}_{}", - os.to_string().to_lowercase(), - release, - arch - )] + vec![format!("{os}_{release}_{arch}")] } (Os::Illumos { release, arch }, _) => { // See https://github.com/python/cpython/blob/46c8d915715aa2bd4d697482aa051fe974d440e1/Lib/sysconfig.py#L722-L730 @@ -533,23 +527,17 @@ fn compatible_tags(platform: &Platform) -> Result, PlatformError> { })?; if major_ver >= 5 { // SunOS 5 == Solaris 2 - let os = "solaris".to_string(); + let os = "solaris"; let release = format!("{}_{}", major_ver - 3, other); let arch = format!("{arch}_64bit"); - return Ok(vec![format!("{}_{}_{}", os, release, arch)]); + return Ok(vec![format!("{os}_{release}_{arch}")]); } } - let os = os.to_string().to_lowercase(); - vec![format!("{}_{}_{}", os, release, arch)] + vec![format!("{os}_{release}_{arch}")] } (Os::Android { api_level }, _) => { - vec![format!( - "{}_{}_{}", - os.to_string().to_lowercase(), - api_level, - arch - )] + vec![format!("{os}_{api_level}_{arch}")] } _ => { return Err(PlatformError::OsVersionDetectionError(format!( diff --git a/crates/uv-python/Cargo.toml b/crates/uv-python/Cargo.toml index 92930aa298ac..fcea7d5ecb60 100644 --- a/crates/uv-python/Cargo.toml +++ b/crates/uv-python/Cargo.toml @@ -73,7 +73,7 @@ anyhow = { version = "1.0.89" } assert_fs = { version = "1.1.2" } indoc = { workspace = true } insta = { version = "1.40.0" } -itertools = { version = "0.13.0" } +itertools = { version = "0.14.0" } temp-env = { version = "0.3.6" } tempfile = { workspace = true } test-log = { version = "0.2.16", features = ["trace"], default-features = false } diff --git a/crates/uv-python/download-metadata.json b/crates/uv-python/download-metadata.json index 6ed5bb690a53..ffc83675c803 100644 --- a/crates/uv-python/download-metadata.json +++ b/crates/uv-python/download-metadata.json @@ -1,4 +1,500 @@ { + "cpython-3.14.0a3-darwin-aarch64-none": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "a3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "4d474a12d52c316f0b68790fc1f8d747cc665f3d9a4d44c8f6757b8e4aeed497", + "variant": null + }, + "cpython-3.14.0a3-darwin-x86_64-none": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "a3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "f5da54ddec8866113cb2809e1dec0ec0f4617a149b21945da7b8c8f5ccab8e5e", + "variant": null + }, + "cpython-3.14.0a3-linux-aarch64-gnu": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "a3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "2796c6573a5359c88cb8c8b2daa561c7377ad7876f2fe45983fb977b1d10e208", + "variant": null + }, + "cpython-3.14.0a3-linux-armv7-gnueabi": { + "name": "cpython", + "arch": { + "family": "armv7", + "variant": null + }, + "os": "linux", + "libc": "gnueabi", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "a3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "de6d76889576fdfb63a6f0df3df651bed4c9586a6c4c352a3d5dc2d202837d83", + "variant": null + }, + "cpython-3.14.0a3-linux-armv7-gnueabihf": { + "name": "cpython", + "arch": { + "family": "armv7", + "variant": null + }, + "os": "linux", + "libc": "gnueabihf", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "a3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "820f889a25e0ceea42742b3496ea7ffddf323a61db4a97c3712554b254650de9", + "variant": null + }, + "cpython-3.14.0a3-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": { + "family": "powerpc64le", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "a3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "e4595b35c5d0040527b9fe32ae53a3ad223ec931ccd579d0d5dd6235eaf81378", + "variant": null + }, + "cpython-3.14.0a3-linux-s390x-gnu": { + "name": "cpython", + "arch": { + "family": "s390x", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "a3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "0a632b7493756ea057f0037a0794bb3bcaa32816fd57dde9dd6103dddc0099f8", + "variant": null + }, + "cpython-3.14.0a3-linux-x86_64-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "a3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "f143d48894e9c865a520982f28a575f138623b5ca14ca082f126171ccba46696", + "variant": null + }, + "cpython-3.14.0a3-linux-x86_64_v2-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v2" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "a3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "7aed85dc32191b6601abff3e55f1933795df97fada0982130d186f612b745407", + "variant": null + }, + "cpython-3.14.0a3-linux-x86_64_v3-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v3" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "a3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "d10663344c4bd115744afda54616053a005b51565ac9f383ff8c2bc097b20c2a", + "variant": null + }, + "cpython-3.14.0a3-linux-x86_64_v4-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v4" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "a3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "091ba9cf1fe4810fc5fbf5513d5bec4bff255bbebba4605ea3825f8f4bb90d1d", + "variant": null + }, + "cpython-3.14.0a3+freethreaded-darwin-aarch64-none": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "a3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "81a500eb67385a03e7da7add8dac34a52dcb7ca391b2a4faa493f7ebc8b4ee78", + "variant": "freethreaded" + }, + "cpython-3.14.0a3+freethreaded-darwin-x86_64-none": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "a3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-x86_64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "36a393f8d51d565e60ab3471c67c1dd029dc0f9bfb69bc0a890dc60a5764c0df", + "variant": "freethreaded" + }, + "cpython-3.14.0a3+freethreaded-linux-aarch64-gnu": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "a3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-aarch64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "2af2e24d0bc0760c0e5ba2a3250c5a94624a3059d234d2bc922abb5904ca1a35", + "variant": "freethreaded" + }, + "cpython-3.14.0a3+freethreaded-linux-armv7-gnueabi": { + "name": "cpython", + "arch": { + "family": "armv7", + "variant": null + }, + "os": "linux", + "libc": "gnueabi", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "a3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-armv7-unknown-linux-gnueabi-freethreaded%2Blto-full.tar.zst", + "sha256": "5370785843b4cebd4d3a22d74d8c9d26179cb31ad438dcb17332922f5fc99889", + "variant": "freethreaded" + }, + "cpython-3.14.0a3+freethreaded-linux-armv7-gnueabihf": { + "name": "cpython", + "arch": { + "family": "armv7", + "variant": null + }, + "os": "linux", + "libc": "gnueabihf", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "a3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-armv7-unknown-linux-gnueabihf-freethreaded%2Blto-full.tar.zst", + "sha256": "7572e0ef1cf8bde126c88a372f015af5d106cf507e4f2461379af5aa74b4806c", + "variant": "freethreaded" + }, + "cpython-3.14.0a3+freethreaded-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": { + "family": "powerpc64le", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "a3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-ppc64le-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "b727b1f3cd424e9fed726871223c64cca306e9a3074da3fe588aec2dd18f78a6", + "variant": "freethreaded" + }, + "cpython-3.14.0a3+freethreaded-linux-s390x-gnu": { + "name": "cpython", + "arch": { + "family": "s390x", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "a3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-s390x-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "9de3a8dde0b43b1d69369b8d64f9b8174bee1a979e975fb69ed10d11875bac54", + "variant": "freethreaded" + }, + "cpython-3.14.0a3+freethreaded-linux-x86_64-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "a3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-x86_64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "f4f151237b4eb6c6b2a5ed34776f6621bf84f88e9195fe5fe4a44a4446bb2ba8", + "variant": "freethreaded" + }, + "cpython-3.14.0a3+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": "a3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-x86_64_v2-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "3b9bb813870f259c73a3400f8de211c3df03d3544d82b47a9785bb14174107ee", + "variant": "freethreaded" + }, + "cpython-3.14.0a3+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": "a3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-x86_64_v3-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "2f94f0c5df33e27d5e3f8e53cb96a78cf5f2453961ea496b5b72bb41ae855cc3", + "variant": "freethreaded" + }, + "cpython-3.14.0a3+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": "a3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-x86_64_v4-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "ba9e511b020df7e04b5f78bb13d7cbc0776e4bddb41101fe26f4bfdaf7b2cd6a", + "variant": "freethreaded" + }, + "cpython-3.14.0a3+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "a3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "0e2be777200b3c81ddf080804263959ac3922851143c5b63d0105e272134e5dd", + "variant": "debug" + }, + "cpython-3.14.0a3+debug-linux-armv7-gnueabi": { + "name": "cpython", + "arch": { + "family": "armv7", + "variant": null + }, + "os": "linux", + "libc": "gnueabi", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "a3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "056e61632cafb618b0479ea6f7ebf541f831fc8b4846a4e1b89e5a3b61edc270", + "variant": "debug" + }, + "cpython-3.14.0a3+debug-linux-armv7-gnueabihf": { + "name": "cpython", + "arch": { + "family": "armv7", + "variant": null + }, + "os": "linux", + "libc": "gnueabihf", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "a3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "4f319e38997e1ded281ecbb007a8735039aba67a5f778766e74e20023a6dcc57", + "variant": "debug" + }, + "cpython-3.14.0a3+debug-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": { + "family": "powerpc64le", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "a3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "803e56d55390b03f559120b1e86eee5e80ca88649ba03b1a2a6493f19c586a56", + "variant": "debug" + }, + "cpython-3.14.0a3+debug-linux-s390x-gnu": { + "name": "cpython", + "arch": { + "family": "s390x", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "a3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "e1435d10067e927f5c5b64b43539b9be8b66d6205a33ad447f6c82edc8fd8c7c", + "variant": "debug" + }, + "cpython-3.14.0a3+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "a3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "7d52224d7f43a2d9d59a0bc1f8439eb5f12c182bc397a2370d331ac06ead81fc", + "variant": "debug" + }, + "cpython-3.14.0a3+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": "a3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "00ef15e2dbb1cb03b6321caeaac835907d82a4f4a1ac14c968d6812c5e2994f3", + "variant": "debug" + }, + "cpython-3.14.0a3+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": "a3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "58be4d73cc372b337bc4c90d5a6b7157ce7418545928c063f5822c94572db2ac", + "variant": "debug" + }, + "cpython-3.14.0a3+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": "a3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "a72cb0116a6511c20de6350a1fc664b96f8f5af4bdc10abdaa7208526a729fad", + "variant": "debug" + }, "cpython-3.13.1-darwin-aarch64-none": { "name": "cpython", "arch": { @@ -11,8 +507,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "25ca42cfac68cfa6bd6aa6c950cb76c950dca56e01ff00458120d3f62967a498", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "26b2ceded19b2eac3f0ae59d895f5032b541270bc65288c0ea36c6d8ae25e234", "variant": null }, "cpython-3.13.1-darwin-x86_64-none": { @@ -27,8 +523,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "cf3cfc41dc3017732d448f7273ba9a77a5629e3605459feff756922721c410fd", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "d1207ac02ae14a8714a5c15277396719421cd8c60184f4961905bcc6d7978c83", "variant": null }, "cpython-3.13.1-linux-aarch64-gnu": { @@ -43,8 +539,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "c801a80c40ad36d7e3d38d2d550bb64b39af0efef7081a4b1de3e4a43ece5c0d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "3c720649b2af9873262429074826ef0e35f98140e38df365b36a668573a86cf0", "variant": null }, "cpython-3.13.1-linux-armv7-gnueabi": { @@ -59,8 +555,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "8c32b8a7a210a108ba3bf5391ecba48d30b5794c2d27783fdcec50b95797157e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "660a036eced9c1052a80e152f0cfe08dde43cbc38ca73c6b969920e9382dfbe8", "variant": null }, "cpython-3.13.1-linux-armv7-gnueabihf": { @@ -75,8 +571,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "e854912d185dfd6f1e8606cb4a74a95a7535a7c95c58b5be78f4014296b488be", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "48e0ee03b779be8485f2f74728b2047708626af14e2c6711a384040afcbb8ad5", "variant": null }, "cpython-3.13.1-linux-powerpc64le-gnu": { @@ -91,8 +587,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "dc519bd25ee659db1b5f1f05e342fdc9c74c348b49f793ca3536f33e189002b0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "8408e87648118b626d79ccf1b1aa0cbf427442c46b13983f1fa07bc4b7d2cb7c", "variant": null }, "cpython-3.13.1-linux-s390x-gnu": { @@ -107,8 +603,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "f15df22d793733a457115292289091fbf51d64af042800b1218daa2a0498105f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "2c6649a03c11582b3b3de95ccc25ecec39e7407ad60ac0f274ef48260070d97f", "variant": null }, "cpython-3.13.1-linux-x86_64-gnu": { @@ -123,8 +619,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "05b3bbc97d113b64f4a0dab680e0dbe73e9f59ce8304312ef64d4b6c2822c520", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "726a608e2867c2867caa2cf3f668cb15018456070eee32375675f190946650bc", "variant": null }, "cpython-3.13.1-linux-x86_64-musl": { @@ -139,8 +635,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "5bfb969703d2f26c26e3a31b07145d40c7a3910052fdc5f95a199868908c78e3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "a7c27836ff102e92d0c604ab5d06a0f9c474c3d87a01689f477c99e3ce4b8a67", "variant": null }, "cpython-3.13.1-linux-x86_64_v2-gnu": { @@ -155,8 +651,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "07af19e001b8bce915296e2f0aaa32314515864d735175d52d604f8f8fef55ad", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "6778cc7eab5e9ee64b880025300dbf36f93bded39a23681e5977004fcecd9cfa", "variant": null }, "cpython-3.13.1-linux-x86_64_v2-musl": { @@ -171,8 +667,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "27be1c4a64b3b1ace70f3e72662405c393b5e50690516b755f56c65582235e24", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "212486199b7fd545be66e363051b93297e044353ea035e7dbe94657cafa23e83", "variant": null }, "cpython-3.13.1-linux-x86_64_v3-gnu": { @@ -187,8 +683,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "d56109dd712199b25477154a93eb68b867ad46d5049c07869f8161c5c9271fab", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "e0a7e81a06987129aebc5cd28b1acbc2f5dfb615cb675da37ac8462dbd421ef5", "variant": null }, "cpython-3.13.1-linux-x86_64_v3-musl": { @@ -203,8 +699,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "4949fd6a337418c9eb7e55871619502b96e8be712711f61414f62a161456b83c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "a827eb8b5f736fb060a154a85091f800de320a412204c11e0e34208ebc32f707", "variant": null }, "cpython-3.13.1-linux-x86_64_v4-gnu": { @@ -219,8 +715,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "410460370f55da3b4778f89acafc3ec02d2aa390fb9b48f6b5dbdccfd60d39ac", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "5d7631269cef71e59c5f0bb90c45dca550252c6a54f1b5ec5297fb44351ef94d", "variant": null }, "cpython-3.13.1-linux-x86_64_v4-musl": { @@ -235,8 +731,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "aa0316647307516ee1028055f0df98f05656033f1968d6a935dd1a34dde955b7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "f3f43199919c40d974f6c616b5e8ce820af085c0218fb2415fecbba85c15a9c1", "variant": null }, "cpython-3.13.1-windows-i686-none": { @@ -251,8 +747,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "082907fea39786100190137cdcf2d2e3672a33ef90e88ad0b669ba0c5205ec7d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "972858c2d658daa2bd7b8c6bbfe81c5252990323fb64acee2eb0b033d9ad9042", "variant": null }, "cpython-3.13.1-windows-x86_64-none": { @@ -267,8 +763,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "af37b475224eb9d37aa90e68094bbc5f9186dd421eb0fdff59f24c34a1eed4f0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "45c77db3fefd2ea1ef1c90c8595edfd6d12a53bc5c6cbaa5a2dd5b4a29a045ec", "variant": null }, "cpython-3.13.1+freethreaded-darwin-aarch64-none": { @@ -283,8 +779,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "b7044c2d584eb2261edb18db58a51b8b164e3cf2101a650f7be6d850a2859474", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "49f7bd481b69a99fbbd444bf03e078c7bcdc30f656042cb2cb72d6dd258425e1", "variant": "freethreaded" }, "cpython-3.13.1+freethreaded-darwin-x86_64-none": { @@ -299,8 +795,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "ccff425786a59f387d449eeff7d8afeab2d11cf2aa3631b5777e7d697f60bacd", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "a11eb407678ac127269e3d7aff658a94b14ab42cb11e2eaa057d6d7398c486ba", "variant": "freethreaded" }, "cpython-3.13.1+freethreaded-linux-aarch64-gnu": { @@ -315,8 +811,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-aarch64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "5d0b6ab67f02f343348560e32664d5cf4343aa016f34334fc178fe976e5ea74b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-aarch64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "f8910a48a7ce3587f21bd080c963a49441ac8c621d38424e3e69ca8dcc65b242", "variant": "freethreaded" }, "cpython-3.13.1+freethreaded-linux-armv7-gnueabi": { @@ -331,8 +827,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-armv7-unknown-linux-gnueabi-freethreaded%2Blto-full.tar.zst", - "sha256": "4373948ca535d2c011ddc42e9d7d6b415ac1c2495d2c8ec414cc22042ae0811c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-armv7-unknown-linux-gnueabi-freethreaded%2Blto-full.tar.zst", + "sha256": "ba624a019bf9ae15d4ea271537e0e0a913463f2a1c7975c39fda87c1208a7977", "variant": "freethreaded" }, "cpython-3.13.1+freethreaded-linux-armv7-gnueabihf": { @@ -347,8 +843,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-armv7-unknown-linux-gnueabihf-freethreaded%2Blto-full.tar.zst", - "sha256": "bbfb13cdf815bceffdf3b22d2992ddd24f09deb3cd3721285438151733c027db", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-armv7-unknown-linux-gnueabihf-freethreaded%2Blto-full.tar.zst", + "sha256": "87cd5e03afba6164009d3001cd97dcba613a23619801bcf4358dae28e36011c7", "variant": "freethreaded" }, "cpython-3.13.1+freethreaded-linux-powerpc64le-gnu": { @@ -363,8 +859,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-ppc64le-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "9f82f0e05d37362be651f51a5028979b0409c0b4b3ceecd207b8b4539b8c72ef", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-ppc64le-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "b9f94f840b390a68e6c34cb0ae56be3a14c067cbdc34144f6edddeefda59391c", "variant": "freethreaded" }, "cpython-3.13.1+freethreaded-linux-s390x-gnu": { @@ -379,8 +875,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-s390x-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "5b6bacbeb90bac09799f08f9937f811ab87c18e2654f9827f4ef2cb7832d8920", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-s390x-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "8ce257291bd99bdbfe93688febf5a5fe8b986370df72801b0414386bea4bc99d", "variant": "freethreaded" }, "cpython-3.13.1+freethreaded-linux-x86_64-gnu": { @@ -395,8 +891,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "065368d84d0d271cf081cf8a3599a0bfdcab00d45ddf3cde487dc5f901992ab5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "080133624e16068a8d74698fa673ce6f71914ca4d836a760758f383895f4e6b3", "variant": "freethreaded" }, "cpython-3.13.1+freethreaded-linux-x86_64_v2-gnu": { @@ -411,8 +907,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64_v2-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "196aa926332003687eb8167e55d5761439890a763dc3f0a0758355af10979f9f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64_v2-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "4d8121f8e42f18da69c8106eca6612b5edfb39ab4684af588c6626f413c78819", "variant": "freethreaded" }, "cpython-3.13.1+freethreaded-linux-x86_64_v3-gnu": { @@ -427,8 +923,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64_v3-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "65176446a0aa53f8cdbcb967d3e70d09e6bb21b058657461041611dc0f6ac111", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64_v3-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "c756e498571375568928507fbb17bca22e8c02f22cb39fb10efa3bd3c745dc1f", "variant": "freethreaded" }, "cpython-3.13.1+freethreaded-linux-x86_64_v4-gnu": { @@ -443,8 +939,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64_v4-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "a3f43b7b6073ef7a932638c3fde128c9db306c26655ec0cdaf13af9e15f1f0cf", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64_v4-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "96866696116e5b886eca6052db15da38bb7ab07dce0148fdbc171f53b7da79ae", "variant": "freethreaded" }, "cpython-3.13.1+freethreaded-windows-i686-none": { @@ -459,8 +955,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-i686-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", - "sha256": "957da31c99b168d15116af5e1f3c285a5e0da9ed2c6440aa7080f49118c0931a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-i686-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "0455a9866b1b02055022e1dd9237875533c57e7bbcd8e7993da9ab16f719f1a8", "variant": "freethreaded" }, "cpython-3.13.1+freethreaded-windows-x86_64-none": { @@ -475,8 +971,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", - "sha256": "e34828f6ae08e252b07a277f0021658963cddca8a858ae858397c1e5898d7e2a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "e2c63cd840a808705f5dd72251d2e07d70ed497c2a36d151b7701ea2c68f8bc6", "variant": "freethreaded" }, "cpython-3.13.1+debug-linux-aarch64-gnu": { @@ -491,8 +987,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "5d4dee75762392669cdf24e1ecef1015613fcd6e715af3bb7ff3859e47a76179", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "f60b09a91b610c48b3b7408e4616dbdb0e679c146b00a0171fe1d37d2c903390", "variant": "debug" }, "cpython-3.13.1+debug-linux-armv7-gnueabi": { @@ -507,8 +1003,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "e4eafca449a02bd30cde2a2370d5719095f704cca7f1182af028704836ed9bb7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "c0be744c74c148290183425e97c660a410af459fcfbb55fb108d3829a6450805", "variant": "debug" }, "cpython-3.13.1+debug-linux-armv7-gnueabihf": { @@ -523,8 +1019,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "5fc2c50cf39fa339dec661f921396c9cf219e7093150a5606e8f1ec91119d8c5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "516aa1e9922752034024b4dfffb06b69f469641fc9ef877c451345a680085b00", "variant": "debug" }, "cpython-3.13.1+debug-linux-powerpc64le-gnu": { @@ -539,8 +1035,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "5fbd41b63cd6c2cdd6f307b86490bafc35ad78db32379718df13aa3552ae3648", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "e7286cd32e05ada13ab77ff272e28ae4797053ff9ebca8e1c12c0317935899d9", "variant": "debug" }, "cpython-3.13.1+debug-linux-s390x-gnu": { @@ -555,8 +1051,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "ee1b01c237c297f9eccffe8a74e098324ee2a85ab450ac2a3872fb29eb7c3215", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "0896c6682de647c27c9e5f82e0dfc6fdb5ff5454852a8aa891c60dedeb1e2a1a", "variant": "debug" }, "cpython-3.13.1+debug-linux-x86_64-gnu": { @@ -571,8 +1067,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "852bd9da759f9aa4d0f3cde2f1cedf2189c1a88cb0ddfa5733dc2c8d9af308b1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "e52e8e9d0e6b3187826d71c34a097037c8155f6d213a12e9ad5b060fe7725b0b", "variant": "debug" }, "cpython-3.13.1+debug-linux-x86_64-musl": { @@ -587,8 +1083,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "3ee21a32aad4740d8e2c48398c16d3d7caa28c22e6e8d0366d19946603c130ac", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "4bb7fdf90257208009a2b0ad26ed29c18d5bf865e39cdee03b85f0ed78f95b11", "variant": "debug" }, "cpython-3.13.1+debug-linux-x86_64_v2-gnu": { @@ -603,8 +1099,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "3a386d92162e9af301a0e783f8d9f4d223ac13bd6ce2b7417757dae1cd0d4423", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "cb58d762b220e328f083e548f47ada021a351d0a4d8cd121b1e3350be5e6b100", "variant": "debug" }, "cpython-3.13.1+debug-linux-x86_64_v2-musl": { @@ -619,8 +1115,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "f1ce84c832bccff8b625233402300df32c7d5d40621dd380512afed41dbbe687", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "1fdd3cfe6cc58f96298840ee550d6f7098bf57394461ff11edf5ce5d16c0ba2f", "variant": "debug" }, "cpython-3.13.1+debug-linux-x86_64_v3-gnu": { @@ -635,8 +1131,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "598ea7197de2fa5ff93d3417af5539d3b10d85c50b93fa944de469414c216822", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "220ea76af9ffb35008be23bdc5790ec38bcc786a1ddf7b1811ab5c7fda86b66c", "variant": "debug" }, "cpython-3.13.1+debug-linux-x86_64_v3-musl": { @@ -651,8 +1147,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "54d2c809f2098f9974d62d7c76665e79fe2c57c29b8ae96212e6c4e283e0e2d8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "1c9d74f3dad974d4e255d6732947ac06c8e368a946c0751d38d8b15a4906a923", "variant": "debug" }, "cpython-3.13.1+debug-linux-x86_64_v4-gnu": { @@ -667,8 +1163,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "3c13e8010573aaf9012119626f6a842c19619feeb6d2670ab3d543ef61105f82", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "3fa9db6c3889e2f4f9d1d8b4bb06438828a3b28a4851061dea4e21ef5b6b372b", "variant": "debug" }, "cpython-3.13.1+debug-linux-x86_64_v4-musl": { @@ -683,8 +1179,8 @@ "minor": 13, "patch": 1, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "a84292aa6cdf1720aa5da3929cde2bf30e6838d7a36b52fdf48d09ac64adfb4a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "ecbc6a5086bcd19207149aa8bbd7ab578d2603d6c3cdf154445cece74d2ae93d", "variant": "debug" }, "cpython-3.13.0-darwin-aarch64-none": { @@ -2347,8 +2843,8 @@ "minor": 12, "patch": 8, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "abe1de2494bb8b243fd507944f4d50292848fa00685d5288c858a72623a16635", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "3a31b4f82d589a0ff6efac3cdc1e55284cb67645470b78c5aa50f156c10a93d2", "variant": null }, "cpython-3.12.8-darwin-x86_64-none": { @@ -2363,8 +2859,8 @@ "minor": 12, "patch": 8, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "867c1af10f204224b571f8f2593fc9eb580fe0c2376224d1096ebe855ad8c722", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "00e2d29ce8a688f5d2f192718e6f87e4aedae88ac2a0958011b8af9a49e049a7", "variant": null }, "cpython-3.12.8-linux-aarch64-gnu": { @@ -2379,8 +2875,8 @@ "minor": 12, "patch": 8, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "fb983ec85952513f5f013674fcbf4306b1a142c50fcfd914c2c3f00c61a874b0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "29df00dc1435f536c50396d1753fb507f81a81c93a04a7c24b145c9ad68798f8", "variant": null }, "cpython-3.12.8-linux-armv7-gnueabi": { @@ -2395,8 +2891,8 @@ "minor": 12, "patch": 8, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "0567907c0147753b54683ffdd3559d53aa4f10b203f067628586bdc545dddffb", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "d648de42934e775c78fc73638d35d021c9bf7306d03c505e10a270e70439bc9e", "variant": null }, "cpython-3.12.8-linux-armv7-gnueabihf": { @@ -2411,8 +2907,8 @@ "minor": 12, "patch": 8, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "d970da6a727a069ae765ae067ca93e369cf9881e5140e28f5030f6314595dd9b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "fc1bf99d6c64386438d83e1cee031b2764c801e5480d7c221c5625f48bd7381a", "variant": null }, "cpython-3.12.8-linux-powerpc64le-gnu": { @@ -2427,8 +2923,8 @@ "minor": 12, "patch": 8, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "462b028ce314eb57ced802ce7f9459520365ce5d635d7ae24d2d532b78c9ee12", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "58b09124abbdd4408cafc6e93d7df29e21e346f6dde4df38b75dee3ef5f3ef60", "variant": null }, "cpython-3.12.8-linux-s390x-gnu": { @@ -2443,8 +2939,8 @@ "minor": 12, "patch": 8, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "6791e6572b6f412fab38e6032dac87c37d6870b12c598f17c76c230678a7a86d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "f0b61f319a1e9d2dc03a086df2b7c4721bcdbcfd2dbf4fe24d7cb3fea75fb2bb", "variant": null }, "cpython-3.12.8-linux-x86_64-gnu": { @@ -2459,8 +2955,8 @@ "minor": 12, "patch": 8, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "698e53b264a9bcd35cfa15cd680c4d78b0878fa529838844b5ffd0cd661d6bc2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "5591da823631467e204baa520ee379ba09b5699b2e2c322c456c44cb2c2e60eb", "variant": null }, "cpython-3.12.8-linux-x86_64-musl": { @@ -2475,8 +2971,8 @@ "minor": 12, "patch": 8, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "451c02f24d1883c2f8ed80914ca4d8567dbe429888332e7cc6cf597e8a7c2555", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "6f931dbe2058e3dfa1d5f381a501d3726ed2191ad464167904dd7b198cb852d2", "variant": null }, "cpython-3.12.8-linux-x86_64_v2-gnu": { @@ -2491,8 +2987,8 @@ "minor": 12, "patch": 8, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "9cf1273ae2fdbbbf308bd81ad2d0b384bec963097a83f8a6455bb187a7714d82", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "71fa45cf960cafd0097f4825d4539833077031e3152a439ab681c27a61665cdf", "variant": null }, "cpython-3.12.8-linux-x86_64_v2-musl": { @@ -2507,8 +3003,8 @@ "minor": 12, "patch": 8, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "630e0131c947a82b342cc8115e85a98a70bb1ebb1580c88b01e5c1dc61d25814", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "8c6430da267171cff9ac741e98d6c64785860af93f530df818ed7dcf57444d7c", "variant": null }, "cpython-3.12.8-linux-x86_64_v3-gnu": { @@ -2523,8 +3019,8 @@ "minor": 12, "patch": 8, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "07319b611e2563ef5281ab4233c78bab1eca02be498fec636a2233c46dd81334", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "3337c050775285e757406bcac24f221383958f75edb1e2ed923b0abdfd5ee350", "variant": null }, "cpython-3.12.8-linux-x86_64_v3-musl": { @@ -2539,8 +3035,8 @@ "minor": 12, "patch": 8, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "81769694bda1d525a9d9b7f493e71ae816ce939e208561c5b8b1362d6a484ed8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "fad5ae4f20f39d0aadf42d7d79030eb5a3b56543808a5adba7020753718cc431", "variant": null }, "cpython-3.12.8-linux-x86_64_v4-gnu": { @@ -2555,8 +3051,8 @@ "minor": 12, "patch": 8, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "4e83c898973be409cf242c6dfd33a2576433f36f58f4000d6f120cc436edb5cb", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "f6f08b06acfded7fecdc62885e8ccc999cd2984dc64d792c8f8cf0a481a1623b", "variant": null }, "cpython-3.12.8-linux-x86_64_v4-musl": { @@ -2571,8 +3067,8 @@ "minor": 12, "patch": 8, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "d0c84e94534042361f715a8e1cf9139745ab6804ab0b78289b4dd13c533c9930", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "f5275e0573b082b3e75d424354eefe95ddcc85b7a550f6818d1fd03c5b1b9569", "variant": null }, "cpython-3.12.8-windows-i686-none": { @@ -2587,8 +3083,8 @@ "minor": 12, "patch": 8, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "cbdf8fca5d3a43e8fe06d2d56f969dbc46051439828211f73047b4800c9c5ed6", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "6af6bf6a7981cbe086b394c0d56c95e66d660f9b2dbfe452831990fe2da751c3", "variant": null }, "cpython-3.12.8-windows-x86_64-none": { @@ -2603,8 +3099,8 @@ "minor": 12, "patch": 8, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "1a702b3463cf87ec0d2e33902a47e95456053b0178fe96bd673c1dbb554f5d15", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "2c6af885033be26a8a2ac2e069aac4889c53ab9d212fdffe0d7c8157b66a3d14", "variant": null }, "cpython-3.12.8+debug-linux-aarch64-gnu": { @@ -2619,8 +3115,8 @@ "minor": 12, "patch": 8, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "d860dfa85e95daa534c810b734c1cd24457abee9f34fb044b280f1a04a371c2c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "7c71910f4d65ea7c3f466868d0ed218a8f3eb5dfeb14a25e53b9229237c4d0d5", "variant": "debug" }, "cpython-3.12.8+debug-linux-armv7-gnueabi": { @@ -2635,8 +3131,8 @@ "minor": 12, "patch": 8, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "2267a7c356858bbd99661f308c23af6e36a0ad383d39c44589df7f91154102b7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "328c533bf6af310b7df899d92211ffb305b098c3399e5d5cf082c9c85b85e770", "variant": "debug" }, "cpython-3.12.8+debug-linux-armv7-gnueabihf": { @@ -2651,8 +3147,8 @@ "minor": 12, "patch": 8, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "214e513d5dc8ba88245566d40686442416c2b030f046a1052195a46464442af6", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "0bb92451ea907458b0a88d06451fc1b8c03c06e9d0eef320fd042ba5de6d1a8d", "variant": "debug" }, "cpython-3.12.8+debug-linux-powerpc64le-gnu": { @@ -2667,8 +3163,8 @@ "minor": 12, "patch": 8, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "0da419b4733b298c9295a9beb169bd4ceb4fd644f17dc92738099c885e91ba53", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "b14918340ef09312ac34c188fc577484691846673dac48d75c54f79eb030e1c3", "variant": "debug" }, "cpython-3.12.8+debug-linux-s390x-gnu": { @@ -2683,8 +3179,8 @@ "minor": 12, "patch": 8, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "ca9431ac327f86febe6952a8b27dab4bf61f427ee810b8864bb3fa4ab3447812", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "f16491a4bff9eed72ba96a8778b653ade5eade5df8d2ae5f6bb37973b3b3b044", "variant": "debug" }, "cpython-3.12.8+debug-linux-x86_64-gnu": { @@ -2699,8 +3195,8 @@ "minor": 12, "patch": 8, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "4483afca7cfb89d31b2aa684d7a885cdc535c58de081dbb0dc0a84c28277ef6e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "7f99e4132717dfafdeaa3bc20d3528d69f194eca7b15b589bb1ee0244af71538", "variant": "debug" }, "cpython-3.12.8+debug-linux-x86_64-musl": { @@ -2715,8 +3211,8 @@ "minor": 12, "patch": 8, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "c541d6292f5e26fe4183a92f6d87f6942a30d3a9c67edaea83befb4c495f0e02", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "3034a8df442d5ec7bca64e71c5de4142406526b6a8a128acc80e72ff96a8971e", "variant": "debug" }, "cpython-3.12.8+debug-linux-x86_64_v2-gnu": { @@ -2731,8 +3227,8 @@ "minor": 12, "patch": 8, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "5277a81cb19da7eab88f7dfff7131133aaa1ad40872b55e686d7ae1e21c44e68", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "34dcc79e4513974c9375ccc236a46742eab11628e6621562fba8a46fa8104352", "variant": "debug" }, "cpython-3.12.8+debug-linux-x86_64_v2-musl": { @@ -2747,8 +3243,8 @@ "minor": 12, "patch": 8, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "b4fb9111ccd598c3e6a826fdfcf2b36f492f4d1968cbe295caa69de3d9847941", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "5604e197149fb37be4460b82e0d12c50a79eb9da8fd1fbeb6ad1cfa23b2e98f2", "variant": "debug" }, "cpython-3.12.8+debug-linux-x86_64_v3-gnu": { @@ -2763,8 +3259,8 @@ "minor": 12, "patch": 8, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "f920911fd04de68530a803db01088de854c761b6fda535d5b2696e91795c5e78", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "b5bbc97d8e86f8319213a3932e21d5baa2dedc5a6c1c34a6daebc305c9907edf", "variant": "debug" }, "cpython-3.12.8+debug-linux-x86_64_v3-musl": { @@ -2779,8 +3275,8 @@ "minor": 12, "patch": 8, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "686e1208496a0d76ab1c1bd7676c58178515caca72304bb28e8715d39d992f8c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "5c5aec9c91f1c6bd2701c64cd6ffb27a3da2a325147bf96226ad998d2312017a", "variant": "debug" }, "cpython-3.12.8+debug-linux-x86_64_v4-gnu": { @@ -2795,8 +3291,8 @@ "minor": 12, "patch": 8, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "7e909c386db18316065ca7a88f89f1ed6549ac29c638dc67fc59a1abe8592e47", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "448385e9274e37b33c7001cec7864096a4f6e5c923c03c98a751904d5b43dea9", "variant": "debug" }, "cpython-3.12.8+debug-linux-x86_64_v4-musl": { @@ -2811,8 +3307,8 @@ "minor": 12, "patch": 8, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "94c9da55701f18c2a8b49e6981583c1af72ef1258c31c75c016a2766a2dd59e8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "ed047f25b12696eca1830adb8f47226c49dcd7de74f418ca41442e60bc375023", "variant": "debug" }, "cpython-3.12.7-darwin-aarch64-none": { @@ -6475,8 +6971,8 @@ "minor": 11, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "af82c85992dace78cd2682deb8e9ef41e448f66cb602f31e5b7c7af75a74bbf1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "fc6cb8fa7ed727acdb317fb6e5149cdcd29128583e99a6dc14640a7e57a9cd34", "variant": null }, "cpython-3.11.11-darwin-x86_64-none": { @@ -6491,8 +6987,8 @@ "minor": 11, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "d33b1ee09b2a9fe692cbc6e3ed0fbadb23c430233ce92e2954522c5594c6b274", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "5b036f1403725f91bd2fe5a90c5a763b14da4bc94182fb48e1a62d063de66c5e", "variant": null }, "cpython-3.11.11-linux-aarch64-gnu": { @@ -6507,8 +7003,8 @@ "minor": 11, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "23fa4e8eeb3a9c7fff540b0fdd4047ae5d2e6f0caf5095e7d767ed96ccc7efe7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "541c1d514cd2a3155f4a5805ccece9ac86f510b186e3cf1af7cfd8cf7097d0e9", "variant": null }, "cpython-3.11.11-linux-armv7-gnueabi": { @@ -6523,8 +7019,8 @@ "minor": 11, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "d798c008dfde19f0ffdf3684f49adaf922873bee2ad1a2e5dd8d0dd2ad3a4699", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "01de7a51461cdb25014041c97bebbe80e1f99b1231a525aa7a2463e55347d26f", "variant": null }, "cpython-3.11.11-linux-armv7-gnueabihf": { @@ -6539,8 +7035,8 @@ "minor": 11, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "e838902cb7d47fbf8636d30b59d27182bf818dc633eef4b47c8d28163689b5ff", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "0cad64bf6041f68fc960ab75e6e817cd36046331538c8e70533d709bedb6d8ef", "variant": null }, "cpython-3.11.11-linux-powerpc64le-gnu": { @@ -6555,8 +7051,8 @@ "minor": 11, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "477dbc811b0f834bc3386126925d490ce734c2191a6e5022e1e0ebb346e5ddcf", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "69020a370009ab331c008f333ca6d8ff684b7b120300d195cc6fb0e6d6f641ac", "variant": null }, "cpython-3.11.11-linux-s390x-gnu": { @@ -6571,8 +7067,8 @@ "minor": 11, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "4e6a9f5111d08e0421446de539224addca624865d1d1e0c5c6ce7b2382d6cf11", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "eaff12c96540a87aea624f98e439ce446afe88aca024d106aa3593f007e237d1", "variant": null }, "cpython-3.11.11-linux-x86_64-gnu": { @@ -6587,8 +7083,8 @@ "minor": 11, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "a5448390ef50caaeac60c42806060d0d00488df0b61e7ab4df3b020cceb2bb73", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "56bda37ad88aaf404a8a1f77c725fde18dee6a2b8d30d361cf4b30a1f807d345", "variant": null }, "cpython-3.11.11-linux-x86_64-musl": { @@ -6603,8 +7099,8 @@ "minor": 11, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "916b2d4336d9f672b5b1f0f15b0ee43504ee178244ec6656ce49de57ddefaa76", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "b9950749da7e6147cf8ad0838b9c7660a8e62d248771b3bbfef4d1f33bf2e998", "variant": null }, "cpython-3.11.11-linux-x86_64_v2-gnu": { @@ -6619,8 +7115,8 @@ "minor": 11, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "267dbf5f9272bb2f8336d13b9f2468ff2b5659a5bf820afc564af49531dc676e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "a8bd2b532d3401af71e5470eb41b85d1beb63accca9ea37c3e32cbe44d3119c6", "variant": null }, "cpython-3.11.11-linux-x86_64_v2-musl": { @@ -6635,8 +7131,8 @@ "minor": 11, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "f9678be5d8366faefcabe11ce9db63a3172176e1f3d6f910ec007453fb2e114d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "8c3961b653ea18c76dcf5e2ee05053bfdbe6353f8ba0515316f906354a200d54", "variant": null }, "cpython-3.11.11-linux-x86_64_v3-gnu": { @@ -6651,8 +7147,8 @@ "minor": 11, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "3746c4c74ec610e4425b876a74161ebcd01aaf556d99017254d782e975d35f46", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "3cf193e4c8dd36bb9d1c6494f3e36fb6c2bf91a91ad724ee3ac38fa6fa958a65", "variant": null }, "cpython-3.11.11-linux-x86_64_v3-musl": { @@ -6667,8 +7163,8 @@ "minor": 11, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "6662013e83d5f97c673f6b346f062d29c5a6dda36a088a0deabfd5dcf6fb9b09", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "0be47a46f66cd2d00f97a0a97cc2edede9983d856f4d6822a8ad96a574a463e0", "variant": null }, "cpython-3.11.11-linux-x86_64_v4-gnu": { @@ -6683,8 +7179,8 @@ "minor": 11, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "a93ebc464644990638ae3029c9bbea6249eb90047152dfb98cbaac66da1c199e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "9e27ab9e35c73caca1ab607364456b7e8279def51855ec73b863cafb51f2d493", "variant": null }, "cpython-3.11.11-linux-x86_64_v4-musl": { @@ -6699,8 +7195,8 @@ "minor": 11, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "5d01a991ad42ad49569145d86752aa79d881d49dd9503c8304ac9a7cea0b83c8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "b24e33285bbe050cd122e395c96420a2067449d4d27f94928d89bb2cb6d2064b", "variant": null }, "cpython-3.11.11-windows-i686-none": { @@ -6715,8 +7211,8 @@ "minor": 11, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "063b3582b31d4b5c918439701ad4037cec9b90c8e2e4c55c502a9001c638ebc6", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "026d777d86ebfe687cebda919aaa6eeb2c1d0618270e19ce2996e0e74b648944", "variant": null }, "cpython-3.11.11-windows-x86_64-none": { @@ -6731,8 +7227,8 @@ "minor": 11, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "f5bceb5efff7802e38638caf9dd748a88d7b67a4620d36f7511c2848b1eb83e3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "eab345a90cac05f6617250f1de33d6606fb29b627c01357033f2276480967730", "variant": null }, "cpython-3.11.11+debug-linux-aarch64-gnu": { @@ -6747,8 +7243,8 @@ "minor": 11, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "58882518507aa6a10e25a46357287fe82be7f6aa5aed8d312de99ca3d2b8f5c3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "ff2c17e8540a5d521bdb9eedad09157aa11fa111c75f62f948cec965b9cb739e", "variant": "debug" }, "cpython-3.11.11+debug-linux-armv7-gnueabi": { @@ -6763,8 +7259,8 @@ "minor": 11, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "12d2a6a596239f8fc6f09b68d3d7f05977e371ae624fb02dd1042ed90337f1cb", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "437f9cdbc4ec1aca9c7597929f80bc8e026a56a0f20694f6d08b547d77fa6289", "variant": "debug" }, "cpython-3.11.11+debug-linux-armv7-gnueabihf": { @@ -6779,8 +7275,8 @@ "minor": 11, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "869cab18e672804220ebeb686243fd5875929d347aa57f7066ee4d382696df55", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "bed3b650e6a59665cfd890b72ab8e36f283dec796e34a2573457b0af972a4e53", "variant": "debug" }, "cpython-3.11.11+debug-linux-powerpc64le-gnu": { @@ -6795,8 +7291,8 @@ "minor": 11, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "5616c62edd0448b073d3c80d3d73d31e64996365bdf98ae9045b25961adb0bac", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "5529208bb0217666aaac11249fd2286d145a579e37c4efbf547f0368e92094df", "variant": "debug" }, "cpython-3.11.11+debug-linux-s390x-gnu": { @@ -6811,8 +7307,8 @@ "minor": 11, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "d05d685cdd17e3b872bfcfcba6bfe37466ed0a4b3d866250fb006efd4c87bb09", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "385f57308833832ae561658a06b3bc5c0c7a3b2f68853d30dc7ba363df496335", "variant": "debug" }, "cpython-3.11.11+debug-linux-x86_64-gnu": { @@ -6827,8 +7323,8 @@ "minor": 11, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "b451c624092f1a10e2557b4516687a6b681da05a153ae967d089d4e577a99590", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "04154ad54d016d5287acf37f92a3139e24ffb101156c370fb5385635a08efe59", "variant": "debug" }, "cpython-3.11.11+debug-linux-x86_64-musl": { @@ -6843,8 +7339,8 @@ "minor": 11, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "9c359bab0e03f14124860f1f5a9f82dffb87bd44ad1dd90f55aebff54d51ef6a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "b04ed35bddf38b2293d37a9a5ca3e92fd97ad75ba62583d99536fe8335df5a18", "variant": "debug" }, "cpython-3.11.11+debug-linux-x86_64_v2-gnu": { @@ -6859,8 +7355,8 @@ "minor": 11, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "fefe7f1a3ca8becfead5a28c31ebc1e4a8edaf6d3594890308f5d0a922b0ce43", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "040a4e319e3919e7dfe09f61ca30fd585cfb6b2496fbd0ca1db6cfcaa289bfaa", "variant": "debug" }, "cpython-3.11.11+debug-linux-x86_64_v2-musl": { @@ -6875,8 +7371,8 @@ "minor": 11, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "6ea98cde291f6cbc2c23f54818093442c20d26ed0ac21a6e28cbca6c4c633de4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "e2b8a9eaa5e70657a996ec1dfa7efbfdb3bd870e38d432087a25d6108f3f2c82", "variant": "debug" }, "cpython-3.11.11+debug-linux-x86_64_v3-gnu": { @@ -6891,8 +7387,8 @@ "minor": 11, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "885cbb2255ca8cf22254a11174e89761b12d128aebd41183a7fa606896c86250", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "c3869697b9a12c0339067d55610a64277ca74062f50643b2b511c2bcfcd45c18", "variant": "debug" }, "cpython-3.11.11+debug-linux-x86_64_v3-musl": { @@ -6907,8 +7403,8 @@ "minor": 11, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "9f4f1546b11531e09660f94d2be79ef2cf7ae60ab188cb119c074cf03a8d028e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "c471c2021963cf725b5781e35876878f88c3305463ce0288f3e1d614db00fa9c", "variant": "debug" }, "cpython-3.11.11+debug-linux-x86_64_v4-gnu": { @@ -6923,8 +7419,8 @@ "minor": 11, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "dd6f357cef8f7c0cd2294c4cad112443fe057ecd472a5c5d6bade5b00523d35c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "9eb8b3136c27ef54a79b2d1162256c9d2100ed6baa820747a006161c4dd63d46", "variant": "debug" }, "cpython-3.11.11+debug-linux-x86_64_v4-musl": { @@ -6939,8 +7435,8 @@ "minor": 11, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "a997220581f5ed9f8a5d3bd47fae6ef361cfffc84f20c1683bd95a20b6a16c0a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "76f45a3c615c169dc7a2f671914561b25488320c57bd01f8f4692b0c1df56a87", "variant": "debug" }, "cpython-3.11.10-darwin-aarch64-none": { @@ -10859,8 +11355,8 @@ "minor": 10, "patch": 16, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "a5a1eb1f13a688621af748d552888d7b22daaddfe6b9c7069f9c955c0fcf2da3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "b5807207ff3e99436049ef8912dfc5f553bf8b1fa332cf233805784f925fea76", "variant": null }, "cpython-3.10.16-darwin-x86_64-none": { @@ -10875,8 +11371,8 @@ "minor": 10, "patch": 16, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "27bc1b56a5268f79a9eb9bf79f8281b336f8bfffc361032f41811bc3c58cdb1d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "fbd4c16651165764a1b2bcc60b2092d3b9b807108cdb17ae82abeeb3ec598175", "variant": null }, "cpython-3.10.16-linux-aarch64-gnu": { @@ -10891,8 +11387,8 @@ "minor": 10, "patch": 16, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "85d1ba2ada13fdbeedbaad24e1871d49766be1f4ba6cdee3c16ca7c4197cca92", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "ef4e55c684a87ce2bd3e5b38700da512340c98307b57bd4da7cc80fb6afcec79", "variant": null }, "cpython-3.10.16-linux-armv7-gnueabi": { @@ -10907,8 +11403,8 @@ "minor": 10, "patch": 16, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "61f76e2d924b4c6cdd706d322bdda9634bec4d41e50071a03c7e1f7a5178aef2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "3ccaef3b620e988d6cc72b6fe386fa5fdd98bf26fb24b8e1b74b33d94d4c2c69", "variant": null }, "cpython-3.10.16-linux-armv7-gnueabihf": { @@ -10923,8 +11419,8 @@ "minor": 10, "patch": 16, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "68ff1232aeba789195f6061c6c0e2e6868c4a0c8547f2aa7a873c4226bf04a3a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "32fdc81d839c3dc289324a71a207f64feb1804353d68a5b7a0d7bd1dbdf205ac", "variant": null }, "cpython-3.10.16-linux-powerpc64le-gnu": { @@ -10939,8 +11435,8 @@ "minor": 10, "patch": 16, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "57282eba8bccf723af7edf0e018407ea5a035deed9daa5fe684c980d4dd2aebc", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "4f90917977d089b3a3a7d67130737f9ba28baea2d6508f35da92ca103f3b3551", "variant": null }, "cpython-3.10.16-linux-s390x-gnu": { @@ -10955,8 +11451,8 @@ "minor": 10, "patch": 16, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "d2048f722562ff30f268e89310f9e567508d3dd6e071ced4c04b4a4aceb6236c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "77c8370579ed0dc38ffc351010acfb5b3bc6663e91230c8d462d8a323c382863", "variant": null }, "cpython-3.10.16-linux-x86_64-gnu": { @@ -10971,8 +11467,8 @@ "minor": 10, "patch": 16, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "ec5ff780e53c9af0399086f17299a88e1dd3de707940eab76fa6ef15642edcbd", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "3ba89e564ace5536a62783362c3aaf8a18d882007d9f202aff26cd8d0de0b581", "variant": null }, "cpython-3.10.16-linux-x86_64-musl": { @@ -10987,8 +11483,8 @@ "minor": 10, "patch": 16, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "9a6f91e3166e36aceb1dfc1f2246f4253b62de7ca0ee0534282e0ded229ab1f2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "f6734667688ee41ada182024f5bf369c764060a44905a17fa1df5dfa3dc76ae7", "variant": null }, "cpython-3.10.16-linux-x86_64_v2-gnu": { @@ -11003,8 +11499,8 @@ "minor": 10, "patch": 16, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "99e51551b920a73066ca0e6b4139c1e47cecb0bd97711d8efaba819c9c3cf5ed", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "d04b15f020598b111ad1b4db757a2e26106944a6d58e1241b129636e127929e0", "variant": null }, "cpython-3.10.16-linux-x86_64_v2-musl": { @@ -11019,8 +11515,8 @@ "minor": 10, "patch": 16, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "bcd8a5c316e1e6727bb891d1b0ac58f3cf0b492a1ca55db4d7b54a8655a3c52c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "eea24e9df73f15183f34f69cc1a4db30013cb4c9f0d884987018a2b9d23ee984", "variant": null }, "cpython-3.10.16-linux-x86_64_v3-gnu": { @@ -11035,8 +11531,8 @@ "minor": 10, "patch": 16, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "bb7cc15b0870979a101d887ec6ba5ed7ac38063a1a02201b709218a32f6151fa", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "c889ba100de9bfcab46950c1db000c66a8f0cc24e9de028e8223490a903b7865", "variant": null }, "cpython-3.10.16-linux-x86_64_v3-musl": { @@ -11051,8 +11547,8 @@ "minor": 10, "patch": 16, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "37b96735ec9776fb40a17d22761d9ed5b4d42ff891cfb3d8ef9ac08411e9051a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "f45527ffb0238d4918f9b76ec59469ae32e2820956ecb9d454d41fd843e7b37c", "variant": null }, "cpython-3.10.16-linux-x86_64_v4-gnu": { @@ -11067,8 +11563,8 @@ "minor": 10, "patch": 16, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "344b34b0c8abd2e536c4b16ab38773e4f4b6c490000a4df162d9181fb396de7c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "c0d770782f2176657c8fe2aaef3a58d61ce975c794510424ec8c9d9629ce0b32", "variant": null }, "cpython-3.10.16-linux-x86_64_v4-musl": { @@ -11083,8 +11579,8 @@ "minor": 10, "patch": 16, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "be3471934543269246ce9b5e33552d9480fffa038296a5c7875d6ca194ddda5d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "625fa12389d3bc1e08e477e95547a81bc1bc5aa3152043b53e266a6d865dac4c", "variant": null }, "cpython-3.10.16-windows-i686-none": { @@ -11099,8 +11595,8 @@ "minor": 10, "patch": 16, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "56462270fd467a3cb2a353a6b6b7c8cd7b2734513dfa87638313db6e769407af", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "4af78f6d3a9e423e5dd47be8ca84027a9fd0d0aee48f2e1cbccdb24e7980deb7", "variant": null }, "cpython-3.10.16-windows-x86_64-none": { @@ -11115,8 +11611,8 @@ "minor": 10, "patch": 16, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "6121810d6899d1bfd89a1c3b7fba3ece4402a7f49772a3ebef6b4c707bcf22ec", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "df76a5d696485937e51cf9d2ebf8a2d559d4cb064d3e721714f6470eb35d90cc", "variant": null }, "cpython-3.10.16+debug-linux-aarch64-gnu": { @@ -11131,8 +11627,8 @@ "minor": 10, "patch": 16, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "c8271d1a53368d685fa1ea831a052c8ffc9be4ea7438304e04a27948bdd98675", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "34e70f8befedec64ea2c2b42a4c065389c7ce0a6d213ecc62161db6181a56abd", "variant": "debug" }, "cpython-3.10.16+debug-linux-armv7-gnueabi": { @@ -11147,8 +11643,8 @@ "minor": 10, "patch": 16, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "6de8c505025663bb6c1de9c277cde36e9b03f03d82e363d353ed34c6f74c5c45", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "d8dda934f097fcb7141434e696401abc4ce9b9608d2966833096ad4edbeb028d", "variant": "debug" }, "cpython-3.10.16+debug-linux-armv7-gnueabihf": { @@ -11163,8 +11659,8 @@ "minor": 10, "patch": 16, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "bcd7f6ce32cecee8997be8330beca24e6895f055da6ea96d053b815718e94d85", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "a604ff286a3710debde1b0525064cb1e77ca5db7147972f74cad2bcf24f56109", "variant": "debug" }, "cpython-3.10.16+debug-linux-powerpc64le-gnu": { @@ -11179,8 +11675,8 @@ "minor": 10, "patch": 16, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "d8ea2bfeaf07584c7896d40e8adf6d18cc7d4a60002fce0399bdbf65d95bd085", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "3bc82283a1d70645f3f7a076a68e58915da533d4d7c527e13111fe5b6edb5b1a", "variant": "debug" }, "cpython-3.10.16+debug-linux-s390x-gnu": { @@ -11195,8 +11691,8 @@ "minor": 10, "patch": 16, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "5ab813240d9fa536ccbd19c9de4820e58467c2f223830fa555cfb63e528a3818", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "8951d8d0fe0ea3f59ee6d2bc995d158083ef687e3e2283cb3eda2368b134503f", "variant": "debug" }, "cpython-3.10.16+debug-linux-x86_64-gnu": { @@ -11211,8 +11707,8 @@ "minor": 10, "patch": 16, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "72ccfb128407ddd4406c8fe09caf5049bd38afc60788bf4f635aa89847c67b97", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "8e50a2deeb93c7da887bc0ccfd64b52db6fde29f6472d253e136184d38e9a393", "variant": "debug" }, "cpython-3.10.16+debug-linux-x86_64-musl": { @@ -11227,8 +11723,8 @@ "minor": 10, "patch": 16, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "cd5532164297159b66e697b59bd66d321a164088f6e0c5870a17da0f9a86f5db", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "c66094b325b79252e36bfa558d3665a5cacebd0ae71a2e4363f11dd91bf50fc7", "variant": "debug" }, "cpython-3.10.16+debug-linux-x86_64_v2-gnu": { @@ -11243,8 +11739,8 @@ "minor": 10, "patch": 16, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "e509c3ea9b395e53001e319251932c050118de5ed1116155372c3953d0dbbf90", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "a790bff174552301991abe4a24d58b27db112384c588a19fdd13ced074cb4864", "variant": "debug" }, "cpython-3.10.16+debug-linux-x86_64_v2-musl": { @@ -11259,8 +11755,8 @@ "minor": 10, "patch": 16, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "c46c7cec00605ab090b0c61059fdefcd80503b9763fd59344312d19bdaae15a5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "e2a4e094509e4772a530eee3b55b08092172ea42f1b05b730b95aa843e7db0b1", "variant": "debug" }, "cpython-3.10.16+debug-linux-x86_64_v3-gnu": { @@ -11275,8 +11771,8 @@ "minor": 10, "patch": 16, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "d20e5e6069de6c00d104b7887e4e11e072cfdbd015fc8851d78857af4f3894a2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "57acf73e7e7a33094daf94ef3570b33373460980bb9e9e526f7b8e43b7c38c7e", "variant": "debug" }, "cpython-3.10.16+debug-linux-x86_64_v3-musl": { @@ -11291,8 +11787,8 @@ "minor": 10, "patch": 16, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "871e66c1c0a1c25bfbee6f89a58f2e2fc361f44654d9af27a3be8722b5f507a8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "514586b119da1640acaa1c8cf14ede4457bc7fd1ae7b1634905653e58332c460", "variant": "debug" }, "cpython-3.10.16+debug-linux-x86_64_v4-gnu": { @@ -11307,8 +11803,8 @@ "minor": 10, "patch": 16, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "9478741cd46afe9ddebd07b4f022b2c4ed1fee6e24289ebfbf99c53a79d07b46", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "065db4b882e3750e0721a7cf69e370aaef2d91defb9fbde33d82a0a1d93a98fa", "variant": "debug" }, "cpython-3.10.16+debug-linux-x86_64_v4-musl": { @@ -11323,8 +11819,8 @@ "minor": 10, "patch": 16, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "2f3a87d8015e5e551ac1637b11418fbc1eeef08a7828169edaceedf17d2db65b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "4e6f9dd48ae8193b928a71b20559d4a44cffb6693d22810ecd20b9a04e888f7d", "variant": "debug" }, "cpython-3.10.15-darwin-aarch64-none": { @@ -16939,8 +17435,8 @@ "minor": 9, "patch": 21, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "f0628c6dee878610c1e3da924c8d930c6948e85c56e75b95b3d437e902acf782", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "65de6878752c9603772e0e4543dfa66e287d770c013eed14a6530d30f901585b", "variant": null }, "cpython-3.9.21-darwin-x86_64-none": { @@ -16955,8 +17451,8 @@ "minor": 9, "patch": 21, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "ed5c87541ea92d5dd66a6187dbec4d43d395d6908f7f42e650e12e62bf6fe145", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "d0936e25d48a471ac7bcbb64e8ed02bc493a7e5d46ed879a580c006db5bb75ef", "variant": null }, "cpython-3.9.21-linux-aarch64-gnu": { @@ -16971,8 +17467,8 @@ "minor": 9, "patch": 21, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "72c1ce8791b78719080fc00575bc96f21de1024955a56d00b151eeb774804bc9", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "59044e65adff98e98ea61fc68a71299722fccf9c162f1908c8d53b94e994039c", "variant": null }, "cpython-3.9.21-linux-armv7-gnueabi": { @@ -16987,8 +17483,8 @@ "minor": 9, "patch": 21, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "8dcfda0951f25bc5be61ef244f8892a2015ba4512bcabcf37005480683f6e72b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "ecdf124007832d1cbb5092c4b5232f9c8ea88c7cd1abd47ea9703028f785a88f", "variant": null }, "cpython-3.9.21-linux-armv7-gnueabihf": { @@ -17003,8 +17499,8 @@ "minor": 9, "patch": 21, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "f6bac804b844fc2ad75e98bfa4ba5fb2df17dd484a6f8b7b9fda7e55e4de283b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "874aa9b7b4a45512f0b8e90020cc3b38d7e1e5c21fead24e67a57ebcfb5d1707", "variant": null }, "cpython-3.9.21-linux-powerpc64le-gnu": { @@ -17019,8 +17515,8 @@ "minor": 9, "patch": 21, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "eff34eb5c7314c98382a8028647c3e0c788c48aab12879282feae2e7e176af6c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "5fa9f333333cc09e5602fd89d3ce6b9ad9b4e2ffd52d3dc29719a7194c2a7491", "variant": null }, "cpython-3.9.21-linux-s390x-gnu": { @@ -17035,8 +17531,8 @@ "minor": 9, "patch": 21, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "a1c252593e364e7fdb99f48b233311c54001a6e993057948a232ead94b10329e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "cbd9a8e187d18fe370e5f71f4ef77c4e28813de8bef8018e35d95cb2c2f545a6", "variant": null }, "cpython-3.9.21-linux-x86_64-gnu": { @@ -17051,8 +17547,8 @@ "minor": 9, "patch": 21, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "e0ce3038ef0e779791f62ac64ed08bfaf4e09bc48ff15a82038cef2009124cbf", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "8df8d0484cda57420c2cb9ab1c805b06b3e0b01fd519f08bda0617282d2b68b2", "variant": null }, "cpython-3.9.21-linux-x86_64-musl": { @@ -17067,8 +17563,8 @@ "minor": 9, "patch": 21, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "57ba961d7cd48a4473000a0fb10581094cd5fd4cd33558b3ddc8ae6856097e80", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "7b1774e3db1e8e8d5f4eaafe2ba3f7427a7caa6955a6b74e05eb9673bd08ba4f", "variant": null }, "cpython-3.9.21-linux-x86_64_v2-gnu": { @@ -17083,8 +17579,8 @@ "minor": 9, "patch": 21, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "a96090f2eb47952b3abb6815864d7c4477bbf23d5d2559c4514140c674c8835b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "800218e9d232e2e7d734922ddda6770911895fd9bc1e08e9c6cb96fa01c46257", "variant": null }, "cpython-3.9.21-linux-x86_64_v2-musl": { @@ -17099,8 +17595,8 @@ "minor": 9, "patch": 21, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "fb308474d3d148fbe4223704d47f4397a85317d1000dd9978f2cd97c7992a931", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "6c7b7304ea033e38e1b47d16ca784bee00f800e4c9977bcac41fbd106638652f", "variant": null }, "cpython-3.9.21-linux-x86_64_v3-gnu": { @@ -17115,8 +17611,8 @@ "minor": 9, "patch": 21, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "b9d39c72d6f503e7fb759c32310fbf1cb402189c2bb26f1d25608b8c1d8a41b6", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "c5c2ddfb7d259c607e180a97ad1a5c2bca62a4b1bd43b51b0ef7a3fb029b5387", "variant": null }, "cpython-3.9.21-linux-x86_64_v3-musl": { @@ -17131,8 +17627,8 @@ "minor": 9, "patch": 21, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "6dd47b0f86888312cee676a4ac63ddd1e217009317e8f0c6dcb82d681a56277b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "08099c07425761f9cd100a7d0752bf66e228075941560e9afebe65a44ad33534", "variant": null }, "cpython-3.9.21-linux-x86_64_v4-gnu": { @@ -17147,8 +17643,8 @@ "minor": 9, "patch": 21, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "4f6ca60512658efeb4b73afd8b86883948370ed02081af2f722b2c63e08fc1ec", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "b4bafc45b439532bd6c9fe32410b91ed17152f38711b4df428746fd5314f1fb7", "variant": null }, "cpython-3.9.21-linux-x86_64_v4-musl": { @@ -17163,8 +17659,8 @@ "minor": 9, "patch": 21, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "3d39de9f4c52465a546e2bed637e20b1811ba8d85b1400dad28dbb2db4af3c2f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "cca7dba1d42006b97e87b0da20bf2a7ad82909b7cb7a033649a2ea3363cb1fc4", "variant": null }, "cpython-3.9.21-windows-i686-none": { @@ -17179,8 +17675,8 @@ "minor": 9, "patch": 21, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "b0c1a25cd1c07bdf7d3d861d39527eaff66363a28eb3306463d8ffc134d55743", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "9c950f9f6185b488c0d57a2d5d00bcdf6dbeefcb72c62b4ff83634ef280a08f7", "variant": null }, "cpython-3.9.21-windows-x86_64-none": { @@ -17195,8 +17691,8 @@ "minor": 9, "patch": 21, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "acd70e2f5596d8e24a06d47394b15d326fc12c26662a8cc018e3635fb3025897", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "8522829a1617039b56b5f2744c972203fdb8897e9092b26cc59cdb1e996119c4", "variant": null }, "cpython-3.9.21+debug-linux-aarch64-gnu": { @@ -17211,8 +17707,8 @@ "minor": 9, "patch": 21, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "5aa07292964dc64bddec6f1c4f8d08e0a8737ac2f3a0fa0df5d5adb018126f39", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "d43465f8514ac4166766deb52e3b115d7a14cbe3245968dae9e55255b398623c", "variant": "debug" }, "cpython-3.9.21+debug-linux-armv7-gnueabi": { @@ -17227,8 +17723,8 @@ "minor": 9, "patch": 21, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "feb7a8bd2adbb8a041d32cfa5180271997817be6d27c2a02688f4afaf8da07a4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "86763a083b629fe4d1e366ff95d9eecb8b1dcf62cf3e618b90405959cd6c7057", "variant": "debug" }, "cpython-3.9.21+debug-linux-armv7-gnueabihf": { @@ -17243,8 +17739,8 @@ "minor": 9, "patch": 21, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "1513e3114c94a2322800a35eadfa9fed72d237f8c6fd48c5b2df4cfb25bebeb0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "e881f54eef0aaaa7e5778f81acf7c53edc3393eb86f9b531d64e37697f67b94e", "variant": "debug" }, "cpython-3.9.21+debug-linux-powerpc64le-gnu": { @@ -17259,8 +17755,8 @@ "minor": 9, "patch": 21, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "3e3e2c3676816328cbfc6a7fcffb4fa644c2c30133484104742c75d7e6c4b284", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "2b9c4a88af78a1d0f0b550da738be658b91454df929504ce73153c658861959f", "variant": "debug" }, "cpython-3.9.21+debug-linux-s390x-gnu": { @@ -17275,8 +17771,8 @@ "minor": 9, "patch": 21, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "36685ccf8a4f3adb8e0e9f7ab1b5ea0d681b0ee407e4f82fae2c2c8d43c2534c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "18fc0ae17824a95487a5b81221c9e857e1f839729923cc9aad36ba6e77910d85", "variant": "debug" }, "cpython-3.9.21+debug-linux-x86_64-gnu": { @@ -17291,8 +17787,8 @@ "minor": 9, "patch": 21, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "9d1c3af4ba47e3ff1f8c47c574b22f3fb1ed1f71a26ec668d5b1bbafd83cf73f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "75c8268c33fa84d4c7a27a00fc7119932a93d1404b354ec64543ee595bb61f24", "variant": "debug" }, "cpython-3.9.21+debug-linux-x86_64-musl": { @@ -17307,8 +17803,8 @@ "minor": 9, "patch": 21, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "adb778b6fa75576d6d044869c03e9c7c8c6ba6fcead0c74ccf273ff4651f7f8c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "462180c309753cf6e70297d08f938a371b5e0052f7ec2ea702b652c59fab60b8", "variant": "debug" }, "cpython-3.9.21+debug-linux-x86_64_v2-gnu": { @@ -17323,8 +17819,8 @@ "minor": 9, "patch": 21, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "5f61e51a1472cfda28b25e6ad37e93a10a35010d58b13e642eb5ec6bd0e2ac8c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "db3f06dd3e8820c0a898ccd676224ae7a34386be446f2cf0c3d7c3f32b12f96c", "variant": "debug" }, "cpython-3.9.21+debug-linux-x86_64_v2-musl": { @@ -17339,8 +17835,8 @@ "minor": 9, "patch": 21, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "87ed1b3450896098e21c54b3e9094fe46cc154d96ee710f54395dc67c2dd5797", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "13dc373140b2e6430ec8508e7340843f3a4e86b43f36af23a2e4591e73282d54", "variant": "debug" }, "cpython-3.9.21+debug-linux-x86_64_v3-gnu": { @@ -17355,8 +17851,8 @@ "minor": 9, "patch": 21, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "969996c06722faf4360a383dea039ae740e514219b9f322f45049ee578c77961", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "84aca4167d03a9d704d4377b7313466fbab2e5bb11fc3c3e77a98e16a8260252", "variant": "debug" }, "cpython-3.9.21+debug-linux-x86_64_v3-musl": { @@ -17371,8 +17867,8 @@ "minor": 9, "patch": 21, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "d16628417b266648e0ce95f90a38d2d1833ef13a8c1a7760e63814a0b8499a68", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "5ad0883b1832ba9522a32b815dcf3693bc5234b1e617f7816f626397c3c420cc", "variant": "debug" }, "cpython-3.9.21+debug-linux-x86_64_v4-gnu": { @@ -17387,8 +17883,8 @@ "minor": 9, "patch": 21, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "d1d6f4ff07e59d89654abc92d349f9086faec559c99124831063d6882f8e73f2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "20d5a79dda4bc1e3f7780af7ad88fc08590ad5332336c6870c7e7f79450242ae", "variant": "debug" }, "cpython-3.9.21+debug-linux-x86_64_v4-musl": { @@ -17403,8 +17899,8 @@ "minor": 9, "patch": 21, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "37bf2eea32c081c9cb4f66cfca66793960e8262836a05b521a16c1e492f9f2ec", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "2579017e1f3cd5cd2a4556c989ece393d26b479266d3d3916c2cd69ae0a93240", "variant": "debug" }, "cpython-3.9.20-darwin-aarch64-none": { diff --git a/crates/uv-python/src/discovery.rs b/crates/uv-python/src/discovery.rs index d7210ab4d4bc..77fb6acf64be 100644 --- a/crates/uv-python/src/discovery.rs +++ b/crates/uv-python/src/discovery.rs @@ -1154,7 +1154,7 @@ pub(crate) fn is_windows_store_shim(path: &Path) -> bool { component.starts_with("python") && std::path::Path::new(component) .extension() - .map_or(false, |ext| ext.eq_ignore_ascii_case("exe")) + .is_some_and(|ext| ext.eq_ignore_ascii_case("exe")) }) { return false; @@ -2220,7 +2220,7 @@ impl FromStr for VersionRequest { }; // Cast the release components into u8s since that's what we use in `VersionRequest` - let Ok(release) = try_into_u8_slice(version.release()) else { + let Ok(release) = try_into_u8_slice(&version.release()) else { return Err(Error::InvalidVersionRequest(s.to_string())); }; diff --git a/crates/uv-python/src/downloads.inc b/crates/uv-python/src/downloads.inc index 056125b91b40..d6a2331bda20 100644 --- a/crates/uv-python/src/downloads.inc +++ b/crates/uv-python/src/downloads.inc @@ -8,6 +8,402 @@ use crate::PythonVariant; use uv_pep440::{Prerelease, PrereleaseKind}; pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 14, + patch: 0, + prerelease: Some(Prerelease { kind: PrereleaseKind::Alpha, number: 3 }), + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch{ + family: target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64), + variant: None, + }, + os: Os(target_lexicon::OperatingSystem::Darwin(None)), + libc: Libc::None, + variant: PythonVariant::Default + }, + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-aarch64-apple-darwin-install_only_stripped.tar.gz", + sha256: Some("4d474a12d52c316f0b68790fc1f8d747cc665f3d9a4d44c8f6757b8e4aeed497") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 14, + patch: 0, + prerelease: Some(Prerelease { kind: PrereleaseKind::Alpha, number: 3 }), + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch{ + family: target_lexicon::Architecture::X86_64, + variant: None, + }, + os: Os(target_lexicon::OperatingSystem::Darwin(None)), + libc: Libc::None, + variant: PythonVariant::Default + }, + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-x86_64-apple-darwin-install_only_stripped.tar.gz", + sha256: Some("f5da54ddec8866113cb2809e1dec0ec0f4617a149b21945da7b8c8f5ccab8e5e") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 14, + patch: 0, + prerelease: Some(Prerelease { kind: PrereleaseKind::Alpha, number: 3 }), + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch{ + family: target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64), + variant: None, + }, + os: Os(target_lexicon::OperatingSystem::Linux), + libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("2796c6573a5359c88cb8c8b2daa561c7377ad7876f2fe45983fb977b1d10e208") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 14, + patch: 0, + prerelease: Some(Prerelease { kind: PrereleaseKind::Alpha, number: 3 }), + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch{ + family: target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7), + variant: None, + }, + os: Os(target_lexicon::OperatingSystem::Linux), + libc: Libc::Some(target_lexicon::Environment::Gnueabi), + variant: PythonVariant::Default + }, + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + sha256: Some("de6d76889576fdfb63a6f0df3df651bed4c9586a6c4c352a3d5dc2d202837d83") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 14, + patch: 0, + prerelease: Some(Prerelease { kind: PrereleaseKind::Alpha, number: 3 }), + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch{ + family: target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7), + variant: None, + }, + os: Os(target_lexicon::OperatingSystem::Linux), + libc: Libc::Some(target_lexicon::Environment::Gnueabihf), + variant: PythonVariant::Default + }, + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + sha256: Some("820f889a25e0ceea42742b3496ea7ffddf323a61db4a97c3712554b254650de9") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 14, + patch: 0, + prerelease: Some(Prerelease { kind: PrereleaseKind::Alpha, number: 3 }), + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch{ + family: target_lexicon::Architecture::Powerpc64le, + variant: None, + }, + os: Os(target_lexicon::OperatingSystem::Linux), + libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("e4595b35c5d0040527b9fe32ae53a3ad223ec931ccd579d0d5dd6235eaf81378") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 14, + patch: 0, + prerelease: Some(Prerelease { kind: PrereleaseKind::Alpha, number: 3 }), + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch{ + family: target_lexicon::Architecture::S390x, + variant: None, + }, + os: Os(target_lexicon::OperatingSystem::Linux), + libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("0a632b7493756ea057f0037a0794bb3bcaa32816fd57dde9dd6103dddc0099f8") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 14, + patch: 0, + prerelease: Some(Prerelease { kind: PrereleaseKind::Alpha, number: 3 }), + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch{ + family: target_lexicon::Architecture::X86_64, + variant: None, + }, + os: Os(target_lexicon::OperatingSystem::Linux), + libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("f143d48894e9c865a520982f28a575f138623b5ca14ca082f126171ccba46696") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 14, + patch: 0, + prerelease: Some(Prerelease { kind: PrereleaseKind::Alpha, number: 3 }), + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch{ + family: target_lexicon::Architecture::X86_64, + variant: Some(ArchVariant::V2), + }, + os: Os(target_lexicon::OperatingSystem::Linux), + libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("7aed85dc32191b6601abff3e55f1933795df97fada0982130d186f612b745407") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 14, + patch: 0, + prerelease: Some(Prerelease { kind: PrereleaseKind::Alpha, number: 3 }), + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch{ + family: target_lexicon::Architecture::X86_64, + variant: Some(ArchVariant::V3), + }, + os: Os(target_lexicon::OperatingSystem::Linux), + libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("d10663344c4bd115744afda54616053a005b51565ac9f383ff8c2bc097b20c2a") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 14, + patch: 0, + prerelease: Some(Prerelease { kind: PrereleaseKind::Alpha, number: 3 }), + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch{ + family: target_lexicon::Architecture::X86_64, + variant: Some(ArchVariant::V4), + }, + os: Os(target_lexicon::OperatingSystem::Linux), + libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("091ba9cf1fe4810fc5fbf5513d5bec4bff255bbebba4605ea3825f8f4bb90d1d") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 14, + patch: 0, + prerelease: Some(Prerelease { kind: PrereleaseKind::Alpha, number: 3 }), + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch{ + family: target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64), + variant: None, + }, + os: Os(target_lexicon::OperatingSystem::Darwin(None)), + libc: Libc::None, + variant: PythonVariant::Freethreaded + }, + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + sha256: Some("81a500eb67385a03e7da7add8dac34a52dcb7ca391b2a4faa493f7ebc8b4ee78") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 14, + patch: 0, + prerelease: Some(Prerelease { kind: PrereleaseKind::Alpha, number: 3 }), + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch{ + family: target_lexicon::Architecture::X86_64, + variant: None, + }, + os: Os(target_lexicon::OperatingSystem::Darwin(None)), + libc: Libc::None, + variant: PythonVariant::Freethreaded + }, + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-x86_64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + sha256: Some("36a393f8d51d565e60ab3471c67c1dd029dc0f9bfb69bc0a890dc60a5764c0df") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 14, + patch: 0, + prerelease: Some(Prerelease { kind: PrereleaseKind::Alpha, number: 3 }), + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch{ + family: target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64), + variant: None, + }, + os: Os(target_lexicon::OperatingSystem::Linux), + libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Freethreaded + }, + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-aarch64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + sha256: Some("2af2e24d0bc0760c0e5ba2a3250c5a94624a3059d234d2bc922abb5904ca1a35") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 14, + patch: 0, + prerelease: Some(Prerelease { kind: PrereleaseKind::Alpha, number: 3 }), + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch{ + family: target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7), + variant: None, + }, + os: Os(target_lexicon::OperatingSystem::Linux), + libc: Libc::Some(target_lexicon::Environment::Gnueabi), + variant: PythonVariant::Freethreaded + }, + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-armv7-unknown-linux-gnueabi-freethreaded%2Blto-full.tar.zst", + sha256: Some("5370785843b4cebd4d3a22d74d8c9d26179cb31ad438dcb17332922f5fc99889") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 14, + patch: 0, + prerelease: Some(Prerelease { kind: PrereleaseKind::Alpha, number: 3 }), + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch{ + family: target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7), + variant: None, + }, + os: Os(target_lexicon::OperatingSystem::Linux), + libc: Libc::Some(target_lexicon::Environment::Gnueabihf), + variant: PythonVariant::Freethreaded + }, + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-armv7-unknown-linux-gnueabihf-freethreaded%2Blto-full.tar.zst", + sha256: Some("7572e0ef1cf8bde126c88a372f015af5d106cf507e4f2461379af5aa74b4806c") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 14, + patch: 0, + prerelease: Some(Prerelease { kind: PrereleaseKind::Alpha, number: 3 }), + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch{ + family: target_lexicon::Architecture::Powerpc64le, + variant: None, + }, + os: Os(target_lexicon::OperatingSystem::Linux), + libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Freethreaded + }, + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-ppc64le-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + sha256: Some("b727b1f3cd424e9fed726871223c64cca306e9a3074da3fe588aec2dd18f78a6") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 14, + patch: 0, + prerelease: Some(Prerelease { kind: PrereleaseKind::Alpha, number: 3 }), + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch{ + family: target_lexicon::Architecture::S390x, + variant: None, + }, + os: Os(target_lexicon::OperatingSystem::Linux), + libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Freethreaded + }, + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-s390x-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + sha256: Some("9de3a8dde0b43b1d69369b8d64f9b8174bee1a979e975fb69ed10d11875bac54") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 14, + patch: 0, + prerelease: Some(Prerelease { kind: PrereleaseKind::Alpha, number: 3 }), + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch{ + family: target_lexicon::Architecture::X86_64, + variant: None, + }, + os: Os(target_lexicon::OperatingSystem::Linux), + libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Freethreaded + }, + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-x86_64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + sha256: Some("f4f151237b4eb6c6b2a5ed34776f6621bf84f88e9195fe5fe4a44a4446bb2ba8") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 14, + patch: 0, + prerelease: Some(Prerelease { kind: PrereleaseKind::Alpha, number: 3 }), + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch{ + family: target_lexicon::Architecture::X86_64, + variant: Some(ArchVariant::V2), + }, + os: Os(target_lexicon::OperatingSystem::Linux), + libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Freethreaded + }, + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-x86_64_v2-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + sha256: Some("3b9bb813870f259c73a3400f8de211c3df03d3544d82b47a9785bb14174107ee") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 14, + patch: 0, + prerelease: Some(Prerelease { kind: PrereleaseKind::Alpha, number: 3 }), + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch{ + family: target_lexicon::Architecture::X86_64, + variant: Some(ArchVariant::V3), + }, + os: Os(target_lexicon::OperatingSystem::Linux), + libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Freethreaded + }, + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-x86_64_v3-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + sha256: Some("2f94f0c5df33e27d5e3f8e53cb96a78cf5f2453961ea496b5b72bb41ae855cc3") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 14, + patch: 0, + prerelease: Some(Prerelease { kind: PrereleaseKind::Alpha, number: 3 }), + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch{ + family: target_lexicon::Architecture::X86_64, + variant: Some(ArchVariant::V4), + }, + os: Os(target_lexicon::OperatingSystem::Linux), + libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Freethreaded + }, + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.14.0a3%2B20250106-x86_64_v4-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + sha256: Some("ba9e511b020df7e04b5f78bb13d7cbc0776e4bddb41101fe26f4bfdaf7b2cd6a") + }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, @@ -23,8 +419,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::None, variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-aarch64-apple-darwin-install_only_stripped.tar.gz", - sha256: Some("25ca42cfac68cfa6bd6aa6c950cb76c950dca56e01ff00458120d3f62967a498") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-aarch64-apple-darwin-install_only_stripped.tar.gz", + sha256: Some("26b2ceded19b2eac3f0ae59d895f5032b541270bc65288c0ea36c6d8ae25e234") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -41,8 +437,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::None, variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64-apple-darwin-install_only_stripped.tar.gz", - sha256: Some("cf3cfc41dc3017732d448f7273ba9a77a5629e3605459feff756922721c410fd") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64-apple-darwin-install_only_stripped.tar.gz", + sha256: Some("d1207ac02ae14a8714a5c15277396719421cd8c60184f4961905bcc6d7978c83") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -59,8 +455,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("c801a80c40ad36d7e3d38d2d550bb64b39af0efef7081a4b1de3e4a43ece5c0d") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("3c720649b2af9873262429074826ef0e35f98140e38df365b36a668573a86cf0") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -77,8 +473,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnueabi), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - sha256: Some("8c32b8a7a210a108ba3bf5391ecba48d30b5794c2d27783fdcec50b95797157e") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + sha256: Some("660a036eced9c1052a80e152f0cfe08dde43cbc38ca73c6b969920e9382dfbe8") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -95,8 +491,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnueabihf), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - sha256: Some("e854912d185dfd6f1e8606cb4a74a95a7535a7c95c58b5be78f4014296b488be") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + sha256: Some("48e0ee03b779be8485f2f74728b2047708626af14e2c6711a384040afcbb8ad5") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -113,8 +509,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("dc519bd25ee659db1b5f1f05e342fdc9c74c348b49f793ca3536f33e189002b0") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("8408e87648118b626d79ccf1b1aa0cbf427442c46b13983f1fa07bc4b7d2cb7c") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -131,8 +527,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("f15df22d793733a457115292289091fbf51d64af042800b1218daa2a0498105f") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("2c6649a03c11582b3b3de95ccc25ecec39e7407ad60ac0f274ef48260070d97f") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -149,8 +545,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("05b3bbc97d113b64f4a0dab680e0dbe73e9f59ce8304312ef64d4b6c2822c520") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("726a608e2867c2867caa2cf3f668cb15018456070eee32375675f190946650bc") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -167,8 +563,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Musl), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - sha256: Some("5bfb969703d2f26c26e3a31b07145d40c7a3910052fdc5f95a199868908c78e3") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + sha256: Some("a7c27836ff102e92d0c604ab5d06a0f9c474c3d87a01689f477c99e3ce4b8a67") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -185,8 +581,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("07af19e001b8bce915296e2f0aaa32314515864d735175d52d604f8f8fef55ad") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("6778cc7eab5e9ee64b880025300dbf36f93bded39a23681e5977004fcecd9cfa") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -203,8 +599,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Musl), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - sha256: Some("27be1c4a64b3b1ace70f3e72662405c393b5e50690516b755f56c65582235e24") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + sha256: Some("212486199b7fd545be66e363051b93297e044353ea035e7dbe94657cafa23e83") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -221,8 +617,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("d56109dd712199b25477154a93eb68b867ad46d5049c07869f8161c5c9271fab") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("e0a7e81a06987129aebc5cd28b1acbc2f5dfb615cb675da37ac8462dbd421ef5") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -239,8 +635,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Musl), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - sha256: Some("4949fd6a337418c9eb7e55871619502b96e8be712711f61414f62a161456b83c") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + sha256: Some("a827eb8b5f736fb060a154a85091f800de320a412204c11e0e34208ebc32f707") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -257,8 +653,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("410460370f55da3b4778f89acafc3ec02d2aa390fb9b48f6b5dbdccfd60d39ac") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("5d7631269cef71e59c5f0bb90c45dca550252c6a54f1b5ec5297fb44351ef94d") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -275,8 +671,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Musl), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - sha256: Some("aa0316647307516ee1028055f0df98f05656033f1968d6a935dd1a34dde955b7") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + sha256: Some("f3f43199919c40d974f6c616b5e8ce820af085c0218fb2415fecbba85c15a9c1") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -293,8 +689,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::None, variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-i686-pc-windows-msvc-install_only_stripped.tar.gz", - sha256: Some("082907fea39786100190137cdcf2d2e3672a33ef90e88ad0b669ba0c5205ec7d") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-i686-pc-windows-msvc-install_only_stripped.tar.gz", + sha256: Some("972858c2d658daa2bd7b8c6bbfe81c5252990323fb64acee2eb0b033d9ad9042") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -311,8 +707,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::None, variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - sha256: Some("af37b475224eb9d37aa90e68094bbc5f9186dd421eb0fdff59f24c34a1eed4f0") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + sha256: Some("45c77db3fefd2ea1ef1c90c8595edfd6d12a53bc5c6cbaa5a2dd5b4a29a045ec") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -329,8 +725,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::None, variant: PythonVariant::Freethreaded }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", - sha256: Some("b7044c2d584eb2261edb18db58a51b8b164e3cf2101a650f7be6d850a2859474") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + sha256: Some("49f7bd481b69a99fbbd444bf03e078c7bcdc30f656042cb2cb72d6dd258425e1") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -347,8 +743,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::None, variant: PythonVariant::Freethreaded }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", - sha256: Some("ccff425786a59f387d449eeff7d8afeab2d11cf2aa3631b5777e7d697f60bacd") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + sha256: Some("a11eb407678ac127269e3d7aff658a94b14ab42cb11e2eaa057d6d7398c486ba") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -365,8 +761,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Freethreaded }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-aarch64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - sha256: Some("5d0b6ab67f02f343348560e32664d5cf4343aa016f34334fc178fe976e5ea74b") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-aarch64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + sha256: Some("f8910a48a7ce3587f21bd080c963a49441ac8c621d38424e3e69ca8dcc65b242") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -383,8 +779,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnueabi), variant: PythonVariant::Freethreaded }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-armv7-unknown-linux-gnueabi-freethreaded%2Blto-full.tar.zst", - sha256: Some("4373948ca535d2c011ddc42e9d7d6b415ac1c2495d2c8ec414cc22042ae0811c") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-armv7-unknown-linux-gnueabi-freethreaded%2Blto-full.tar.zst", + sha256: Some("ba624a019bf9ae15d4ea271537e0e0a913463f2a1c7975c39fda87c1208a7977") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -401,8 +797,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnueabihf), variant: PythonVariant::Freethreaded }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-armv7-unknown-linux-gnueabihf-freethreaded%2Blto-full.tar.zst", - sha256: Some("bbfb13cdf815bceffdf3b22d2992ddd24f09deb3cd3721285438151733c027db") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-armv7-unknown-linux-gnueabihf-freethreaded%2Blto-full.tar.zst", + sha256: Some("87cd5e03afba6164009d3001cd97dcba613a23619801bcf4358dae28e36011c7") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -419,8 +815,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Freethreaded }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-ppc64le-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - sha256: Some("9f82f0e05d37362be651f51a5028979b0409c0b4b3ceecd207b8b4539b8c72ef") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-ppc64le-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + sha256: Some("b9f94f840b390a68e6c34cb0ae56be3a14c067cbdc34144f6edddeefda59391c") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -437,8 +833,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Freethreaded }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-s390x-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - sha256: Some("5b6bacbeb90bac09799f08f9937f811ab87c18e2654f9827f4ef2cb7832d8920") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-s390x-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + sha256: Some("8ce257291bd99bdbfe93688febf5a5fe8b986370df72801b0414386bea4bc99d") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -455,8 +851,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Freethreaded }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - sha256: Some("065368d84d0d271cf081cf8a3599a0bfdcab00d45ddf3cde487dc5f901992ab5") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + sha256: Some("080133624e16068a8d74698fa673ce6f71914ca4d836a760758f383895f4e6b3") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -473,8 +869,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Freethreaded }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64_v2-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - sha256: Some("196aa926332003687eb8167e55d5761439890a763dc3f0a0758355af10979f9f") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64_v2-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + sha256: Some("4d8121f8e42f18da69c8106eca6612b5edfb39ab4684af588c6626f413c78819") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -491,8 +887,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Freethreaded }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64_v3-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - sha256: Some("65176446a0aa53f8cdbcb967d3e70d09e6bb21b058657461041611dc0f6ac111") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64_v3-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + sha256: Some("c756e498571375568928507fbb17bca22e8c02f22cb39fb10efa3bd3c745dc1f") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -509,8 +905,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Freethreaded }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64_v4-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - sha256: Some("a3f43b7b6073ef7a932638c3fde128c9db306c26655ec0cdaf13af9e15f1f0cf") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64_v4-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + sha256: Some("96866696116e5b886eca6052db15da38bb7ab07dce0148fdbc171f53b7da79ae") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -527,8 +923,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::None, variant: PythonVariant::Freethreaded }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-i686-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", - sha256: Some("957da31c99b168d15116af5e1f3c285a5e0da9ed2c6440aa7080f49118c0931a") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-i686-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + sha256: Some("0455a9866b1b02055022e1dd9237875533c57e7bbcd8e7993da9ab16f719f1a8") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -545,8 +941,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::None, variant: PythonVariant::Freethreaded }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", - sha256: Some("e34828f6ae08e252b07a277f0021658963cddca8a858ae858397c1e5898d7e2a") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.13.1%2B20250106-x86_64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + sha256: Some("e2c63cd840a808705f5dd72251d2e07d70ed497c2a36d151b7701ea2c68f8bc6") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -1715,8 +2111,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::None, variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-aarch64-apple-darwin-install_only_stripped.tar.gz", - sha256: Some("abe1de2494bb8b243fd507944f4d50292848fa00685d5288c858a72623a16635") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-aarch64-apple-darwin-install_only_stripped.tar.gz", + sha256: Some("3a31b4f82d589a0ff6efac3cdc1e55284cb67645470b78c5aa50f156c10a93d2") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -1733,8 +2129,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::None, variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-x86_64-apple-darwin-install_only_stripped.tar.gz", - sha256: Some("867c1af10f204224b571f8f2593fc9eb580fe0c2376224d1096ebe855ad8c722") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-x86_64-apple-darwin-install_only_stripped.tar.gz", + sha256: Some("00e2d29ce8a688f5d2f192718e6f87e4aedae88ac2a0958011b8af9a49e049a7") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -1751,8 +2147,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("fb983ec85952513f5f013674fcbf4306b1a142c50fcfd914c2c3f00c61a874b0") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("29df00dc1435f536c50396d1753fb507f81a81c93a04a7c24b145c9ad68798f8") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -1769,8 +2165,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnueabi), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - sha256: Some("0567907c0147753b54683ffdd3559d53aa4f10b203f067628586bdc545dddffb") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + sha256: Some("d648de42934e775c78fc73638d35d021c9bf7306d03c505e10a270e70439bc9e") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -1787,8 +2183,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnueabihf), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - sha256: Some("d970da6a727a069ae765ae067ca93e369cf9881e5140e28f5030f6314595dd9b") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + sha256: Some("fc1bf99d6c64386438d83e1cee031b2764c801e5480d7c221c5625f48bd7381a") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -1805,8 +2201,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("462b028ce314eb57ced802ce7f9459520365ce5d635d7ae24d2d532b78c9ee12") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("58b09124abbdd4408cafc6e93d7df29e21e346f6dde4df38b75dee3ef5f3ef60") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -1823,8 +2219,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("6791e6572b6f412fab38e6032dac87c37d6870b12c598f17c76c230678a7a86d") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("f0b61f319a1e9d2dc03a086df2b7c4721bcdbcfd2dbf4fe24d7cb3fea75fb2bb") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -1841,8 +2237,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("698e53b264a9bcd35cfa15cd680c4d78b0878fa529838844b5ffd0cd661d6bc2") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("5591da823631467e204baa520ee379ba09b5699b2e2c322c456c44cb2c2e60eb") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -1859,8 +2255,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Musl), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - sha256: Some("451c02f24d1883c2f8ed80914ca4d8567dbe429888332e7cc6cf597e8a7c2555") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + sha256: Some("6f931dbe2058e3dfa1d5f381a501d3726ed2191ad464167904dd7b198cb852d2") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -1877,8 +2273,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("9cf1273ae2fdbbbf308bd81ad2d0b384bec963097a83f8a6455bb187a7714d82") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("71fa45cf960cafd0097f4825d4539833077031e3152a439ab681c27a61665cdf") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -1895,8 +2291,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Musl), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - sha256: Some("630e0131c947a82b342cc8115e85a98a70bb1ebb1580c88b01e5c1dc61d25814") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + sha256: Some("8c6430da267171cff9ac741e98d6c64785860af93f530df818ed7dcf57444d7c") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -1913,8 +2309,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("07319b611e2563ef5281ab4233c78bab1eca02be498fec636a2233c46dd81334") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("3337c050775285e757406bcac24f221383958f75edb1e2ed923b0abdfd5ee350") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -1931,8 +2327,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Musl), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - sha256: Some("81769694bda1d525a9d9b7f493e71ae816ce939e208561c5b8b1362d6a484ed8") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + sha256: Some("fad5ae4f20f39d0aadf42d7d79030eb5a3b56543808a5adba7020753718cc431") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -1949,8 +2345,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("4e83c898973be409cf242c6dfd33a2576433f36f58f4000d6f120cc436edb5cb") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("f6f08b06acfded7fecdc62885e8ccc999cd2984dc64d792c8f8cf0a481a1623b") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -1967,8 +2363,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Musl), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - sha256: Some("d0c84e94534042361f715a8e1cf9139745ab6804ab0b78289b4dd13c533c9930") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + sha256: Some("f5275e0573b082b3e75d424354eefe95ddcc85b7a550f6818d1fd03c5b1b9569") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -1985,8 +2381,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::None, variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-i686-pc-windows-msvc-install_only_stripped.tar.gz", - sha256: Some("cbdf8fca5d3a43e8fe06d2d56f969dbc46051439828211f73047b4800c9c5ed6") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-i686-pc-windows-msvc-install_only_stripped.tar.gz", + sha256: Some("6af6bf6a7981cbe086b394c0d56c95e66d660f9b2dbfe452831990fe2da751c3") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -2003,8 +2399,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::None, variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.12.8%2B20241219-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - sha256: Some("1a702b3463cf87ec0d2e33902a47e95456053b0178fe96bd673c1dbb554f5d15") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.12.8%2B20250106-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + sha256: Some("2c6af885033be26a8a2ac2e069aac4889c53ab9d212fdffe0d7c8157b66a3d14") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -4361,8 +4757,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::None, variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-aarch64-apple-darwin-install_only_stripped.tar.gz", - sha256: Some("af82c85992dace78cd2682deb8e9ef41e448f66cb602f31e5b7c7af75a74bbf1") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-aarch64-apple-darwin-install_only_stripped.tar.gz", + sha256: Some("fc6cb8fa7ed727acdb317fb6e5149cdcd29128583e99a6dc14640a7e57a9cd34") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -4379,8 +4775,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::None, variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-x86_64-apple-darwin-install_only_stripped.tar.gz", - sha256: Some("d33b1ee09b2a9fe692cbc6e3ed0fbadb23c430233ce92e2954522c5594c6b274") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-x86_64-apple-darwin-install_only_stripped.tar.gz", + sha256: Some("5b036f1403725f91bd2fe5a90c5a763b14da4bc94182fb48e1a62d063de66c5e") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -4397,8 +4793,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("23fa4e8eeb3a9c7fff540b0fdd4047ae5d2e6f0caf5095e7d767ed96ccc7efe7") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("541c1d514cd2a3155f4a5805ccece9ac86f510b186e3cf1af7cfd8cf7097d0e9") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -4415,8 +4811,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnueabi), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - sha256: Some("d798c008dfde19f0ffdf3684f49adaf922873bee2ad1a2e5dd8d0dd2ad3a4699") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + sha256: Some("01de7a51461cdb25014041c97bebbe80e1f99b1231a525aa7a2463e55347d26f") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -4433,8 +4829,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnueabihf), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - sha256: Some("e838902cb7d47fbf8636d30b59d27182bf818dc633eef4b47c8d28163689b5ff") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + sha256: Some("0cad64bf6041f68fc960ab75e6e817cd36046331538c8e70533d709bedb6d8ef") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -4451,8 +4847,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("477dbc811b0f834bc3386126925d490ce734c2191a6e5022e1e0ebb346e5ddcf") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("69020a370009ab331c008f333ca6d8ff684b7b120300d195cc6fb0e6d6f641ac") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -4469,8 +4865,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("4e6a9f5111d08e0421446de539224addca624865d1d1e0c5c6ce7b2382d6cf11") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("eaff12c96540a87aea624f98e439ce446afe88aca024d106aa3593f007e237d1") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -4487,8 +4883,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("a5448390ef50caaeac60c42806060d0d00488df0b61e7ab4df3b020cceb2bb73") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("56bda37ad88aaf404a8a1f77c725fde18dee6a2b8d30d361cf4b30a1f807d345") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -4505,8 +4901,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Musl), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - sha256: Some("916b2d4336d9f672b5b1f0f15b0ee43504ee178244ec6656ce49de57ddefaa76") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + sha256: Some("b9950749da7e6147cf8ad0838b9c7660a8e62d248771b3bbfef4d1f33bf2e998") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -4523,8 +4919,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("267dbf5f9272bb2f8336d13b9f2468ff2b5659a5bf820afc564af49531dc676e") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("a8bd2b532d3401af71e5470eb41b85d1beb63accca9ea37c3e32cbe44d3119c6") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -4541,8 +4937,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Musl), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - sha256: Some("f9678be5d8366faefcabe11ce9db63a3172176e1f3d6f910ec007453fb2e114d") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + sha256: Some("8c3961b653ea18c76dcf5e2ee05053bfdbe6353f8ba0515316f906354a200d54") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -4559,8 +4955,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("3746c4c74ec610e4425b876a74161ebcd01aaf556d99017254d782e975d35f46") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("3cf193e4c8dd36bb9d1c6494f3e36fb6c2bf91a91ad724ee3ac38fa6fa958a65") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -4577,8 +4973,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Musl), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - sha256: Some("6662013e83d5f97c673f6b346f062d29c5a6dda36a088a0deabfd5dcf6fb9b09") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + sha256: Some("0be47a46f66cd2d00f97a0a97cc2edede9983d856f4d6822a8ad96a574a463e0") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -4595,8 +4991,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("a93ebc464644990638ae3029c9bbea6249eb90047152dfb98cbaac66da1c199e") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("9e27ab9e35c73caca1ab607364456b7e8279def51855ec73b863cafb51f2d493") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -4613,8 +5009,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Musl), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - sha256: Some("5d01a991ad42ad49569145d86752aa79d881d49dd9503c8304ac9a7cea0b83c8") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + sha256: Some("b24e33285bbe050cd122e395c96420a2067449d4d27f94928d89bb2cb6d2064b") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -4631,8 +5027,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::None, variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-i686-pc-windows-msvc-install_only_stripped.tar.gz", - sha256: Some("063b3582b31d4b5c918439701ad4037cec9b90c8e2e4c55c502a9001c638ebc6") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-i686-pc-windows-msvc-install_only_stripped.tar.gz", + sha256: Some("026d777d86ebfe687cebda919aaa6eeb2c1d0618270e19ce2996e0e74b648944") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -4649,8 +5045,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::None, variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.11.11%2B20241219-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - sha256: Some("f5bceb5efff7802e38638caf9dd748a88d7b67a4620d36f7511c2848b1eb83e3") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.11.11%2B20250106-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + sha256: Some("eab345a90cac05f6617250f1de33d6606fb29b627c01357033f2276480967730") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -7187,8 +7583,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::None, variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-aarch64-apple-darwin-install_only_stripped.tar.gz", - sha256: Some("a5a1eb1f13a688621af748d552888d7b22daaddfe6b9c7069f9c955c0fcf2da3") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-aarch64-apple-darwin-install_only_stripped.tar.gz", + sha256: Some("b5807207ff3e99436049ef8912dfc5f553bf8b1fa332cf233805784f925fea76") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -7205,8 +7601,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::None, variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-x86_64-apple-darwin-install_only_stripped.tar.gz", - sha256: Some("27bc1b56a5268f79a9eb9bf79f8281b336f8bfffc361032f41811bc3c58cdb1d") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-x86_64-apple-darwin-install_only_stripped.tar.gz", + sha256: Some("fbd4c16651165764a1b2bcc60b2092d3b9b807108cdb17ae82abeeb3ec598175") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -7223,8 +7619,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("85d1ba2ada13fdbeedbaad24e1871d49766be1f4ba6cdee3c16ca7c4197cca92") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("ef4e55c684a87ce2bd3e5b38700da512340c98307b57bd4da7cc80fb6afcec79") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -7241,8 +7637,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnueabi), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - sha256: Some("61f76e2d924b4c6cdd706d322bdda9634bec4d41e50071a03c7e1f7a5178aef2") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + sha256: Some("3ccaef3b620e988d6cc72b6fe386fa5fdd98bf26fb24b8e1b74b33d94d4c2c69") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -7259,8 +7655,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnueabihf), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - sha256: Some("68ff1232aeba789195f6061c6c0e2e6868c4a0c8547f2aa7a873c4226bf04a3a") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + sha256: Some("32fdc81d839c3dc289324a71a207f64feb1804353d68a5b7a0d7bd1dbdf205ac") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -7277,8 +7673,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("57282eba8bccf723af7edf0e018407ea5a035deed9daa5fe684c980d4dd2aebc") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("4f90917977d089b3a3a7d67130737f9ba28baea2d6508f35da92ca103f3b3551") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -7295,8 +7691,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("d2048f722562ff30f268e89310f9e567508d3dd6e071ced4c04b4a4aceb6236c") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("77c8370579ed0dc38ffc351010acfb5b3bc6663e91230c8d462d8a323c382863") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -7313,8 +7709,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("ec5ff780e53c9af0399086f17299a88e1dd3de707940eab76fa6ef15642edcbd") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("3ba89e564ace5536a62783362c3aaf8a18d882007d9f202aff26cd8d0de0b581") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -7331,8 +7727,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Musl), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - sha256: Some("9a6f91e3166e36aceb1dfc1f2246f4253b62de7ca0ee0534282e0ded229ab1f2") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + sha256: Some("f6734667688ee41ada182024f5bf369c764060a44905a17fa1df5dfa3dc76ae7") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -7349,8 +7745,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("99e51551b920a73066ca0e6b4139c1e47cecb0bd97711d8efaba819c9c3cf5ed") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("d04b15f020598b111ad1b4db757a2e26106944a6d58e1241b129636e127929e0") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -7367,8 +7763,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Musl), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - sha256: Some("bcd8a5c316e1e6727bb891d1b0ac58f3cf0b492a1ca55db4d7b54a8655a3c52c") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + sha256: Some("eea24e9df73f15183f34f69cc1a4db30013cb4c9f0d884987018a2b9d23ee984") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -7385,8 +7781,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("bb7cc15b0870979a101d887ec6ba5ed7ac38063a1a02201b709218a32f6151fa") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("c889ba100de9bfcab46950c1db000c66a8f0cc24e9de028e8223490a903b7865") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -7403,8 +7799,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Musl), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - sha256: Some("37b96735ec9776fb40a17d22761d9ed5b4d42ff891cfb3d8ef9ac08411e9051a") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + sha256: Some("f45527ffb0238d4918f9b76ec59469ae32e2820956ecb9d454d41fd843e7b37c") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -7421,8 +7817,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("344b34b0c8abd2e536c4b16ab38773e4f4b6c490000a4df162d9181fb396de7c") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("c0d770782f2176657c8fe2aaef3a58d61ce975c794510424ec8c9d9629ce0b32") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -7439,8 +7835,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Musl), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - sha256: Some("be3471934543269246ce9b5e33552d9480fffa038296a5c7875d6ca194ddda5d") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + sha256: Some("625fa12389d3bc1e08e477e95547a81bc1bc5aa3152043b53e266a6d865dac4c") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -7457,8 +7853,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::None, variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-i686-pc-windows-msvc-install_only_stripped.tar.gz", - sha256: Some("56462270fd467a3cb2a353a6b6b7c8cd7b2734513dfa87638313db6e769407af") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-i686-pc-windows-msvc-install_only_stripped.tar.gz", + sha256: Some("4af78f6d3a9e423e5dd47be8ca84027a9fd0d0aee48f2e1cbccdb24e7980deb7") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -7475,8 +7871,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::None, variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.10.16%2B20241219-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - sha256: Some("6121810d6899d1bfd89a1c3b7fba3ece4402a7f49772a3ebef6b4c707bcf22ec") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.10.16%2B20250106-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + sha256: Some("df76a5d696485937e51cf9d2ebf8a2d559d4cb064d3e721714f6470eb35d90cc") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -11111,8 +11507,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::None, variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-aarch64-apple-darwin-install_only_stripped.tar.gz", - sha256: Some("f0628c6dee878610c1e3da924c8d930c6948e85c56e75b95b3d437e902acf782") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-aarch64-apple-darwin-install_only_stripped.tar.gz", + sha256: Some("65de6878752c9603772e0e4543dfa66e287d770c013eed14a6530d30f901585b") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -11129,8 +11525,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::None, variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-x86_64-apple-darwin-install_only_stripped.tar.gz", - sha256: Some("ed5c87541ea92d5dd66a6187dbec4d43d395d6908f7f42e650e12e62bf6fe145") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-x86_64-apple-darwin-install_only_stripped.tar.gz", + sha256: Some("d0936e25d48a471ac7bcbb64e8ed02bc493a7e5d46ed879a580c006db5bb75ef") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -11147,8 +11543,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("72c1ce8791b78719080fc00575bc96f21de1024955a56d00b151eeb774804bc9") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("59044e65adff98e98ea61fc68a71299722fccf9c162f1908c8d53b94e994039c") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -11165,8 +11561,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnueabi), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - sha256: Some("8dcfda0951f25bc5be61ef244f8892a2015ba4512bcabcf37005480683f6e72b") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + sha256: Some("ecdf124007832d1cbb5092c4b5232f9c8ea88c7cd1abd47ea9703028f785a88f") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -11183,8 +11579,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnueabihf), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - sha256: Some("f6bac804b844fc2ad75e98bfa4ba5fb2df17dd484a6f8b7b9fda7e55e4de283b") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + sha256: Some("874aa9b7b4a45512f0b8e90020cc3b38d7e1e5c21fead24e67a57ebcfb5d1707") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -11201,8 +11597,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("eff34eb5c7314c98382a8028647c3e0c788c48aab12879282feae2e7e176af6c") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("5fa9f333333cc09e5602fd89d3ce6b9ad9b4e2ffd52d3dc29719a7194c2a7491") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -11219,8 +11615,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("a1c252593e364e7fdb99f48b233311c54001a6e993057948a232ead94b10329e") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("cbd9a8e187d18fe370e5f71f4ef77c4e28813de8bef8018e35d95cb2c2f545a6") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -11237,8 +11633,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("e0ce3038ef0e779791f62ac64ed08bfaf4e09bc48ff15a82038cef2009124cbf") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("8df8d0484cda57420c2cb9ab1c805b06b3e0b01fd519f08bda0617282d2b68b2") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -11255,8 +11651,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Musl), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - sha256: Some("57ba961d7cd48a4473000a0fb10581094cd5fd4cd33558b3ddc8ae6856097e80") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + sha256: Some("7b1774e3db1e8e8d5f4eaafe2ba3f7427a7caa6955a6b74e05eb9673bd08ba4f") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -11273,8 +11669,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("a96090f2eb47952b3abb6815864d7c4477bbf23d5d2559c4514140c674c8835b") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("800218e9d232e2e7d734922ddda6770911895fd9bc1e08e9c6cb96fa01c46257") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -11291,8 +11687,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Musl), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - sha256: Some("fb308474d3d148fbe4223704d47f4397a85317d1000dd9978f2cd97c7992a931") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + sha256: Some("6c7b7304ea033e38e1b47d16ca784bee00f800e4c9977bcac41fbd106638652f") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -11309,8 +11705,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("b9d39c72d6f503e7fb759c32310fbf1cb402189c2bb26f1d25608b8c1d8a41b6") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("c5c2ddfb7d259c607e180a97ad1a5c2bca62a4b1bd43b51b0ef7a3fb029b5387") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -11327,8 +11723,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Musl), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - sha256: Some("6dd47b0f86888312cee676a4ac63ddd1e217009317e8f0c6dcb82d681a56277b") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + sha256: Some("08099c07425761f9cd100a7d0752bf66e228075941560e9afebe65a44ad33534") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -11345,8 +11741,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Gnu), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("4f6ca60512658efeb4b73afd8b86883948370ed02081af2f722b2c63e08fc1ec") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("b4bafc45b439532bd6c9fe32410b91ed17152f38711b4df428746fd5314f1fb7") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -11363,8 +11759,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::Some(target_lexicon::Environment::Musl), variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - sha256: Some("3d39de9f4c52465a546e2bed637e20b1811ba8d85b1400dad28dbb2db4af3c2f") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + sha256: Some("cca7dba1d42006b97e87b0da20bf2a7ad82909b7cb7a033649a2ea3363cb1fc4") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -11381,8 +11777,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::None, variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-i686-pc-windows-msvc-install_only_stripped.tar.gz", - sha256: Some("b0c1a25cd1c07bdf7d3d861d39527eaff66363a28eb3306463d8ffc134d55743") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-i686-pc-windows-msvc-install_only_stripped.tar.gz", + sha256: Some("9c950f9f6185b488c0d57a2d5d00bcdf6dbeefcb72c62b4ff83634ef280a08f7") }, ManagedPythonDownload { key: PythonInstallationKey { @@ -11399,8 +11795,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ libc: Libc::None, variant: PythonVariant::Default }, - url: "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.9.21%2B20241219-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - sha256: Some("acd70e2f5596d8e24a06d47394b15d326fc12c26662a8cc018e3635fb3025897") + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250106/cpython-3.9.21%2B20250106-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + sha256: Some("8522829a1617039b56b5f2744c972203fdb8897e9092b26cc59cdb1e996119c4") }, ManagedPythonDownload { key: PythonInstallationKey { diff --git a/crates/uv-python/src/installation.rs b/crates/uv-python/src/installation.rs index 2733a14a0885..e7fc3994056c 100644 --- a/crates/uv-python/src/installation.rs +++ b/crates/uv-python/src/installation.rs @@ -78,12 +78,12 @@ impl PythonInstallation { /// Find or fetch a [`PythonInstallation`]. /// /// Unlike [`PythonInstallation::find`], if the required Python is not installed it will be installed automatically. - pub async fn find_or_download<'a>( + pub async fn find_or_download( request: Option<&PythonRequest>, environments: EnvironmentPreference, preference: PythonPreference, python_downloads: PythonDownloads, - client_builder: &BaseClientBuilder<'a>, + client_builder: &BaseClientBuilder<'_>, cache: &Cache, reporter: Option<&dyn Reporter>, python_install_mirror: Option<&str>, @@ -127,9 +127,9 @@ impl PythonInstallation { } /// Download and install the requested installation. - pub async fn fetch<'a>( + pub async fn fetch( request: PythonDownloadRequest, - client_builder: &BaseClientBuilder<'a>, + client_builder: &BaseClientBuilder<'_>, cache: &Cache, reporter: Option<&dyn Reporter>, python_install_mirror: Option<&str>, diff --git a/crates/uv-python/src/interpreter.rs b/crates/uv-python/src/interpreter.rs index e725fbccd0f0..13a12be575f0 100644 --- a/crates/uv-python/src/interpreter.rs +++ b/crates/uv-python/src/interpreter.rs @@ -894,10 +894,10 @@ mod tests { fs::write( &mocked_interpreter, - formatdoc! {r##" + formatdoc! {r" #!/bin/bash echo '{json}' - "##}, + "}, ) .unwrap(); @@ -913,10 +913,10 @@ mod tests { ); fs::write( &mocked_interpreter, - formatdoc! {r##" + formatdoc! {r" #!/bin/bash echo '{}' - "##, json.replace("3.12", "3.13")}, + ", json.replace("3.12", "3.13")}, ) .unwrap(); let interpreter = Interpreter::query(&mocked_interpreter, &cache).unwrap(); diff --git a/crates/uv-python/src/lib.rs b/crates/uv-python/src/lib.rs index 982878ff3d56..ea5b3e57275a 100644 --- a/crates/uv-python/src/lib.rs +++ b/crates/uv-python/src/lib.rs @@ -282,10 +282,10 @@ mod tests { fs_err::create_dir_all(path.parent().unwrap())?; fs_err::write( path, - formatdoc! {r##" + formatdoc! {r" #!/bin/bash echo '{json}' - "##}, + "}, )?; fs_err::set_permissions(path, std::os::unix::fs::PermissionsExt::from_mode(0o770))?; @@ -304,10 +304,10 @@ mod tests { fs_err::write( path, - formatdoc! {r##" + formatdoc! {r" #!/bin/bash echo '{output}' 1>&2 - "##}, + "}, )?; fs_err::set_permissions(path, std::os::unix::fs::PermissionsExt::from_mode(0o770))?; @@ -525,10 +525,10 @@ mod tests { #[cfg(unix)] fs_err::write( children[0].join(format!("python{}", env::consts::EXE_SUFFIX)), - formatdoc! {r##" + formatdoc! {r" #!/bin/bash echo 'foo' - "##}, + "}, )?; fs_err::set_permissions( children[0].join(format!("python{}", env::consts::EXE_SUFFIX)), diff --git a/crates/uv-python/src/platform.rs b/crates/uv-python/src/platform.rs index 9f933633d0c5..d34634754378 100644 --- a/crates/uv-python/src/platform.rs +++ b/crates/uv-python/src/platform.rs @@ -54,7 +54,7 @@ impl Libc { // Checks if the CPU supports hardware floating-point operations. // Depending on the result, it selects either the `gnueabihf` (hard-float) or `gnueabi` (soft-float) environment. // download-metadata.json only includes armv7. - "arm" | "armv7" => match detect_hardware_floating_point_support() { + "arm" | "armv5te" | "armv7" => match detect_hardware_floating_point_support() { Ok(true) => target_lexicon::Environment::Gnueabihf, Ok(false) => target_lexicon::Environment::Gnueabi, Err(_) => target_lexicon::Environment::Gnu, @@ -240,6 +240,10 @@ impl From<&uv_platform_tags::Arch> for Arch { ), variant: None, }, + uv_platform_tags::Arch::Armv5TEL => Self { + family: target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv5te), + variant: None, + }, uv_platform_tags::Arch::Armv6L => Self { family: target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv6), variant: None, @@ -270,6 +274,10 @@ impl From<&uv_platform_tags::Arch> for Arch { family: target_lexicon::Architecture::X86_64, variant: None, }, + uv_platform_tags::Arch::LoongArch64 => Self { + family: target_lexicon::Architecture::LoongArch64, + variant: None, + }, uv_platform_tags::Arch::Riscv64 => Self { family: target_lexicon::Architecture::Riscv64( target_lexicon::Riscv64Architecture::Riscv64, diff --git a/crates/uv-python/template-download-metadata.py b/crates/uv-python/template-download-metadata.py index 95313a699777..86bb14966df5 100755 --- a/crates/uv-python/template-download-metadata.py +++ b/crates/uv-python/template-download-metadata.py @@ -69,6 +69,8 @@ def prepare_arch(arch: dict) -> tuple[str, str]: family = "X86_32(target_lexicon::X86_32Architecture::I686)" case "aarch64": family = "Aarch64(target_lexicon::Aarch64Architecture::Aarch64)" + case "armv5tel": + family = "Arm(target_lexicon::ArmArchitecture::Armv5te)" case "armv7": family = "Arm(target_lexicon::ArmArchitecture::Armv7)" case value: @@ -96,7 +98,8 @@ def prepare_prerelease(prerelease: str) -> str: if not (match := PRERELEASE_PATTERN.match(prerelease)): raise ValueError(f"Invalid prerelease: {prerelease!r}") kind, number = match.groups() - return f"Some(Prerelease {{ kind: PrereleaseKind::{kind.capitalize()}, number: {number} }})" + kind_mapping = {"a": "Alpha", "b": "Beta", "rc": "Rc"} + return f"Some(Prerelease {{ kind: PrereleaseKind::{kind_mapping[kind]}, number: {number} }})" def prepare_value(value: dict) -> dict: diff --git a/crates/uv-requirements-txt/Cargo.toml b/crates/uv-requirements-txt/Cargo.toml index dae6b3870482..7dcc2964ecd2 100644 --- a/crates/uv-requirements-txt/Cargo.toml +++ b/crates/uv-requirements-txt/Cargo.toml @@ -16,13 +16,14 @@ doctest = false workspace = true [dependencies] -uv-distribution-types = { workspace = true } -uv-pep508 = { workspace = true } -uv-pypi-types = { workspace = true } uv-client = { workspace = true } +uv-configuration = { workspace = true } +uv-distribution-types = { workspace = true } uv-fs = { workspace = true } uv-normalize = { workspace = true } -uv-configuration = { workspace = true } +uv-pep508 = { workspace = true } +uv-pypi-types = { workspace = true } +uv-warnings = { workspace = true } fs-err = { workspace = true } regex = { workspace = true } @@ -41,7 +42,7 @@ anyhow = { version = "1.0.89" } assert_fs = { version = "1.1.2" } indoc = { workspace = true } insta = { version = "1.40.0", features = ["filters"] } -itertools = { version = "0.13.0" } +itertools = { version = "0.14.0" } tempfile = { workspace = true } test-case = { version = "3.3.1" } tokio = { version = "1.40.0" } diff --git a/crates/uv-requirements-txt/src/lib.rs b/crates/uv-requirements-txt/src/lib.rs index e08c09cf28cd..c4893f22bd6e 100644 --- a/crates/uv-requirements-txt/src/lib.rs +++ b/crates/uv-requirements-txt/src/lib.rs @@ -88,6 +88,8 @@ enum RequirementsTxtStatement { NoBinary(NoBinary), /// `--only-binary` OnlyBinary(NoBuild), + /// An unsupported option (e.g., `--trusted-host`). + UnsupportedOption(UnsupportedOption), } /// A [Requirement] with additional metadata from the `requirements.txt`, currently only hashes but in @@ -384,6 +386,28 @@ impl RequirementsTxt { RequirementsTxtStatement::OnlyBinary(only_binary) => { data.only_binary.extend(only_binary); } + RequirementsTxtStatement::UnsupportedOption(flag) => { + if requirements_txt == Path::new("-") { + if flag.cli() { + uv_warnings::warn_user!("Ignoring unsupported option from stdin: `{flag}` (hint: pass `{flag}` on the command line instead)", flag = flag.green()); + } else { + uv_warnings::warn_user!( + "Ignoring unsupported option from stdin: `{flag}`", + flag = flag.green() + ); + } + } else { + if flag.cli() { + uv_warnings::warn_user!("Ignoring unsupported option in `{path}`: `{flag}` (hint: pass `{flag}` on the command line instead)", path = requirements_txt.user_display().cyan(), flag = flag.green()); + } else { + uv_warnings::warn_user!( + "Ignoring unsupported option in `{path}`: `{flag}`", + path = requirements_txt.user_display().cyan(), + flag = flag.green() + ); + } + } + } } } Ok(data) @@ -416,10 +440,70 @@ impl RequirementsTxt { } } -/// Parse a single entry, that is a requirement, an inclusion or a comment line +/// An unsupported option (e.g., `--trusted-host`). /// -/// Consumes all preceding trivia (whitespace and comments). If it returns None, we've reached -/// the end of file +/// See: +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum UnsupportedOption { + PreferBinary, + RequireHashes, + Pre, + TrustedHost, + UseFeature, +} + +impl UnsupportedOption { + /// The name of the unsupported option. + fn name(self) -> &'static str { + match self { + UnsupportedOption::PreferBinary => "--prefer-binary", + UnsupportedOption::RequireHashes => "--require-hashes", + UnsupportedOption::Pre => "--pre", + UnsupportedOption::TrustedHost => "--trusted-host", + UnsupportedOption::UseFeature => "--use-feature", + } + } + + /// Returns `true` if the option is supported on the CLI. + fn cli(self) -> bool { + match self { + UnsupportedOption::PreferBinary => false, + UnsupportedOption::RequireHashes => true, + UnsupportedOption::Pre => true, + UnsupportedOption::TrustedHost => true, + UnsupportedOption::UseFeature => false, + } + } + + /// Returns an iterator over all unsupported options. + fn iter() -> impl Iterator { + [ + UnsupportedOption::PreferBinary, + UnsupportedOption::RequireHashes, + UnsupportedOption::Pre, + UnsupportedOption::TrustedHost, + UnsupportedOption::UseFeature, + ] + .iter() + .copied() + } +} + +impl Display for UnsupportedOption { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.name()) + } +} + +/// Returns `true` if the character is a newline or a comment character. +const fn is_terminal(c: char) -> bool { + matches!(c, '\n' | '\r' | '#') +} + +/// Parse a single entry, that is a requirement, an inclusion or a comment line. +/// +/// Consumes all preceding trivia (whitespace and comments). If it returns `None`, we've reached +/// the end of file. fn parse_entry( s: &mut Scanner, content: &str, @@ -436,7 +520,7 @@ fn parse_entry( let start = s.cursor(); Ok(Some(if s.eat_if("-r") || s.eat_if("--requirement") { - let requirements_file = parse_value(content, s, |c: char| !['\n', '\r', '#'].contains(&c))?; + let requirements_file = parse_value(content, s, |c: char| !is_terminal(c))?; let end = s.cursor(); RequirementsTxtStatement::Requirements { filename: requirements_file.to_string(), @@ -444,7 +528,7 @@ fn parse_entry( end, } } else if s.eat_if("-c") || s.eat_if("--constraint") { - let constraints_file = parse_value(content, s, |c: char| !['\n', '\r', '#'].contains(&c))?; + let constraints_file = parse_value(content, s, |c: char| !is_terminal(c))?; let end = s.cursor(); RequirementsTxtStatement::Constraint { filename: constraints_file.to_string(), @@ -475,7 +559,7 @@ fn parse_entry( hashes, }) } else if s.eat_if("-i") || s.eat_if("--index-url") { - let given = parse_value(content, s, |c: char| !['\n', '\r', '#'].contains(&c))?; + let given = parse_value(content, s, |c: char| !is_terminal(c))?; let expanded = expand_env_vars(given); let url = if let Some(path) = std::path::absolute(expanded.as_ref()) .ok() @@ -501,7 +585,7 @@ fn parse_entry( }; RequirementsTxtStatement::IndexUrl(url.with_given(given)) } else if s.eat_if("--extra-index-url") { - let given = parse_value(content, s, |c: char| !['\n', '\r', '#'].contains(&c))?; + let given = parse_value(content, s, |c: char| !is_terminal(c))?; let expanded = expand_env_vars(given); let url = if let Some(path) = std::path::absolute(expanded.as_ref()) .ok() @@ -529,7 +613,7 @@ fn parse_entry( } else if s.eat_if("--no-index") { RequirementsTxtStatement::NoIndex } else if s.eat_if("--find-links") || s.eat_if("-f") { - let given = parse_value(content, s, |c: char| !['\n', '\r', '#'].contains(&c))?; + let given = parse_value(content, s, |c: char| !is_terminal(c))?; let expanded = expand_env_vars(given); let url = if let Some(path) = std::path::absolute(expanded.as_ref()) .ok() @@ -555,7 +639,7 @@ fn parse_entry( }; RequirementsTxtStatement::FindLinks(url.with_given(given)) } else if s.eat_if("--no-binary") { - let given = parse_value(content, s, |c: char| !['\n', '\r', '#'].contains(&c))?; + let given = parse_value(content, s, |c: char| !is_terminal(c))?; let specifier = PackageNameSpecifier::from_str(given).map_err(|err| { RequirementsTxtParserError::NoBinary { source: err, @@ -566,7 +650,7 @@ fn parse_entry( })?; RequirementsTxtStatement::NoBinary(NoBinary::from_pip_arg(specifier)) } else if s.eat_if("--only-binary") { - let given = parse_value(content, s, |c: char| !['\n', '\r', '#'].contains(&c))?; + let given = parse_value(content, s, |c: char| !is_terminal(c))?; let specifier = PackageNameSpecifier::from_str(given).map_err(|err| { RequirementsTxtParserError::NoBinary { source: err, @@ -590,14 +674,20 @@ fn parse_entry( hashes, }) } else if let Some(char) = s.peek() { - let (line, column) = calculate_row_column(content, s.cursor()); - return Err(RequirementsTxtParserError::Parser { - message: format!( - "Unexpected '{char}', expected '-c', '-e', '-r' or the start of a requirement" - ), - line, - column, - }); + // Identify an unsupported option, like `--trusted-host`. + if let Some(option) = UnsupportedOption::iter().find(|option| s.eat_if(option.name())) { + s.eat_while(|c: char| !is_terminal(c)); + RequirementsTxtStatement::UnsupportedOption(option) + } else { + let (line, column) = calculate_row_column(content, s.cursor()); + return Err(RequirementsTxtParserError::Parser { + message: format!( + "Unexpected '{char}', expected '-c', '-e', '-r' or the start of a requirement" + ), + line, + column, + }); + } } else { // EOF return Ok(None); diff --git a/crates/uv-requirements/src/extras.rs b/crates/uv-requirements/src/extras.rs index 2d72f6b1f214..d8c2e6959271 100644 --- a/crates/uv-requirements/src/extras.rs +++ b/crates/uv-requirements/src/extras.rs @@ -37,7 +37,7 @@ impl<'a, Context: BuildContext> ExtrasResolver<'a, Context> { /// Set the [`Reporter`] to use for this resolver. #[must_use] - pub fn with_reporter(self, reporter: impl Reporter + 'static) -> Self { + pub fn with_reporter(self, reporter: Arc) -> Self { Self { database: self.database.with_reporter(reporter), ..self diff --git a/crates/uv-requirements/src/lib.rs b/crates/uv-requirements/src/lib.rs index adb342e133c6..a6bff1998a61 100644 --- a/crates/uv-requirements/src/lib.rs +++ b/crates/uv-requirements/src/lib.rs @@ -35,11 +35,7 @@ pub enum Error { impl Error { /// Create an [`Error`] from a distribution error. pub(crate) fn from_dist(dist: Dist, err: uv_distribution::Error) -> Self { - Self::Dist( - DistErrorKind::from_dist_and_err(&dist, &err), - Box::new(dist), - err, - ) + Self::Dist(DistErrorKind::from_dist(&dist, &err), Box::new(dist), err) } } diff --git a/crates/uv-requirements/src/lookahead.rs b/crates/uv-requirements/src/lookahead.rs index 77c18ea81ab1..3f191ed172e7 100644 --- a/crates/uv-requirements/src/lookahead.rs +++ b/crates/uv-requirements/src/lookahead.rs @@ -67,7 +67,7 @@ impl<'a, Context: BuildContext> LookaheadResolver<'a, Context> { /// Set the [`Reporter`] to use for this resolver. #[must_use] - pub fn with_reporter(self, reporter: impl Reporter + 'static) -> Self { + pub fn with_reporter(self, reporter: Arc) -> Self { Self { database: self.database.with_reporter(reporter), ..self diff --git a/crates/uv-requirements/src/source_tree.rs b/crates/uv-requirements/src/source_tree.rs index 569c658b6200..399120fd6dcb 100644 --- a/crates/uv-requirements/src/source_tree.rs +++ b/crates/uv-requirements/src/source_tree.rs @@ -13,7 +13,7 @@ use url::Url; use uv_configuration::ExtrasSpecification; use uv_distribution::{DistributionDatabase, Reporter, RequiresDist}; use uv_distribution_types::{ - BuildableSource, DirectorySourceUrl, HashPolicy, SourceUrl, VersionId, + BuildableSource, DirectorySourceUrl, HashGeneration, HashPolicy, SourceUrl, VersionId, }; use uv_fs::Simplified; use uv_normalize::{ExtraName, PackageName}; @@ -65,7 +65,7 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> { /// Set the [`Reporter`] to use for this resolver. #[must_use] - pub fn with_reporter(self, reporter: impl Reporter + 'static) -> Self { + pub fn with_reporter(self, reporter: Arc) -> Self { Self { database: self.database.with_reporter(reporter), ..self @@ -213,8 +213,8 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> { // manual match. let hashes = match self.hasher { HashStrategy::None => HashPolicy::None, - HashStrategy::Generate => HashPolicy::Generate, - HashStrategy::Verify(_) => HashPolicy::Generate, + HashStrategy::Generate(mode) => HashPolicy::Generate(*mode), + HashStrategy::Verify(_) => HashPolicy::Generate(HashGeneration::All), HashStrategy::Require(_) => { return Err(anyhow::anyhow!( "Hash-checking is not supported for local directories: {}", diff --git a/crates/uv-requirements/src/specification.rs b/crates/uv-requirements/src/specification.rs index 61c5513e1079..2c8ec42a8651 100644 --- a/crates/uv-requirements/src/specification.rs +++ b/crates/uv-requirements/src/specification.rs @@ -50,7 +50,7 @@ use uv_workspace::pyproject::PyProjectToml; use crate::RequirementsSource; -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct RequirementsSpecification { /// The name of the project specifying requirements. pub project: Option, diff --git a/crates/uv-requirements/src/unnamed.rs b/crates/uv-requirements/src/unnamed.rs index 252cc368e1d5..9356e42dea7c 100644 --- a/crates/uv-requirements/src/unnamed.rs +++ b/crates/uv-requirements/src/unnamed.rs @@ -49,7 +49,7 @@ impl<'a, Context: BuildContext> NamedRequirementsResolver<'a, Context> { /// Set the [`Reporter`] to use for this resolver. #[must_use] - pub fn with_reporter(self, reporter: impl Reporter + 'static) -> Self { + pub fn with_reporter(self, reporter: Arc) -> Self { Self { database: self.database.with_reporter(reporter), ..self diff --git a/crates/uv-requirements/src/upgrade.rs b/crates/uv-requirements/src/upgrade.rs index 8a472b373b94..ed6d9a747461 100644 --- a/crates/uv-requirements/src/upgrade.rs +++ b/crates/uv-requirements/src/upgrade.rs @@ -68,6 +68,11 @@ pub fn read_lock_requirements( install_path: &Path, upgrade: &Upgrade, ) -> Result { + // As an optimization, skip iterating over the lockfile is we're upgrading all packages anyway. + if upgrade.is_all() { + return Ok(LockedRequirements::default()); + } + let mut preferences = Vec::new(); let mut git = Vec::new(); @@ -81,7 +86,7 @@ pub fn read_lock_requirements( preferences.push(Preference::from_lock(package, install_path)?); // Map each entry in the lockfile to a Git SHA. - if let Some(git_ref) = package.as_git_ref() { + if let Some(git_ref) = package.as_git_ref()? { git.push(git_ref); } } diff --git a/crates/uv-resolver/Cargo.toml b/crates/uv-resolver/Cargo.toml index 6c9381e67849..4f0a4687a7dd 100644 --- a/crates/uv-resolver/Cargo.toml +++ b/crates/uv-resolver/Cargo.toml @@ -38,7 +38,7 @@ uv-types = { workspace = true } uv-warnings = { workspace = true } uv-workspace = { workspace = true } -anyhow = { workspace = true } +arcstr = { workspace = true } clap = { workspace = true, features = ["derive"], optional = true } dashmap = { workspace = true } either = { workspace = true } @@ -67,3 +67,6 @@ url = { workspace = true } [dev-dependencies] insta = { version = "1.40.0" } toml = { workspace = true } + +[features] +tracing-durations-export = [] diff --git a/crates/uv-resolver/src/candidate_selector.rs b/crates/uv-resolver/src/candidate_selector.rs index 7f2db96a6c6f..3104b38a73f9 100644 --- a/crates/uv-resolver/src/candidate_selector.rs +++ b/crates/uv-resolver/src/candidate_selector.rs @@ -1,6 +1,7 @@ +use std::fmt::{Display, Formatter}; + use itertools::Itertools; use pubgrub::Range; -use std::fmt::{Display, Formatter}; use tracing::{debug, trace}; use uv_configuration::IndexStrategy; @@ -83,17 +84,25 @@ impl CandidateSelector { index: Option<&'a IndexUrl>, env: &ResolverEnvironment, ) -> Option> { - let is_excluded = exclusions.contains(package_name); - - // Check for a preference from a lockfile or a previous fork that satisfies the range and - // is allowed. + let reinstall = exclusions.reinstall(package_name); + let upgrade = exclusions.upgrade(package_name); + + // If we have a preference (e.g., from a lockfile), search for a version matching that + // preference. + // + // If `--reinstall` is provided, we should omit any already-installed packages from here, + // since we can't reinstall already-installed packages. + // + // If `--upgrade` is provided, we should still search for a matching preference. In + // practice, preferences should be empty if `--upgrade` is provided, but it's the caller's + // responsibility to ensure that. if let Some(preferred) = self.get_preferred( package_name, range, version_maps, preferences, installed_packages, - is_excluded, + reinstall, index, env, ) { @@ -101,19 +110,52 @@ impl CandidateSelector { return Some(preferred); } - // Check for a locally installed distribution that satisfies the range and is allowed. - if !is_excluded { - if let Some(installed) = Self::get_installed(package_name, range, installed_packages) { + // If we don't have a preference, find an already-installed distribution that satisfies the + // range. + let installed = if reinstall { + None + } else { + Self::get_installed(package_name, range, installed_packages) + }; + + // If we're not upgrading, we should prefer the already-installed distribution. + if !upgrade { + if let Some(installed) = installed { + trace!( + "Using installed {} {} that satisfies {range}", + installed.name, + installed.version + ); + return Some(installed); + } + } + + // Otherwise, find the best candidate from the version maps. + let compatible = self.select_no_preference(package_name, range, version_maps, env); + + // Cross-reference against the already-installed distribution. + // + // If the already-installed version is _more_ compatible than the best candidate + // from the version maps, use the installed version. + if let Some(installed) = installed { + if compatible.as_ref().is_none_or(|compatible| { + let highest = self.use_highest_version(package_name, env); + if highest { + installed.version() >= compatible.version() + } else { + installed.version() <= compatible.version() + } + }) { trace!( - "Using preference {} {} from installed package", + "Using installed {} {} that satisfies {range}", installed.name, - installed.version, + installed.version ); return Some(installed); } } - self.select_no_preference(package_name, range, version_maps, env) + compatible } /// If the package has a preference, an existing version from an existing lockfile or a version @@ -132,7 +174,7 @@ impl CandidateSelector { version_maps: &'a [VersionMap], preferences: &'a Preferences, installed_packages: &'a InstalledPackages, - is_excluded: bool, + reinstall: bool, index: Option<&'a IndexUrl>, env: &ResolverEnvironment, ) -> Option> { @@ -159,7 +201,7 @@ impl CandidateSelector { range, version_maps, installed_packages, - is_excluded, + reinstall, env, ) } @@ -172,7 +214,7 @@ impl CandidateSelector { range: &Range, version_maps: &'a [VersionMap], installed_packages: &'a InstalledPackages, - is_excluded: bool, + reinstall: bool, env: &ResolverEnvironment, ) -> Option> { for (marker, version) in preferences { @@ -181,8 +223,9 @@ impl CandidateSelector { continue; } - // Check for a locally installed distribution that matches the preferred version. - if !is_excluded { + // Check for a locally installed distribution that matches the preferred version, unless + // we have to reinstall, in which case we can't reuse an already-installed distribution. + if !reinstall { let installed_dists = installed_packages.get_packages(package_name); match installed_dists.as_slice() { [] => {} diff --git a/crates/uv-resolver/src/error.rs b/crates/uv-resolver/src/error.rs index 70ef20f60e0d..be35a4fc9ef5 100644 --- a/crates/uv-resolver/src/error.rs +++ b/crates/uv-resolver/src/error.rs @@ -10,8 +10,7 @@ use rustc_hash::FxHashMap; use tracing::trace; use uv_distribution_types::{ - DerivationChain, Dist, DistErrorKind, IndexCapabilities, IndexLocations, IndexUrl, - InstalledDist, InstalledDistError, + DerivationChain, DistErrorKind, IndexCapabilities, IndexLocations, IndexUrl, RequestedDist, }; use uv_normalize::{ExtraName, PackageName}; use uv_pep440::{LocalVersionSlice, Version}; @@ -23,6 +22,7 @@ use crate::fork_urls::ForkUrls; use crate::prerelease::AllowPrerelease; use crate::pubgrub::{PubGrubPackage, PubGrubPackageInner, PubGrubReportFormatter}; use crate::python_requirement::PythonRequirement; +use crate::requires_python::LowerBound; use crate::resolution::ConflictingDistributionError; use crate::resolver::{ MetadataUnavailable, ResolverEnvironment, UnavailablePackage, UnavailableReason, @@ -98,14 +98,11 @@ pub enum ResolveError { #[error("{0} `{1}`")] Dist( DistErrorKind, - Box, + Box, DerivationChain, #[source] Arc, ), - #[error("Failed to read metadata from installed package `{0}`")] - ReadInstalled(Box, #[source] InstalledDistError), - #[error(transparent)] NoSolution(#[from] NoSolutionError), @@ -238,7 +235,7 @@ impl NoSolutionError { match derivation_tree { DerivationTree::External(External::NotRoot(_, _)) => Some(derivation_tree), DerivationTree::External(External::NoVersions(package, versions)) => { - if SentinelRange::from(&versions).is_sentinel() { + if SentinelRange::from(&versions).is_complement() { return None; } @@ -298,6 +295,33 @@ impl NoSolutionError { strip(derivation_tree).expect("derivation tree should contain at least one term") } + /// Given a [`DerivationTree`], identify the largest required Python version that is missing. + pub fn find_requires_python(&self) -> LowerBound { + fn find(derivation_tree: &ErrorTree, minimum: &mut LowerBound) { + match derivation_tree { + DerivationTree::Derived(derived) => { + find(derived.cause1.as_ref(), minimum); + find(derived.cause2.as_ref(), minimum); + } + DerivationTree::External(External::FromDependencyOf(.., package, version)) => { + if let PubGrubPackageInner::Python(_) = &**package { + if let Some((lower, ..)) = version.bounding_range() { + let lower = LowerBound::new(lower.cloned()); + if lower > *minimum { + *minimum = lower; + } + } + } + } + DerivationTree::External(_) => {} + } + } + + let mut minimum = LowerBound::default(); + find(&self.error, &mut minimum); + minimum + } + /// Initialize a [`NoSolutionHeader`] for this error. pub fn header(&self) -> NoSolutionHeader { NoSolutionHeader::new(self.env.clone()) @@ -953,13 +977,30 @@ impl<'range> From<&'range Range> for SentinelRange<'range> { } impl SentinelRange<'_> { - /// Returns `true` if the range appears to be, e.g., `>1.0.0, <1.0.0+[max]`. + /// Returns `true` if the range appears to be, e.g., `>=1.0.0, <1.0.0+[max]`. pub fn is_sentinel(&self) -> bool { + self.0.iter().all(|(lower, upper)| { + let (Bound::Included(lower), Bound::Excluded(upper)) = (lower, upper) else { + return false; + }; + if !lower.local().is_empty() { + return false; + } + if upper.local() != LocalVersionSlice::Max { + return false; + } + *lower == upper.clone().without_local() + }) + } + + /// Returns `true` if the range appears to be, e.g., `>1.0.0, <1.0.0+[max]` (i.e., a sentinel + /// range with the non-local version removed). + pub fn is_complement(&self) -> bool { self.0.iter().all(|(lower, upper)| { let (Bound::Excluded(lower), Bound::Excluded(upper)) = (lower, upper) else { return false; }; - if lower.local() == LocalVersionSlice::Max { + if !lower.local().is_empty() { return false; } if upper.local() != LocalVersionSlice::Max { diff --git a/crates/uv-resolver/src/exclusions.rs b/crates/uv-resolver/src/exclusions.rs index 2222584c9216..24cb7da4298a 100644 --- a/crates/uv-resolver/src/exclusions.rs +++ b/crates/uv-resolver/src/exclusions.rs @@ -1,48 +1,27 @@ -use rustc_hash::FxHashSet; use uv_configuration::{Reinstall, Upgrade}; use uv_pep508::PackageName; /// Tracks locally installed packages that should not be selected during resolution. #[derive(Debug, Default, Clone)] -pub enum Exclusions { - #[default] - None, - /// Exclude some local packages from consideration, e.g. from `--reinstall-package foo --upgrade-package bar` - Some(FxHashSet), - /// Exclude all local packages from consideration, e.g. from `--reinstall` or `--upgrade` - All, +pub struct Exclusions { + reinstall: Reinstall, + upgrade: Upgrade, } impl Exclusions { pub fn new(reinstall: Reinstall, upgrade: Upgrade) -> Self { - if upgrade.is_all() || reinstall.is_all() { - Self::All - } else { - let mut exclusions: FxHashSet = - if let Reinstall::Packages(packages) = reinstall { - FxHashSet::from_iter(packages) - } else { - FxHashSet::default() - }; + Self { reinstall, upgrade } + } - if let Upgrade::Packages(packages) = upgrade { - exclusions.extend(packages.into_keys()); - }; + pub fn reinstall(&self, package: &PackageName) -> bool { + self.reinstall.contains(package) + } - if exclusions.is_empty() { - Self::None - } else { - Self::Some(exclusions) - } - } + pub fn upgrade(&self, package: &PackageName) -> bool { + self.upgrade.contains(package) } - /// Returns true if the package is excluded and a local distribution should not be used. pub fn contains(&self, package: &PackageName) -> bool { - match self { - Self::None => false, - Self::Some(packages) => packages.contains(package), - Self::All => true, - } + self.reinstall(package) || self.upgrade(package) } } diff --git a/crates/uv-resolver/src/lock/mod.rs b/crates/uv-resolver/src/lock/mod.rs index 2e523458e75e..283889b91eca 100644 --- a/crates/uv-resolver/src/lock/mod.rs +++ b/crates/uv-resolver/src/lock/mod.rs @@ -953,7 +953,7 @@ impl Lock { .ok() .flatten() .map(|package| matches!(package.id.source, Source::Virtual(_))); - if actual.map_or(true, |actual| actual != expected) { + if actual != Some(expected) { return Ok(SatisfiesResult::MismatchedSources(name.clone(), expected)); } } @@ -973,7 +973,7 @@ impl Lock { .ok() .flatten() .map(|package| &package.id.version); - if actual.map_or(true, |actual| actual != expected) { + if actual != Some(expected) { return Ok(SatisfiesResult::MismatchedVersion( name.clone(), expected.clone(), @@ -1098,7 +1098,7 @@ impl Lock { .into_iter() .filter_map(|index| match index.url() { IndexUrl::Pypi(_) | IndexUrl::Url(_) => { - Some(UrlString::from(index.url().redacted())) + Some(UrlString::from(index.url().redacted().as_ref())) } IndexUrl::Path(_) => None, }) @@ -1814,13 +1814,13 @@ impl Package { let filename: WheelFilename = self.wheels[best_wheel_index].filename.clone(); let url = Url::from(ParsedArchiveUrl { - url: url.to_url(), + url: url.to_url().map_err(LockErrorKind::InvalidUrl)?, subdirectory: direct.subdirectory.clone(), ext: DistExtension::Wheel, }); let direct_dist = DirectUrlBuiltDist { filename, - location: url.clone(), + location: Box::new(url.clone()), url: VerbatimUrl::from_url(url), }; let built_dist = BuiltDist::DirectUrl(direct_dist); @@ -1946,7 +1946,7 @@ impl Package { Source::Git(url, git) => { // Remove the fragment and query from the URL; they're already present in the // `GitSource`. - let mut url = url.to_url(); + let mut url = url.to_url().map_err(LockErrorKind::InvalidUrl)?; url.set_fragment(None); url.set_query(None); @@ -1976,7 +1976,7 @@ impl Package { let DistExtension::Source(ext) = DistExtension::from_path(url.as_ref())? else { return Ok(None); }; - let location = url.to_url(); + let location = url.to_url().map_err(LockErrorKind::InvalidUrl)?; let subdirectory = direct.subdirectory.as_ref().map(PathBuf::from); let url = Url::from(ParsedArchiveUrl { url: location.clone(), @@ -2020,7 +2020,10 @@ impl Package { url: FileLocation::AbsoluteUrl(file_url.clone()), yanked: None, }); - let index = IndexUrl::from(VerbatimUrl::from_url(url.to_url())); + + let index = IndexUrl::from(VerbatimUrl::from_url( + url.to_url().map_err(LockErrorKind::InvalidUrl)?, + )); let reg_dist = RegistrySourceDist { name: self.id.name.clone(), @@ -2262,7 +2265,9 @@ impl Package { pub fn index(&self, root: &Path) -> Result, LockError> { match &self.id.source { Source::Registry(RegistrySource::Url(url)) => { - let index = IndexUrl::from(VerbatimUrl::from_url(url.to_url())); + let index = IndexUrl::from(VerbatimUrl::from_url( + url.to_url().map_err(LockErrorKind::InvalidUrl)?, + )); Ok(Some(index)) } Source::Registry(RegistrySource::Path(path)) => { @@ -2291,16 +2296,16 @@ impl Package { } /// Returns the [`ResolvedRepositoryReference`] for the package, if it is a Git source. - pub fn as_git_ref(&self) -> Option { + pub fn as_git_ref(&self) -> Result, LockError> { match &self.id.source { - Source::Git(url, git) => Some(ResolvedRepositoryReference { + Source::Git(url, git) => Ok(Some(ResolvedRepositoryReference { reference: RepositoryReference { - url: RepositoryUrl::new(&url.to_url()), + url: RepositoryUrl::new(&url.to_url().map_err(LockErrorKind::InvalidUrl)?), reference: GitReference::from(git.kind.clone()), }, sha: git.precise, - }), - _ => None, + })), + _ => Ok(None), } } } @@ -3138,7 +3143,7 @@ impl SourceDist { match ®_dist.index { IndexUrl::Pypi(_) | IndexUrl::Url(_) => { let url = normalize_file_location(®_dist.file.url) - .map_err(LockErrorKind::InvalidFileUrl) + .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; @@ -3153,7 +3158,7 @@ impl SourceDist { .file .url .to_url() - .map_err(LockErrorKind::InvalidFileUrl)? + .map_err(LockErrorKind::InvalidUrl)? .to_file_path() .map_err(|()| LockErrorKind::UrlToPath)?; let path = relative_to(®_dist_path, index_path) @@ -3444,7 +3449,7 @@ impl Wheel { match &wheel.index { IndexUrl::Pypi(_) | IndexUrl::Url(_) => { let url = normalize_file_location(&wheel.file.url) - .map_err(LockErrorKind::InvalidFileUrl) + .map_err(LockErrorKind::InvalidUrl) .map_err(LockError::from)?; let hash = wheel.file.hashes.iter().max().cloned().map(Hash::from); let size = wheel.file.size; @@ -3461,7 +3466,7 @@ impl Wheel { .file .url .to_url() - .map_err(LockErrorKind::InvalidFileUrl)? + .map_err(LockErrorKind::InvalidUrl)? .to_file_path() .map_err(|()| LockErrorKind::UrlToPath)?; let path = relative_to(&wheel_path, index_path) @@ -3507,7 +3512,7 @@ impl Wheel { let filename: WheelFilename = self.filename.clone(); match source { - RegistrySource::Url(index_url) => { + RegistrySource::Url(url) => { let file_url = match &self.url { WheelWireSource::Url { url } => url, WheelWireSource::Path { .. } | WheelWireSource::Filename { .. } => { @@ -3528,7 +3533,9 @@ impl Wheel { url: FileLocation::AbsoluteUrl(file_url.clone()), yanked: None, }); - let index = IndexUrl::from(VerbatimUrl::from_url(index_url.to_url())); + let index = IndexUrl::from(VerbatimUrl::from_url( + url.to_url().map_err(LockErrorKind::InvalidUrl)?, + )); Ok(RegistryBuiltWheel { filename, file, @@ -3862,15 +3869,14 @@ impl<'de> serde::Deserialize<'de> for Hash { /// Convert a [`FileLocation`] into a normalized [`UrlString`]. fn normalize_file_location(location: &FileLocation) -> Result { match location { - FileLocation::AbsoluteUrl(ref absolute) => Ok(absolute.as_base_url()), + FileLocation::AbsoluteUrl(ref absolute) => Ok(absolute.without_fragment()), FileLocation::RelativeUrl(_, _) => Ok(normalize_url(location.to_url()?)), } } -/// Convert a [`Url`] into a normalized [`UrlString`]. +/// Convert a [`Url`] into a normalized [`UrlString`] by removing the fragment. fn normalize_url(mut url: Url) -> UrlString { url.set_fragment(None); - url.set_query(None); UrlString::from(url) } @@ -3995,9 +4001,8 @@ fn normalize_requirement(requirement: Requirement, root: &Path) -> Result { Source::Git(url, git) => { // Remove the fragment and query from the URL; they're already present in the // `GitSource`. - let mut url = url.to_url(); + let mut url = url.to_url().map_err(|_| std::fmt::Error)?; url.set_fragment(None); url.set_query(None); @@ -325,7 +325,7 @@ impl std::fmt::Display for RequirementsTxtExport<'_> { Source::Direct(url, direct) => { let subdirectory = direct.subdirectory.as_ref().map(PathBuf::from); let url = Url::from(ParsedArchiveUrl { - url: url.to_url(), + url: url.to_url().map_err(|_| std::fmt::Error)?, subdirectory: subdirectory.clone(), ext: DistExtension::Source(SourceDistExtension::TarGz), }); @@ -333,7 +333,11 @@ impl std::fmt::Display for RequirementsTxtExport<'_> { } Source::Path(path) | Source::Directory(path) => { if path.is_absolute() { - write!(f, "{}", Url::from_file_path(path).unwrap())?; + write!( + f, + "{}", + Url::from_file_path(path).map_err(|()| std::fmt::Error)? + )?; } else { write!(f, "{}", anchor(path).portable_display())?; } @@ -344,7 +348,11 @@ impl std::fmt::Display for RequirementsTxtExport<'_> { } EditableMode::NonEditable => { if path.is_absolute() { - write!(f, "{}", Url::from_file_path(path).unwrap())?; + write!( + f, + "{}", + Url::from_file_path(path).map_err(|()| std::fmt::Error)? + )?; } else { write!(f, "{}", anchor(path).portable_display())?; } diff --git a/crates/uv-resolver/src/pins.rs b/crates/uv-resolver/src/pins.rs index 73623f92c9a1..6d0d88f655ca 100644 --- a/crates/uv-resolver/src/pins.rs +++ b/crates/uv-resolver/src/pins.rs @@ -10,15 +10,17 @@ use crate::candidate_selector::Candidate; /// For example, given `Flask==3.0.0`, the [`FilePins`] would contain a mapping from `Flask` to /// `3.0.0` to the specific wheel or source distribution archive that was pinned for that version. #[derive(Clone, Debug, Default)] -pub(crate) struct FilePins(FxHashMap>); +pub(crate) struct FilePins(FxHashMap<(PackageName, uv_pep440::Version), ResolvedDist>); +// Inserts are common (every time we select a version) while reads are rare (converting the +// final resolution). impl FilePins { /// Pin a candidate package. pub(crate) fn insert(&mut self, candidate: &Candidate, dist: &CompatibleDist) { - self.0.entry(candidate.name().clone()).or_default().insert( - candidate.version().clone(), - dist.for_installation().to_owned(), - ); + self.0 + .entry((candidate.name().clone(), candidate.version().clone())) + // Avoid the expensive clone when a version is selected again. + .or_insert_with(|| dist.for_installation().to_owned()); } /// Return the pinned file for the given package name and version, if it exists. @@ -27,6 +29,6 @@ impl FilePins { name: &PackageName, version: &uv_pep440::Version, ) -> Option<&ResolvedDist> { - self.0.get(name)?.get(version) + self.0.get(&(name.clone(), version.clone())) } } diff --git a/crates/uv-resolver/src/pubgrub/priority.rs b/crates/uv-resolver/src/pubgrub/priority.rs index 588b8f6bcc67..5fc5a97575b6 100644 --- a/crates/uv-resolver/src/pubgrub/priority.rs +++ b/crates/uv-resolver/src/pubgrub/priority.rs @@ -1,14 +1,16 @@ -use pubgrub::Range; -use rustc_hash::FxHashMap; use std::cmp::Reverse; use std::collections::hash_map::OccupiedEntry; -use crate::fork_urls::ForkUrls; +use pubgrub::Range; +use rustc_hash::FxHashMap; + use uv_normalize::PackageName; use uv_pep440::Version; +use crate::fork_urls::ForkUrls; use crate::pubgrub::package::PubGrubPackage; use crate::pubgrub::PubGrubPackageInner; +use crate::SentinelRange; /// A prioritization map to guide the PubGrub resolution process. /// @@ -56,7 +58,9 @@ impl PubGrubPriorities { // Compute the priority. let priority = if urls.get(name).is_some() { PubGrubPriority::DirectUrl(Reverse(index)) - } else if version.as_singleton().is_some() { + } else if version.as_singleton().is_some() + || SentinelRange::from(version).is_sentinel() + { PubGrubPriority::Singleton(Reverse(index)) } else { // Keep the conflict-causing packages to avoid loops where we seesaw between @@ -79,7 +83,9 @@ impl PubGrubPriorities { // Compute the priority. let priority = if urls.get(name).is_some() { PubGrubPriority::DirectUrl(Reverse(next)) - } else if version.as_singleton().is_some() { + } else if version.as_singleton().is_some() + || SentinelRange::from(version).is_sentinel() + { PubGrubPriority::Singleton(Reverse(next)) } else { PubGrubPriority::Unspecified(Reverse(next)) @@ -98,7 +104,7 @@ impl PubGrubPriorities { | PubGrubPriority::ConflictEarly(Reverse(index)) | PubGrubPriority::Singleton(Reverse(index)) | PubGrubPriority::DirectUrl(Reverse(index)) => Some(*index), - PubGrubPriority::Root => None, + PubGrubPriority::Proxy | PubGrubPriority::Root => None, } } @@ -107,9 +113,9 @@ impl PubGrubPriorities { let package_priority = match &**package { PubGrubPackageInner::Root(_) => Some(PubGrubPriority::Root), PubGrubPackageInner::Python(_) => Some(PubGrubPriority::Root), - PubGrubPackageInner::Marker { name, .. } => self.package_priority.get(name).copied(), - PubGrubPackageInner::Extra { name, .. } => self.package_priority.get(name).copied(), - PubGrubPackageInner::Dev { name, .. } => self.package_priority.get(name).copied(), + PubGrubPackageInner::Marker { .. } => Some(PubGrubPriority::Proxy), + PubGrubPackageInner::Extra { .. } => Some(PubGrubPriority::Proxy), + PubGrubPackageInner::Dev { .. } => Some(PubGrubPriority::Proxy), PubGrubPackageInner::Package { name, .. } => self.package_priority.get(name).copied(), }; let virtual_package_tiebreaker = self @@ -137,10 +143,7 @@ impl PubGrubPriorities { }; match self.package_priority.entry(name.clone()) { std::collections::hash_map::Entry::Occupied(mut entry) => { - if matches!( - entry.get(), - PubGrubPriority::ConflictEarly(_) | PubGrubPriority::Singleton(_) - ) { + if matches!(entry.get(), PubGrubPriority::ConflictEarly(_)) { // Already in the right category return false; }; @@ -175,9 +178,7 @@ impl PubGrubPriorities { // The ConflictEarly` match avoids infinite loops. if matches!( entry.get(), - PubGrubPriority::ConflictLate(_) - | PubGrubPriority::ConflictEarly(_) - | PubGrubPriority::Singleton(_) + PubGrubPriority::ConflictLate(_) | PubGrubPriority::ConflictEarly(_) ) { // Already in the right category return false; @@ -224,6 +225,13 @@ pub(crate) enum PubGrubPriority { /// [`ForkUrls`]. DirectUrl(Reverse), + /// The package is a proxy package. + /// + /// We process proxy packages eagerly since each proxy package expands into two "regular" + /// [`PubGrubPackage`] packages, which gives us additional constraints while not affecting the + /// priorities (since the expanded dependencies are all linked to the same package name). + Proxy, + /// The package is the root package. Root, } diff --git a/crates/uv-resolver/src/requires_python.rs b/crates/uv-resolver/src/requires_python.rs index 7c6b3b99b7aa..9fbaa36af390 100644 --- a/crates/uv-resolver/src/requires_python.rs +++ b/crates/uv-resolver/src/requires_python.rs @@ -586,6 +586,17 @@ impl SimplifiedMarkerTree { pub struct LowerBound(Bound); impl LowerBound { + /// Initialize a [`LowerBound`] with the given bound. + /// + /// 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::Unbounded => Bound::Unbounded, + }) + } + /// Return the [`LowerBound`] truncated to the major and minor version. fn major_minor(&self) -> Self { match &self.0 { @@ -600,6 +611,15 @@ impl LowerBound { Bound::Unbounded => Self(Bound::Unbounded), } } + + /// Returns `true` if the lower bound contains the given version. + pub fn contains(&self, version: &Version) -> bool { + match self.0 { + Bound::Included(ref bound) => bound <= version, + Bound::Excluded(ref bound) => bound < version, + Bound::Unbounded => true, + } + } } impl PartialOrd for LowerBound { @@ -668,19 +688,6 @@ impl Default for LowerBound { } } -impl LowerBound { - /// Initialize a [`LowerBound`] with the given bound. - /// - /// 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::Unbounded => Bound::Unbounded, - }) - } -} - impl Deref for LowerBound { type Target = Bound; @@ -699,6 +706,17 @@ impl From for Bound { pub struct UpperBound(Bound); impl UpperBound { + /// Initialize a [`UpperBound`] with the given bound. + /// + /// 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::Unbounded => Bound::Unbounded, + }) + } + /// Return the [`UpperBound`] truncated to the major and minor version. fn major_minor(&self) -> Self { match &self.0 { @@ -721,6 +739,15 @@ impl UpperBound { Bound::Unbounded => Self(Bound::Unbounded), } } + + /// Returns `true` if the upper bound contains the given version. + pub fn contains(&self, version: &Version) -> bool { + match self.0 { + Bound::Included(ref bound) => bound >= version, + Bound::Excluded(ref bound) => bound > version, + Bound::Unbounded => true, + } + } } impl PartialOrd for UpperBound { @@ -787,19 +814,6 @@ impl Default for UpperBound { } } -impl UpperBound { - /// Initialize a [`UpperBound`] with the given bound. - /// - /// 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::Unbounded => Bound::Unbounded, - }) - } -} - impl Deref for UpperBound { type Target = Bound; diff --git a/crates/uv-resolver/src/resolution/output.rs b/crates/uv-resolver/src/resolution/output.rs index aa9385d71ed2..deec8ace1730 100644 --- a/crates/uv-resolver/src/resolution/output.rs +++ b/crates/uv-resolver/src/resolution/output.rs @@ -7,6 +7,7 @@ use petgraph::{ Directed, Direction, }; use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet}; + use uv_configuration::{Constraints, Overrides}; use uv_distribution::Metadata; use uv_distribution_types::{ @@ -726,7 +727,7 @@ impl ResolverOutput { MarkerExpression::String { key: value_string.into(), operator: MarkerOperator::Equal, - value: from_env.to_string(), + value: from_env.into(), } } }; diff --git a/crates/uv-resolver/src/resolver/batch_prefetch.rs b/crates/uv-resolver/src/resolver/batch_prefetch.rs index b8e6e95331cd..6d35a2f97913 100644 --- a/crates/uv-resolver/src/resolver/batch_prefetch.rs +++ b/crates/uv-resolver/src/resolver/batch_prefetch.rs @@ -1,8 +1,9 @@ use std::cmp::min; +use std::sync::Arc; use itertools::Itertools; -use pubgrub::{Range, Term}; -use rustc_hash::FxHashMap; +use pubgrub::{Range, Ranges, Term}; +use rustc_hash::{FxHashMap, FxHashSet}; use tokio::sync::mpsc::Sender; use tracing::{debug, trace}; @@ -41,10 +42,19 @@ enum BatchPrefetchStrategy { /// Note that these all heuristics that could totally prefetch lots of irrelevant versions. #[derive(Clone)] pub(crate) struct BatchPrefetcher { - // Internal types. - tried_versions: FxHashMap, + // Types to determine whether we need to prefetch. + tried_versions: FxHashMap>, last_prefetch: FxHashMap, - // Shared (e.g., `Arc`) types. + // Types to execute the prefetch. + prefetch_runner: BatchPrefetcherRunner, +} + +/// The types that are needed for running the batch prefetching after we determined that we need to +/// prefetch. +/// +/// These types are shared (e.g., `Arc`) so they can be cheaply cloned and moved between threads. +#[derive(Clone)] +pub(crate) struct BatchPrefetcherRunner { capabilities: IndexCapabilities, index: InMemoryIndex, request_sink: Sender, @@ -59,9 +69,11 @@ impl BatchPrefetcher { Self { tried_versions: FxHashMap::default(), last_prefetch: FxHashMap::default(), - capabilities, - index, - request_sink, + prefetch_runner: BatchPrefetcherRunner { + capabilities, + index, + request_sink, + }, } } @@ -76,7 +88,7 @@ impl BatchPrefetcher { python_requirement: &PythonRequirement, selector: &CandidateSelector, env: &ResolverEnvironment, - ) -> anyhow::Result<(), ResolveError> { + ) -> Result<(), ResolveError> { let PubGrubPackageInner::Package { name, extra: None, @@ -95,25 +107,120 @@ impl BatchPrefetcher { // This is immediate, we already fetched the version map. let versions_response = if let Some(index) = index { - self.index + self.prefetch_runner + .index .explicit() .wait_blocking(&(name.clone(), index.clone())) .ok_or_else(|| ResolveError::UnregisteredTask(name.to_string()))? } else { - self.index + self.prefetch_runner + .index .implicit() .wait_blocking(name) .ok_or_else(|| ResolveError::UnregisteredTask(name.to_string()))? }; - let VersionsResponse::Found(ref version_map) = *versions_response else { - return Ok(()); - }; - - let mut phase = BatchPrefetchStrategy::Compatible { + let phase = BatchPrefetchStrategy::Compatible { compatible: current_range.clone(), previous: version.clone(), }; + + self.last_prefetch.insert(name.clone(), num_tried); + + self.prefetch_runner.send_prefetch( + name, + unchangeable_constraints, + total_prefetch, + &versions_response, + phase, + python_requirement, + selector, + env, + )?; + + Ok(()) + } + + /// Each time we tried a version for a package, we register that here. + pub(crate) fn version_tried(&mut self, package: &PubGrubPackage, version: &Version) { + // Only track base packages, no virtual packages from extras. + let PubGrubPackageInner::Package { + name, + extra: None, + dev: None, + marker: MarkerTree::TRUE, + } = &**package + else { + return; + }; + self.tried_versions + .entry(name.clone()) + .or_default() + .insert(version.clone()); + } + + /// After 5, 10, 20, 40 tried versions, prefetch that many versions to start early but not + /// too aggressive. Later we schedule the prefetch of 50 versions every 20 versions, this gives + /// us a good buffer until we see prefetch again and is high enough to saturate the task pool. + fn should_prefetch(&self, next: &PubGrubPackage) -> (usize, bool) { + let PubGrubPackageInner::Package { + name, + extra: None, + dev: None, + marker: MarkerTree::TRUE, + } = &**next + else { + return (0, false); + }; + + let num_tried = self.tried_versions.get(name).map_or(0, FxHashSet::len); + let previous_prefetch = self.last_prefetch.get(name).copied().unwrap_or_default(); + let do_prefetch = (num_tried >= 5 && previous_prefetch < 5) + || (num_tried >= 10 && previous_prefetch < 10) + || (num_tried >= 20 && previous_prefetch < 20) + || (num_tried >= 20 && num_tried - previous_prefetch >= 20); + (num_tried, do_prefetch) + } + + /// Log stats about how many versions we tried. + pub(crate) fn log_tried_versions(&self) { + let total_versions: usize = self.tried_versions.values().map(FxHashSet::len).sum(); + let mut tried_versions: Vec<_> = self + .tried_versions + .iter() + .map(|(name, versions)| (name, versions.len())) + .collect(); + tried_versions.sort_by(|(p1, c1), (p2, c2)| { + c1.cmp(c2) + .reverse() + .then(p1.to_string().cmp(&p2.to_string())) + }); + let counts = tried_versions + .iter() + .map(|(package, count)| format!("{package} {count}")) + .join(", "); + debug!("Tried {total_versions} versions: {counts}"); + } +} + +impl BatchPrefetcherRunner { + /// Given that the conditions for prefetching are met, find the versions to prefetch and + /// send the prefetch requests. + fn send_prefetch( + &self, + name: &PackageName, + unchangeable_constraints: Option<&Term>>, + total_prefetch: usize, + versions_response: &Arc, + mut phase: BatchPrefetchStrategy, + python_requirement: &PythonRequirement, + selector: &CandidateSelector, + env: &ResolverEnvironment, + ) -> Result<(), ResolveError> { + let VersionsResponse::Found(ref version_map) = &**versions_response else { + return Ok(()); + }; + let mut prefetch_count = 0; for _ in 0..total_prefetch { let candidate = match phase { @@ -148,7 +255,7 @@ impl BatchPrefetcher { // If we have constraints from root, don't go beyond those. Example: We are // prefetching for foo 1.60 and have a dependency for `foo>=1.50`, so we should // only prefetch 1.60 to 1.50, knowing 1.49 will always be rejected. - if let Some(unchangeable_constraints) = unchangeable_constraints { + if let Some(unchangeable_constraints) = &unchangeable_constraints { range = match unchangeable_constraints { Term::Positive(constraints) => range.intersection(constraints), Term::Negative(negative_constraints) => { @@ -189,33 +296,9 @@ impl BatchPrefetcher { } // Avoid prefetching for distributions that don't satisfy the Python requirement. - match dist { - CompatibleDist::InstalledDist(_) => {} - CompatibleDist::SourceDist { sdist, .. } - | CompatibleDist::IncompatibleWheel { sdist, .. } => { - // Source distributions must meet both the _target_ Python version and the - // _installed_ Python version (to build successfully). - if let Some(requires_python) = sdist.file.requires_python.as_ref() { - if !python_requirement - .installed() - .is_contained_by(requires_python) - { - continue; - } - if !python_requirement.target().is_contained_by(requires_python) { - continue; - } - } - } - CompatibleDist::CompatibleWheel { wheel, .. } => { - // Wheels must meet the _target_ Python version. - if let Some(requires_python) = wheel.file.requires_python.as_ref() { - if !python_requirement.target().is_contained_by(requires_python) { - continue; - } - } - } - }; + if !satisfies_python(dist, python_requirement) { + continue; + } let dist = dist.for_resolution(); @@ -242,64 +325,38 @@ impl BatchPrefetcher { _ => debug!("Prefetched {prefetch_count} `{name}` versions"), } - self.last_prefetch.insert(name.clone(), num_tried); Ok(()) } +} - /// Each time we tried a version for a package, we register that here. - pub(crate) fn version_tried(&mut self, package: &PubGrubPackage) { - // Only track base packages, no virtual packages from extras. - let PubGrubPackageInner::Package { - name, - extra: None, - dev: None, - marker: MarkerTree::TRUE, - } = &**package - else { - return; - }; - *self.tried_versions.entry(name.clone()).or_default() += 1; - } - - /// After 5, 10, 20, 40 tried versions, prefetch that many versions to start early but not - /// too aggressive. Later we schedule the prefetch of 50 versions every 20 versions, this gives - /// us a good buffer until we see prefetch again and is high enough to saturate the task pool. - fn should_prefetch(&self, next: &PubGrubPackage) -> (usize, bool) { - let PubGrubPackageInner::Package { - name, - extra: None, - dev: None, - marker: MarkerTree::TRUE, - } = &**next - else { - return (0, false); - }; - - let num_tried = self.tried_versions.get(name).copied().unwrap_or_default(); - let previous_prefetch = self.last_prefetch.get(name).copied().unwrap_or_default(); - let do_prefetch = (num_tried >= 5 && previous_prefetch < 5) - || (num_tried >= 10 && previous_prefetch < 10) - || (num_tried >= 20 && previous_prefetch < 20) - || (num_tried >= 20 && num_tried - previous_prefetch >= 20); - (num_tried, do_prefetch) +fn satisfies_python(dist: &CompatibleDist, python_requirement: &PythonRequirement) -> bool { + match dist { + CompatibleDist::InstalledDist(_) => {} + CompatibleDist::SourceDist { sdist, .. } + | CompatibleDist::IncompatibleWheel { sdist, .. } => { + // Source distributions must meet both the _target_ Python version and the + // _installed_ Python version (to build successfully). + if let Some(requires_python) = sdist.file.requires_python.as_ref() { + if !python_requirement + .installed() + .is_contained_by(requires_python) + { + return false; + } + if !python_requirement.target().is_contained_by(requires_python) { + return false; + } + } + } + CompatibleDist::CompatibleWheel { wheel, .. } => { + // Wheels must meet the _target_ Python version. + if let Some(requires_python) = wheel.file.requires_python.as_ref() { + if !python_requirement.target().is_contained_by(requires_python) { + return false; + } + } + } } - /// Log stats about how many versions we tried. - /// - /// Note that they may be inflated when we count the same version repeatedly during - /// backtracking. - pub(crate) fn log_tried_versions(&self) { - let total_versions: usize = self.tried_versions.values().sum(); - let mut tried_versions: Vec<_> = self.tried_versions.iter().collect(); - tried_versions.sort_by(|(p1, c1), (p2, c2)| { - c1.cmp(c2) - .reverse() - .then(p1.to_string().cmp(&p2.to_string())) - }); - let counts = tried_versions - .iter() - .map(|(package, count)| format!("{package} {count}")) - .join(", "); - debug!("Tried {total_versions} versions: {counts}"); - } + true } diff --git a/crates/uv-resolver/src/resolver/mod.rs b/crates/uv-resolver/src/resolver/mod.rs index d27f0df1b76b..ced06cf26c9e 100644 --- a/crates/uv-resolver/src/resolver/mod.rs +++ b/crates/uv-resolver/src/resolver/mod.rs @@ -21,7 +21,7 @@ use tokio_stream::wrappers::ReceiverStream; use tracing::{debug, info, instrument, trace, warn, Level}; use uv_configuration::{Constraints, Overrides}; -use uv_distribution::{ArchiveMetadata, DistributionDatabase}; +use uv_distribution::DistributionDatabase; use uv_distribution_types::{ BuiltDist, CompatibleDist, DerivationChain, Dist, DistErrorKind, DistributionMetadata, IncompatibleDist, IncompatibleSource, IncompatibleWheel, IndexCapabilities, IndexLocations, @@ -31,11 +31,9 @@ use uv_distribution_types::{ use uv_git::GitResolver; use uv_normalize::{ExtraName, GroupName, PackageName}; use uv_pep440::{release_specifiers_to_ranges, Version, VersionSpecifiers, MIN_VERSION}; -use uv_pep508::MarkerTree; +use uv_pep508::{MarkerExpression, MarkerOperator, MarkerTree, MarkerValueString}; use uv_platform_tags::Tags; -use uv_pypi_types::{ - ConflictItem, ConflictItemRef, Conflicts, Requirement, ResolutionMetadata, VerbatimParsedUrl, -}; +use uv_pypi_types::{ConflictItem, ConflictItemRef, Conflicts, Requirement, VerbatimParsedUrl}; use uv_types::{BuildContext, HashStrategy, InstalledPackagesProvider}; use uv_warnings::warn_user_once; @@ -75,7 +73,6 @@ pub use crate::resolver::provider::{ DefaultResolverProvider, MetadataResponse, PackageVersionsResult, ResolverProvider, VersionsResponse, WheelMetadataResult, }; -use crate::resolver::reporter::Facade; pub use crate::resolver::reporter::{BuildId, Reporter}; use crate::yanks::AllowedYanks; use crate::{marker, DependencyMode, Exclusions, FlatIndex, Options, ResolutionMode, VersionMap}; @@ -245,15 +242,15 @@ impl /// Set the [`Reporter`] to use for this installer. #[must_use] - pub fn with_reporter(self, reporter: impl Reporter + 'static) -> Self { - let reporter = Arc::new(reporter); - + pub fn with_reporter(self, reporter: Arc) -> Self { Self { state: ResolverState { reporter: Some(reporter.clone()), ..self.state }, - provider: self.provider.with_reporter(Facade { reporter }), + provider: self + .provider + .with_reporter(reporter.into_distribution_reporter()), } } @@ -479,8 +476,6 @@ impl ResolverState ResolverState ResolverState ResolverState { // Dependencies on Python are only added when a package is incompatible; as such, // we don't need to do anything here. - // we don't need to do anything here. Ok(None) } @@ -1097,11 +1096,11 @@ impl ResolverState { - // TODO(charlie): Add derivation chain for URL dependencies. In practice, this isn't - // critical since we fetch URL dependencies _prior_ to invoking the resolver. return Err(ResolveError::Dist( - DistErrorKind::from_dist_and_err(dist, &**err), + DistErrorKind::from_requested_dist(dist, &**err), dist.clone(), DerivationChain::default(), err.clone(), @@ -1330,6 +1329,11 @@ impl ResolverState, ) -> Result, ResolveError> { + // This only applies to universal resolutions. + if env.marker_environment().is_some() { + return Ok(None); + } + // For now, we only apply this to local versions. if !candidate.version().is_local() { return Ok(None); @@ -1369,7 +1373,7 @@ impl ResolverState ResolverState ResolverState ResolverState>(); // Dependency groups can include the project itself, so no need to flatten recursive @@ -1807,9 +1843,13 @@ impl ResolverState { requirement.marker.and(marker); @@ -1875,6 +1915,7 @@ impl ResolverState + 'parameters, extra: Option<&'parameters ExtraName>, env: &'parameters ResolverEnvironment, + python_marker: MarkerTree, python_requirement: &'parameters PythonRequirement, ) -> impl Iterator> + 'parameters where @@ -1882,162 +1923,194 @@ impl ResolverState, + env: &ResolverEnvironment, + python_marker: MarkerTree, + python_requirement: &PythonRequirement, + ) -> bool { + // If the requirement isn't relevant for the current platform, skip it. + match extra { + Some(source_extra) => { + // Only include requirements that are relevant for the current extra. + if requirement.evaluate_markers(env.marker_environment(), &[]) { + return false; + } + if !requirement + .evaluate_markers(env.marker_environment(), slice::from_ref(source_extra)) + { + return false; + } + if !env.included_by_group(ConflictItemRef::from((&requirement.name, source_extra))) + { + return false; + } + } + None => { + if !requirement.evaluate_markers(env.marker_environment(), &[]) { + return false; + } + } + } + + // If the requirement would not be selected with any Python version + // supported by the root, skip it. + if python_marker.is_disjoint(requirement.marker) { + trace!( + "skipping {requirement} because of Requires-Python: {requires_python}", + requires_python = python_requirement.target(), + ); + return false; + } + + // If we're in a fork in universal mode, ignore any dependency that isn't part of + // this fork (but will be part of another fork). + if !env.included_by_marker(requirement.marker) { + trace!("skipping {requirement} because of {env}"); + return false; + } + + true + } + + /// The constraints applicable to the requirement, filtered by Python version, the markers of + /// this fork and the requested extra. + fn constraints_for_requirement<'data, 'parameters>( + &'data self, + requirement: Cow<'data, Requirement>, + extra: Option<&'parameters ExtraName>, + env: &'parameters ResolverEnvironment, + python_marker: MarkerTree, + python_requirement: &'parameters PythonRequirement, + ) -> impl Iterator> + 'parameters + where + 'data: 'parameters, + { + self.constraints + .get(&requirement.name) + .into_iter() + .flatten() + .filter_map(move |constraint| { // If the requirement would not be selected with any Python version // supported by the root, skip it. - if python_marker.is_disjoint(requirement.marker) { - trace!( - "skipping {requirement} because of Requires-Python: {requires_python}", - requires_python = python_requirement.target(), - ); - return None; - } + let constraint = if constraint.marker.is_true() { + // Additionally, if the requirement is `requests ; sys_platform == 'darwin'` + // and the constraint is `requests ; python_version == '3.6'`, the + // constraint should only apply when _both_ markers are true. + if requirement.marker.is_true() { + Cow::Borrowed(constraint) + } else { + let mut marker = constraint.marker; + marker.and(requirement.marker); + + if marker.is_false() { + trace!( + "skipping {constraint} because of disjoint markers: `{}` vs. `{}`", + constraint.marker.try_to_string().unwrap(), + requirement.marker.try_to_string().unwrap(), + ); + return None; + } + + Cow::Owned(Requirement { + name: constraint.name.clone(), + extras: constraint.extras.clone(), + groups: constraint.groups.clone(), + source: constraint.source.clone(), + origin: constraint.origin.clone(), + marker, + }) + } + } else { + let requires_python = python_requirement.target(); + + let mut marker = constraint.marker; + marker.and(requirement.marker); + + if marker.is_false() { + trace!( + "skipping {constraint} because of disjoint markers: `{}` vs. `{}`", + constraint.marker.try_to_string().unwrap(), + requirement.marker.try_to_string().unwrap(), + ); + return None; + } + + // Additionally, if the requirement is `requests ; sys_platform == 'darwin'` + // and the constraint is `requests ; python_version == '3.6'`, the + // constraint should only apply when _both_ markers are true. + if python_marker.is_disjoint(marker) { + trace!( + "skipping constraint {requirement} because of Requires-Python: {requires_python}" + ); + return None; + } + + if marker == constraint.marker { + Cow::Borrowed(constraint) + } else { + Cow::Owned(Requirement { + name: constraint.name.clone(), + extras: constraint.extras.clone(), + groups: constraint.groups.clone(), + source: constraint.source.clone(), + origin: constraint.origin.clone(), + marker, + }) + } + }; // If we're in a fork in universal mode, ignore any dependency that isn't part of // this fork (but will be part of another fork). - if !env.included_by_marker(requirement.marker) { - trace!("skipping {requirement} because of {env}"); + if !env.included_by_marker(constraint.marker) { + trace!("skipping {constraint} because of {env}"); return None; } - // If the requirement isn't relevant for the current platform, skip it. + // If the constraint isn't relevant for the current platform, skip it. match extra { Some(source_extra) => { - // Only include requirements that are relevant for the current extra. - if requirement.evaluate_markers(env.marker_environment(), &[]) { + if !constraint + .evaluate_markers(env.marker_environment(), slice::from_ref(source_extra)) + { return None; } - if !requirement.evaluate_markers( - env.marker_environment(), - slice::from_ref(source_extra), - ) { - return None; - } - if !env.included_by_group( - ConflictItemRef::from((&requirement.name, source_extra)), - ) { + if !env.included_by_group(ConflictItemRef::from((&requirement.name, source_extra))) + { return None; } } None => { - if !requirement.evaluate_markers(env.marker_environment(), &[]) { + if !constraint.evaluate_markers(env.marker_environment(), &[]) { return None; } } } - Some(requirement) - }) - .flat_map(move |requirement| { - iter::once(requirement.clone()).chain( - self.constraints - .get(&requirement.name) - .into_iter() - .flatten() - .filter_map(move |constraint| { - // If the requirement would not be selected with any Python version - // supported by the root, skip it. - let constraint = if constraint.marker.is_true() { - // Additionally, if the requirement is `requests ; sys_platform == 'darwin'` - // and the constraint is `requests ; python_version == '3.6'`, the - // constraint should only apply when _both_ markers are true. - if requirement.marker.is_true() { - Cow::Borrowed(constraint) - } else { - let mut marker = constraint.marker; - marker.and(requirement.marker); - - if marker.is_false() { - trace!( - "skipping {constraint} because of disjoint markers: `{}` vs. `{}`", - constraint.marker.try_to_string().unwrap(), - requirement.marker.try_to_string().unwrap(), - ); - return None; - } - - Cow::Owned(Requirement { - name: constraint.name.clone(), - extras: constraint.extras.clone(), - groups: constraint.groups.clone(), - source: constraint.source.clone(), - origin: constraint.origin.clone(), - marker, - }) - } - } else { - let requires_python = python_requirement.target(); - let python_marker = python_requirement.to_marker_tree(); - - let mut marker = constraint.marker; - marker.and(requirement.marker); - - if marker.is_false() { - trace!( - "skipping {constraint} because of disjoint markers: `{}` vs. `{}`", - constraint.marker.try_to_string().unwrap(), - requirement.marker.try_to_string().unwrap(), - ); - return None; - } - - // Additionally, if the requirement is `requests ; sys_platform == 'darwin'` - // and the constraint is `requests ; python_version == '3.6'`, the - // constraint should only apply when _both_ markers are true. - if python_marker.is_disjoint(marker) { - trace!( - "skipping constraint {requirement} because of Requires-Python: {requires_python}" - ); - return None; - } - - if marker == constraint.marker { - Cow::Borrowed(constraint) - } else { - Cow::Owned(Requirement { - name: constraint.name.clone(), - extras: constraint.extras.clone(), - groups: constraint.groups.clone(), - source: constraint.source.clone(), - origin: constraint.origin.clone(), - marker, - }) - } - }; - - // If we're in a fork in universal mode, ignore any dependency that isn't part of - // this fork (but will be part of another fork). - if !env.included_by_marker(constraint.marker) { - trace!("skipping {constraint} because of {env}"); - return None; - } - - // If the constraint isn't relevant for the current platform, skip it. - match extra { - Some(source_extra) => { - if !constraint.evaluate_markers( - env.marker_environment(), - slice::from_ref(source_extra), - ) { - return None; - } - if !env.included_by_group( - ConflictItemRef::from((&requirement.name, source_extra)), - ) { - return None; - } - } - None => { - if !constraint.evaluate_markers(env.marker_environment(), &[]) { - return None; - } - } - } - - Some(constraint) - }) - ) + Some(constraint) }) } @@ -2068,12 +2141,9 @@ impl ResolverState { trace!("Received installed distribution metadata for: {dist}"); - self.index.distributions().done( - dist.version_id(), - Arc::new(MetadataResponse::Found(ArchiveMetadata::from_metadata23( - metadata, - ))), - ); + self.index + .distributions() + .done(dist.version_id(), Arc::new(metadata)); } Some(Response::Dist { dist, metadata }) => { let dist_kind = match dist { @@ -2134,10 +2204,8 @@ impl ResolverState { - // TODO(charlie): This should be return a `MetadataResponse`. - let metadata = dist - .metadata() - .map_err(|err| ResolveError::ReadInstalled(Box::new(dist.clone()), err))?; + let metadata = provider.get_installed_metadata(&dist).boxed_local().await?; + Ok(Some(Response::Installed { dist, metadata })) } @@ -2251,9 +2319,9 @@ impl ResolverState { - let metadata = dist.metadata().map_err(|err| { - ResolveError::ReadInstalled(Box::new(dist.clone()), err) - })?; + let metadata = + provider.get_installed_metadata(&dist).boxed_local().await?; + Response::Installed { dist, metadata } } }; @@ -2527,7 +2595,9 @@ impl ForkState { } } - if let Some(name) = self.pubgrub.package_store[for_package] + let for_package = &self.pubgrub.package_store[for_package]; + + if let Some(name) = for_package .name_no_root() .filter(|name| !workspace_members.contains(name)) { @@ -2558,7 +2628,9 @@ impl ForkState { } // Update the package priorities. - self.priorities.insert(package, version, &self.fork_urls); + if !for_package.is_proxy() { + self.priorities.insert(package, version, &self.fork_urls); + } } let conflict = self.pubgrub.add_package_version_dependencies( @@ -3079,7 +3151,7 @@ enum Response { /// The returned metadata for an already-installed distribution. Installed { dist: InstalledDist, - metadata: ResolutionMetadata, + metadata: MetadataResponse, }, } @@ -3230,6 +3302,28 @@ impl Forks { } continue; } + } else { + // If all dependencies have the same markers, we should also avoid forking. + if let Some(dep) = deps.first() { + let marker = dep.package.marker(); + if deps.iter().all(|dep| marker == dep.package.marker()) { + // Unless that "same marker" is a Python requirement that is stricter than + // the current Python requirement. In that case, we need to fork to respect + // the stricter requirement. + if marker::requires_python(marker) + .is_none_or(|bound| !python_requirement.raises(&bound)) + { + for dep in deps { + for fork in &mut forks { + if fork.env.included_by_marker(marker) { + fork.add_dependency(dep.clone()); + } + } + } + continue; + } + } + } } for dep in deps { let mut forker = match ForkingPossibility::new(env, &dep) { diff --git a/crates/uv-resolver/src/resolver/provider.rs b/crates/uv-resolver/src/resolver/provider.rs index 29e02384699e..dc9b38e08ec9 100644 --- a/crates/uv-resolver/src/resolver/provider.rs +++ b/crates/uv-resolver/src/resolver/provider.rs @@ -2,8 +2,8 @@ use std::future::Future; use std::sync::Arc; use uv_configuration::BuildOptions; -use uv_distribution::{ArchiveMetadata, DistributionDatabase}; -use uv_distribution_types::{Dist, IndexCapabilities, IndexUrl}; +use uv_distribution::{ArchiveMetadata, DistributionDatabase, Reporter}; +use uv_distribution_types::{Dist, IndexCapabilities, IndexUrl, InstalledDist, RequestedDist}; use uv_normalize::PackageName; use uv_pep440::{Version, VersionSpecifiers}; use uv_platform_tags::Tags; @@ -37,7 +37,7 @@ pub enum MetadataResponse { /// A non-fatal error. Unavailable(MetadataUnavailable), /// The distribution could not be built or downloaded, a fatal error. - Error(Box, Arc), + Error(Box, Arc), } /// Non-fatal metadata fetching error. @@ -83,7 +83,7 @@ pub trait ResolverProvider { /// Get the metadata for a distribution. /// - /// For a wheel, this is done by querying it's (remote) metadata, for a source dist we + /// For a wheel, this is done by querying it (remote) metadata. For a source distribution, we /// (fetch and) build the source distribution and return the metadata from the built /// distribution. fn get_or_build_wheel_metadata<'io>( @@ -91,9 +91,15 @@ pub trait ResolverProvider { dist: &'io Dist, ) -> impl Future + 'io; - /// Set the [`uv_distribution::Reporter`] to use for this installer. + /// Get the metadata for an installed distribution. + fn get_installed_metadata<'io>( + &'io self, + dist: &'io InstalledDist, + ) -> impl Future + 'io; + + /// Set the [`Reporter`] to use for this installer. #[must_use] - fn with_reporter(self, reporter: impl uv_distribution::Reporter + 'static) -> Self; + fn with_reporter(self, reporter: Arc) -> Self; } /// The main IO backend for the resolver, which does cached requests network requests using the @@ -139,7 +145,7 @@ impl<'a, Context: BuildContext> DefaultResolverProvider<'a, Context> { } } -impl<'a, Context: BuildContext> ResolverProvider for DefaultResolverProvider<'a, Context> { +impl ResolverProvider for DefaultResolverProvider<'_, Context> { /// Make a "Simple API" request for the package and convert the result to a [`VersionMap`]. async fn get_package_versions<'io>( &'io self, @@ -246,16 +252,30 @@ impl<'a, Context: BuildContext> ResolverProvider for DefaultResolverProvider<'a, )) } err => Ok(MetadataResponse::Error( - Box::new(dist.clone()), + Box::new(RequestedDist::Installable(dist.clone())), Arc::new(err), )), }, } } - /// Set the [`uv_distribution::Reporter`] to use for this installer. + /// Return the metadata for an installed distribution. + async fn get_installed_metadata<'io>( + &'io self, + dist: &'io InstalledDist, + ) -> WheelMetadataResult { + match self.fetcher.get_installed_metadata(dist).await { + Ok(metadata) => Ok(MetadataResponse::Found(metadata)), + Err(err) => Ok(MetadataResponse::Error( + Box::new(RequestedDist::Installed(dist.clone())), + Arc::new(err), + )), + } + } + + /// Set the [`Reporter`] to use for this installer. #[must_use] - fn with_reporter(self, reporter: impl uv_distribution::Reporter + 'static) -> Self { + fn with_reporter(self, reporter: Arc) -> Self { Self { fetcher: self.fetcher.with_reporter(reporter), ..self diff --git a/crates/uv-resolver/src/resolver/reporter.rs b/crates/uv-resolver/src/resolver/reporter.rs index 3010ec6388c8..f2bf0f006a2a 100644 --- a/crates/uv-resolver/src/resolver/reporter.rs +++ b/crates/uv-resolver/src/resolver/reporter.rs @@ -37,9 +37,20 @@ pub trait Reporter: Send + Sync { fn on_checkout_complete(&self, url: &Url, rev: &str, id: usize); } +impl dyn Reporter { + /// Converts this reporter to a [`uv_distribution::Reporter`]. + pub(crate) fn into_distribution_reporter( + self: Arc, + ) -> Arc { + Arc::new(Facade { + reporter: self.clone(), + }) + } +} + /// A facade for converting from [`Reporter`] to [`uv_distribution::Reporter`]. -pub(crate) struct Facade { - pub(crate) reporter: Arc, +struct Facade { + reporter: Arc, } impl uv_distribution::Reporter for Facade { diff --git a/crates/uv-resolver/src/universal_marker.rs b/crates/uv-resolver/src/universal_marker.rs index 00673e95ae2b..0789ad264a61 100644 --- a/crates/uv-resolver/src/universal_marker.rs +++ b/crates/uv-resolver/src/universal_marker.rs @@ -69,17 +69,24 @@ pub struct UniversalMarker { /// stand-point, evaluating it requires uv's custom encoding of extras (and /// groups). marker: MarkerTree, + /// The strictly PEP 508 version of `marker`. Basically, `marker`, but + /// without any extras in it. This could be computed on demand (and + /// that's what we used to do), but we do it enough that it was causing a + /// regression in some cases. + pep508: MarkerTree, } impl UniversalMarker { /// A constant universal marker that always evaluates to `true`. pub(crate) const TRUE: UniversalMarker = UniversalMarker { marker: MarkerTree::TRUE, + pep508: MarkerTree::TRUE, }; /// A constant universal marker that always evaluates to `false`. pub(crate) const FALSE: UniversalMarker = UniversalMarker { marker: MarkerTree::FALSE, + pep508: MarkerTree::FALSE, }; /// Creates a new universal marker from its constituent pieces. @@ -94,7 +101,10 @@ impl UniversalMarker { /// Creates a new universal marker from a marker that has already been /// combined from a PEP 508 and conflict marker. pub(crate) fn from_combined(marker: MarkerTree) -> UniversalMarker { - UniversalMarker { marker } + UniversalMarker { + marker, + pep508: marker.without_extras(), + } } /// Combine this universal marker with the one given in a way that unions @@ -102,6 +112,7 @@ impl UniversalMarker { /// `other` evaluate to `true`. pub(crate) fn or(&mut self, other: UniversalMarker) { self.marker.or(other.marker); + self.pep508.or(other.pep508); } /// Combine this universal marker with the one given in a way that @@ -109,6 +120,7 @@ impl UniversalMarker { /// `self` and `other` evaluate to `true`. pub(crate) fn and(&mut self, other: UniversalMarker) { self.marker.and(other.marker); + self.pep508.and(other.pep508); } /// Imbibes the world knowledge expressed by `conflicts` into this marker. @@ -121,6 +133,7 @@ impl UniversalMarker { let self_marker = self.marker; self.marker = conflicts.marker; self.marker.implies(self_marker); + self.pep508 = self.marker.without_extras(); } /// Assumes that a given extra/group for the given package is activated. @@ -132,6 +145,7 @@ impl UniversalMarker { ConflictPackage::Extra(ref extra) => self.assume_extra(item.package(), extra), ConflictPackage::Group(ref group) => self.assume_group(item.package(), group), } + self.pep508 = self.marker.without_extras(); } /// Assumes that a given extra/group for the given package is not @@ -144,6 +158,7 @@ impl UniversalMarker { ConflictPackage::Extra(ref extra) => self.assume_not_extra(item.package(), extra), ConflictPackage::Group(ref group) => self.assume_not_group(item.package(), group), } + self.pep508 = self.marker.without_extras(); } /// Assumes that a given extra for the given package is activated. @@ -155,6 +170,7 @@ impl UniversalMarker { self.marker = self .marker .simplify_extras_with(|candidate| *candidate == extra); + self.pep508 = self.marker.without_extras(); } /// Assumes that a given extra for the given package is not activated. @@ -166,6 +182,7 @@ impl UniversalMarker { self.marker = self .marker .simplify_not_extras_with(|candidate| *candidate == extra); + self.pep508 = self.marker.without_extras(); } /// Assumes that a given group for the given package is activated. @@ -177,6 +194,7 @@ impl UniversalMarker { self.marker = self .marker .simplify_extras_with(|candidate| *candidate == extra); + self.pep508 = self.marker.without_extras(); } /// Assumes that a given group for the given package is not activated. @@ -188,6 +206,7 @@ impl UniversalMarker { self.marker = self .marker .simplify_not_extras_with(|candidate| *candidate == extra); + self.pep508 = self.marker.without_extras(); } /// Returns true if this universal marker will always evaluate to `true`. @@ -259,7 +278,7 @@ impl UniversalMarker { /// always use a universal marker since it accounts for all possible ways /// for a package to be installed. pub fn pep508(self) -> MarkerTree { - self.marker.without_extras() + self.pep508 } /// Returns the non-PEP 508 marker expression that represents conflicting @@ -469,6 +488,7 @@ fn encode_package_group(package: &PackageName, group: &GroupName) -> ExtraName { #[cfg(test)] mod tests { use super::*; + use std::str::FromStr; use uv_pypi_types::ConflictSet; @@ -497,7 +517,7 @@ mod tests { /// Shortcut for creating a package name. fn create_package(name: &str) -> PackageName { - PackageName::new(name.to_string()).unwrap() + PackageName::from_str(name).unwrap() } /// Shortcut for creating an extra name. diff --git a/crates/uv-resolver/src/version_map.rs b/crates/uv-resolver/src/version_map.rs index fc4a409d22c7..60d822f20ad6 100644 --- a/crates/uv-resolver/src/version_map.rs +++ b/crates/uv-resolver/src/version_map.rs @@ -1,6 +1,9 @@ -use pubgrub::Range; use std::collections::btree_map::{BTreeMap, Entry}; +use std::collections::Bound; +use std::ops::RangeBounds; use std::sync::OnceLock; + +use pubgrub::Ranges; use tracing::instrument; use uv_client::{OwnedArchive, SimpleMetadata, VersionFiles}; @@ -112,23 +115,9 @@ impl VersionMap { /// Return the [`DistFile`] for the given version, if any. pub(crate) fn get(&self, version: &Version) -> Option<&PrioritizedDist> { - self.get_with_version(version).map(|(_version, dist)| dist) - } - - /// Return the [`DistFile`] and the `Version` from the map for the given - /// version, if any. - /// - /// This is useful when you depend on access to the specific `Version` - /// stored in this map. For example, the versions `1.2.0` and `1.2` are - /// semantically equivalent, but when converted to strings, they are - /// distinct. - pub(crate) fn get_with_version( - &self, - version: &Version, - ) -> Option<(&Version, &PrioritizedDist)> { match self.inner { - VersionMapInner::Eager(ref eager) => eager.map.get_key_value(version), - VersionMapInner::Lazy(ref lazy) => lazy.get_with_version(version), + VersionMapInner::Eager(ref eager) => eager.map.get(version), + VersionMapInner::Lazy(ref lazy) => lazy.get(version), } } @@ -156,8 +145,8 @@ impl VersionMap { /// for each version. pub(crate) fn iter( &self, - range: &Range, - ) -> impl DoubleEndedIterator + ExactSizeIterator { + range: &Ranges, + ) -> impl DoubleEndedIterator { // Performance optimization: If we only have a single version, return that version directly. if let Some(version) = range.as_singleton() { either::Either::Left(match self.inner { @@ -185,20 +174,24 @@ impl VersionMap { } else { either::Either::Right(match self.inner { VersionMapInner::Eager(ref eager) => { - either::Either::Left(eager.map.iter().map(|(version, dist)| { - let version_map_dist = VersionMapDistHandle { - inner: VersionMapDistHandleInner::Eager(dist), - }; - (version, version_map_dist) - })) + either::Either::Left(eager.map.range(BoundingRange::from(range)).map( + |(version, dist)| { + let version_map_dist = VersionMapDistHandle { + inner: VersionMapDistHandleInner::Eager(dist), + }; + (version, version_map_dist) + }, + )) } VersionMapInner::Lazy(ref lazy) => { - either::Either::Right(lazy.map.iter().map(|(version, dist)| { - let version_map_dist = VersionMapDistHandle { - inner: VersionMapDistHandleInner::Lazy { lazy, dist }, - }; - (version, version_map_dist) - })) + either::Either::Right(lazy.map.range(BoundingRange::from(range)).map( + |(version, dist)| { + let version_map_dist = VersionMapDistHandle { + inner: VersionMapDistHandleInner::Lazy { lazy, dist }, + }; + (version, version_map_dist) + }, + )) } }) } @@ -342,16 +335,9 @@ struct VersionMapLazy { impl VersionMapLazy { /// Returns the distribution for the given version, if it exists. fn get(&self, version: &Version) -> Option<&PrioritizedDist> { - self.get_with_version(version) - .map(|(_, prioritized_dist)| prioritized_dist) - } - - /// Returns the distribution for the given version along with the version - /// in this map, if it exists. - fn get_with_version(&self, version: &Version) -> Option<(&Version, &PrioritizedDist)> { - let (version, lazy_dist) = self.map.get_key_value(version)?; + let lazy_dist = self.map.get(version)?; let priority_dist = self.get_lazy(lazy_dist)?; - Some((version, priority_dist)) + Some(priority_dist) } /// Given a reference to a possibly-initialized distribution that is in @@ -535,14 +521,22 @@ impl VersionMapLazy { } // Determine a compatibility for the wheel based on tags. - let priority = match &self.tags { - Some(tags) => match filename.compatibility(tags) { + let priority = if let Some(tags) = &self.tags { + match filename.compatibility(tags) { TagCompatibility::Incompatible(tag) => { - return WheelCompatibility::Incompatible(IncompatibleWheel::Tag(tag)) + return WheelCompatibility::Incompatible(IncompatibleWheel::Tag(tag)); } TagCompatibility::Compatible(priority) => Some(priority), - }, - None => None, + } + } else { + // Check if the wheel is compatible with the `requires-python` (i.e., the Python + // ABI tag is not less than the `requires-python` minimum version). + if !self.requires_python.matches_wheel_tag(filename) { + return WheelCompatibility::Incompatible(IncompatibleWheel::Tag( + IncompatibleTag::AbiPythonVersion, + )); + } + None }; // Check if hashes line up. If hashes aren't required, they're considered matching. @@ -560,14 +554,6 @@ impl VersionMapLazy { } }; - // Check if the wheel is compatible with the `requires-python` (i.e., the Python ABI tag - // is not less than the `requires-python` minimum version). - if !self.requires_python.matches_wheel_tag(filename) { - return WheelCompatibility::Incompatible(IncompatibleWheel::Tag( - IncompatibleTag::AbiPythonVersion, - )); - } - // Break ties with the build tag. let build_tag = filename.build_tag.clone(); @@ -579,7 +565,7 @@ impl VersionMapLazy { /// a single version of a package. #[derive(Debug)] enum LazyPrioritizedDist { - /// Represents a eagerly constructed distribution from a + /// Represents an eagerly constructed distribution from a /// `FlatDistributions`. OnlyFlat(PrioritizedDist), /// Represents a lazily constructed distribution from an index into a @@ -609,3 +595,29 @@ struct SimplePrioritizedDist { /// of writing, is to use `--exclude-newer 1900-01-01`.) dist: OnceLock>, } + +/// A range that can be used to iterate over a subset of a [`BTreeMap`]. +#[derive(Debug)] +struct BoundingRange<'a> { + min: Bound<&'a Version>, + max: Bound<&'a Version>, +} + +impl<'a> From<&'a Ranges> for BoundingRange<'a> { + fn from(value: &'a Ranges) -> Self { + let (min, max) = value + .bounding_range() + .unwrap_or((Bound::Unbounded, Bound::Unbounded)); + Self { min, max } + } +} + +impl<'a> RangeBounds for BoundingRange<'a> { + fn start_bound(&self) -> Bound<&'a Version> { + self.min + } + + fn end_bound(&self) -> Bound<&'a Version> { + self.max + } +} diff --git a/crates/uv-resolver/src/yanks.rs b/crates/uv-resolver/src/yanks.rs index 6ed70a57e93f..6c43f2ed9802 100644 --- a/crates/uv-resolver/src/yanks.rs +++ b/crates/uv-resolver/src/yanks.rs @@ -55,6 +55,6 @@ impl AllowedYanks { pub fn contains(&self, package_name: &PackageName, version: &Version) -> bool { self.0 .get(package_name) - .map_or(false, |versions| versions.contains(version)) + .is_some_and(|versions| versions.contains(version)) } } diff --git a/crates/uv-scripts/src/lib.rs b/crates/uv-scripts/src/lib.rs index cdcccc96e613..ad25ac131c9d 100644 --- a/crates/uv-scripts/src/lib.rs +++ b/crates/uv-scripts/src/lib.rs @@ -54,6 +54,14 @@ impl Pep723Item { Self::Remote(_) => None, } } + + /// Return the PEP 723 script, if any. + pub fn as_script(&self) -> Option<&Pep723Script> { + match self { + Self::Script(script) => Some(script), + _ => None, + } + } } /// A reference to a PEP 723 item. @@ -197,10 +205,10 @@ impl Pep723Script { let metadata = serialize_metadata(&default_metadata); let script = if let Some(existing_contents) = existing_contents { - indoc::formatdoc! {r#" + indoc::formatdoc! {r" {metadata} {content} - "#, + ", content = String::from_utf8(existing_contents).map_err(|err| Pep723Error::Utf8(err.utf8_error()))?} } else { indoc::formatdoc! {r#" @@ -222,7 +230,7 @@ impl Pep723Script { } /// Replace the existing metadata in the file with new metadata and write the updated content. - pub async fn write(&self, metadata: &str) -> Result<(), Pep723Error> { + pub fn write(&self, metadata: &str) -> Result<(), io::Error> { let content = format!( "{}{}{}", self.prelude, @@ -230,10 +238,22 @@ impl Pep723Script { self.postlude ); - fs_err::tokio::write(&self.path, content).await?; + fs_err::write(&self.path, content)?; Ok(()) } + + /// Return the [`Sources`] defined in the PEP 723 metadata. + pub fn sources(&self) -> &BTreeMap { + static EMPTY: BTreeMap = BTreeMap::new(); + + self.metadata + .tool + .as_ref() + .and_then(|tool| tool.uv.as_ref()) + .and_then(|uv| uv.sources.as_ref()) + .unwrap_or(&EMPTY) + } } /// PEP 723 metadata as parsed from a `script` comment block. @@ -287,7 +307,7 @@ impl Pep723Metadata { } impl FromStr for Pep723Metadata { - type Err = Pep723Error; + type Err = toml::de::Error; /// Parse `Pep723Metadata` from a raw TOML string. fn from_str(raw: &str) -> Result { diff --git a/crates/uv-settings/src/combine.rs b/crates/uv-settings/src/combine.rs index d333144740d2..14c37db8d433 100644 --- a/crates/uv-settings/src/combine.rs +++ b/crates/uv-settings/src/combine.rs @@ -4,7 +4,8 @@ use std::path::PathBuf; use url::Url; use uv_configuration::{ - ConfigSettings, IndexStrategy, KeyringProviderType, TargetTriple, TrustedPublishing, + ConfigSettings, IndexStrategy, KeyringProviderType, RequiredVersion, TargetTriple, + TrustedPublishing, }; use uv_distribution_types::{Index, IndexUrl, PipExtraIndex, PipFindLinks, PipIndex}; use uv_install_wheel::linker::LinkMode; @@ -73,12 +74,12 @@ macro_rules! impl_combine_or { impl_combine_or!(AnnotationStyle); impl_combine_or!(ExcludeNewer); +impl_combine_or!(ForkStrategy); impl_combine_or!(Index); impl_combine_or!(IndexStrategy); impl_combine_or!(IndexUrl); impl_combine_or!(KeyringProviderType); impl_combine_or!(LinkMode); -impl_combine_or!(ForkStrategy); impl_combine_or!(NonZeroUsize); impl_combine_or!(PathBuf); impl_combine_or!(PipExtraIndex); @@ -88,10 +89,11 @@ impl_combine_or!(PrereleaseMode); impl_combine_or!(PythonDownloads); impl_combine_or!(PythonPreference); impl_combine_or!(PythonVersion); +impl_combine_or!(RequiredVersion); impl_combine_or!(ResolutionMode); +impl_combine_or!(SchemaConflicts); impl_combine_or!(String); impl_combine_or!(SupportedEnvironments); -impl_combine_or!(SchemaConflicts); impl_combine_or!(TargetTriple); impl_combine_or!(TrustedPublishing); impl_combine_or!(Url); diff --git a/crates/uv-settings/src/settings.rs b/crates/uv-settings/src/settings.rs index 1285f0d6d1b5..6dd61ea380cd 100644 --- a/crates/uv-settings/src/settings.rs +++ b/crates/uv-settings/src/settings.rs @@ -1,10 +1,12 @@ -use serde::{Deserialize, Serialize}; use std::{fmt::Debug, num::NonZeroUsize, path::PathBuf}; + +use serde::{Deserialize, Serialize}; use url::Url; + use uv_cache_info::CacheKey; use uv_configuration::{ - ConfigSettings, IndexStrategy, KeyringProviderType, PackageNameSpecifier, TargetTriple, - TrustedHost, TrustedPublishing, + ConfigSettings, IndexStrategy, KeyringProviderType, PackageNameSpecifier, RequiredVersion, + TargetTriple, TrustedHost, TrustedPublishing, }; use uv_distribution_types::{ Index, IndexUrl, PipExtraIndex, PipFindLinks, PipIndex, StaticMetadata, @@ -149,6 +151,20 @@ impl Options { #[serde(rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct GlobalOptions { + /// Enforce a requirement on the version of uv. + /// + /// If the version of uv does not meet the requirement at runtime, uv will exit + /// with an error. + /// + /// Accepts a [PEP 440](https://peps.python.org/pep-0440/) specifier, like `==0.5.0` or `>=0.5.0`. + #[option( + default = "null", + value_type = "str", + example = r#" + required-version = ">=0.5.0" + "# + )] + pub required_version: Option, /// Whether to load TLS certificates from the platform's native certificate store. /// /// By default, uv loads certificates from the bundled `webpki-roots` crate. The @@ -1262,16 +1278,16 @@ pub struct PipOptions { "# )] pub universal: Option, - /// Limit candidate packages to those that were uploaded prior to the given date. + /// Limit candidate packages to those that were uploaded prior to a given point in time. /// - /// Accepts both [RFC 3339](https://www.rfc-editor.org/rfc/rfc3339.html) timestamps (e.g., - /// `2006-12-02T02:07:43Z`) and local dates in the same format (e.g., `2006-12-02`) in your - /// system's configured time zone. + /// Accepts 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. #[option( default = "None", value_type = "str", example = r#" - exclude-newer = "2006-12-02" + exclude-newer = "2006-12-02T02:07:43Z" "# )] pub exclude_newer: Option, @@ -1623,6 +1639,7 @@ impl From for ResolverInstallerOptions { pub struct OptionsWire { // #[serde(flatten)] // globals: GlobalOptions + required_version: Option, native_tls: Option, offline: Option, no_cache: Option, @@ -1704,6 +1721,7 @@ pub struct OptionsWire { impl From for Options { fn from(value: OptionsWire) -> Self { let OptionsWire { + required_version, native_tls, offline, no_cache, @@ -1764,6 +1782,7 @@ impl From for Options { Self { globals: GlobalOptions { + required_version, native_tls, offline, no_cache, diff --git a/crates/uv-small-str/Cargo.toml b/crates/uv-small-str/Cargo.toml new file mode 100644 index 000000000000..c8e6cf18baf6 --- /dev/null +++ b/crates/uv-small-str/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "uv-small-str" +version = "0.0.1" +edition = { workspace = true } +rust-version = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } +repository = { workspace = true } +authors = { workspace = true } +license = { workspace = true } + +[lib] +doctest = false + +[lints] +workspace = true + +[dependencies] +arcstr = { workspace = true } +rkyv = { workspace = true } +schemars = { workspace = true, optional = true } +serde = { workspace = true } diff --git a/crates/uv-small-str/src/lib.rs b/crates/uv-small-str/src/lib.rs new file mode 100644 index 000000000000..20d66190ef7d --- /dev/null +++ b/crates/uv-small-str/src/lib.rs @@ -0,0 +1,131 @@ +use std::cmp::PartialEq; +use std::ops::Deref; + +/// An optimized type for immutable identifiers. Represented as an [`arcstr::ArcStr`] internally. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct SmallString(arcstr::ArcStr); + +impl From for SmallString { + #[inline] + fn from(s: arcstr::ArcStr) -> Self { + Self(s) + } +} + +impl From<&str> for SmallString { + #[inline] + fn from(s: &str) -> Self { + Self(s.into()) + } +} + +impl From for SmallString { + #[inline] + fn from(s: String) -> Self { + Self(s.into()) + } +} + +impl AsRef for SmallString { + #[inline] + fn as_ref(&self) -> &str { + &self.0 + } +} + +impl core::borrow::Borrow for SmallString { + #[inline] + fn borrow(&self) -> &str { + self + } +} + +impl Deref for SmallString { + type Target = str; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl core::fmt::Debug for SmallString { + #[inline] + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Debug::fmt(&self.0, f) + } +} + +impl core::fmt::Display for SmallString { + #[inline] + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Display::fmt(&self.0, f) + } +} + +/// A [`serde::Serialize`] implementation for [`SmallString`]. +impl serde::Serialize for SmallString { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0.serialize(serializer) + } +} + +/// An [`rkyv`] implementation for [`SmallString`]. +impl rkyv::Archive for SmallString { + type Archived = rkyv::string::ArchivedString; + type Resolver = rkyv::string::StringResolver; + + #[inline] + fn resolve(&self, resolver: Self::Resolver, out: rkyv::Place) { + rkyv::string::ArchivedString::resolve_from_str(&self.0, resolver, out); + } +} + +impl rkyv::Serialize for SmallString +where + S: rkyv::rancor::Fallible + rkyv::ser::Allocator + rkyv::ser::Writer + ?Sized, + S::Error: rkyv::rancor::Source, +{ + fn serialize(&self, serializer: &mut S) -> Result { + rkyv::string::ArchivedString::serialize_from_str(&self.0, serializer) + } +} + +impl rkyv::Deserialize + for rkyv::string::ArchivedString +{ + fn deserialize(&self, _deserializer: &mut D) -> Result { + Ok(SmallString::from(self.as_str())) + } +} + +impl PartialEq for rkyv::string::ArchivedString { + fn eq(&self, other: &SmallString) -> bool { + **other == **self + } +} + +impl PartialOrd for rkyv::string::ArchivedString { + fn partial_cmp(&self, other: &SmallString) -> Option<::core::cmp::Ordering> { + Some(self.as_str().cmp(other)) + } +} + +/// An [`schemars::JsonSchema`] implementation for [`SmallString`]. +#[cfg(feature = "schemars")] +impl schemars::JsonSchema for SmallString { + fn is_referenceable() -> bool { + String::is_referenceable() + } + + fn schema_name() -> String { + String::schema_name() + } + + fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + String::json_schema(_gen) + } +} diff --git a/crates/uv-trampoline-builder/src/lib.rs b/crates/uv-trampoline-builder/src/lib.rs index a5920fd2d054..2fb461fc8e5b 100644 --- a/crates/uv-trampoline-builder/src/lib.rs +++ b/crates/uv-trampoline-builder/src/lib.rs @@ -268,7 +268,7 @@ pub fn windows_script_launcher( launcher.extend_from_slice(&payload); launcher.extend_from_slice(python_path.as_bytes()); launcher.extend_from_slice( - &u32::try_from(python_path.as_bytes().len()) + &u32::try_from(python_path.len()) .expect("file path should be smaller than 4GB") .to_le_bytes(), ); @@ -300,7 +300,7 @@ pub fn windows_python_launcher( launcher.extend_from_slice(launcher_bin); launcher.extend_from_slice(python_path.as_bytes()); launcher.extend_from_slice( - &u32::try_from(python_path.as_bytes().len()) + &u32::try_from(python_path.len()) .expect("file path should be smaller than 4GB") .to_le_bytes(), ); @@ -377,7 +377,7 @@ mod test { fn get_script_launcher(shebang: &str, is_gui: bool) -> String { if is_gui { format!( - r##"{shebang} + r#"{shebang} # -*- coding: utf-8 -*- import re import sys @@ -394,11 +394,11 @@ def make_gui() -> None: if __name__ == "__main__": sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0]) sys.exit(make_gui()) -"## +"# ) } else { format!( - r##"{shebang} + r#"{shebang} # -*- coding: utf-8 -*- import re import sys @@ -412,7 +412,7 @@ def main_console() -> None: if __name__ == "__main__": sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0]) sys.exit(main_console()) -"## +"# ) } } diff --git a/crates/uv-trampoline/Cargo.lock b/crates/uv-trampoline/Cargo.lock index eaf671b0e217..ca87ceb981e0 100644 --- a/crates/uv-trampoline/Cargo.lock +++ b/crates/uv-trampoline/Cargo.lock @@ -426,18 +426,18 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" [[package]] name = "thiserror" -version = "2.0.9" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.9" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ "proc-macro2", "quote", diff --git a/crates/uv-types/src/hash.rs b/crates/uv-types/src/hash.rs index 2ac08effa3b5..e1420cdddcc2 100644 --- a/crates/uv-types/src/hash.rs +++ b/crates/uv-types/src/hash.rs @@ -5,7 +5,8 @@ use url::Url; use uv_configuration::HashCheckingMode; use uv_distribution_types::{ - DistributionMetadata, HashPolicy, Name, Resolution, UnresolvedRequirement, VersionId, + DistributionMetadata, HashGeneration, HashPolicy, Name, Resolution, UnresolvedRequirement, + VersionId, }; use uv_normalize::PackageName; use uv_pep440::Version; @@ -19,7 +20,7 @@ pub enum HashStrategy { #[default] None, /// Hashes should be generated (specifically, a SHA-256 hash), but not validated. - Generate, + Generate(HashGeneration), /// Hashes should be validated, if present, but ignored if absent. /// /// If necessary, hashes should be generated to ensure that the archive is valid. @@ -35,7 +36,7 @@ impl HashStrategy { pub fn get(&self, distribution: &T) -> HashPolicy { match self { Self::None => HashPolicy::None, - Self::Generate => HashPolicy::Generate, + Self::Generate(mode) => HashPolicy::Generate(*mode), Self::Verify(hashes) => { if let Some(hashes) = hashes.get(&distribution.version_id()) { HashPolicy::Validate(hashes.as_slice()) @@ -56,7 +57,7 @@ impl HashStrategy { pub fn get_package(&self, name: &PackageName, version: &Version) -> HashPolicy { match self { Self::None => HashPolicy::None, - Self::Generate => HashPolicy::Generate, + Self::Generate(mode) => HashPolicy::Generate(*mode), Self::Verify(hashes) => { if let Some(hashes) = hashes.get(&VersionId::from_registry(name.clone(), version.clone())) @@ -79,7 +80,7 @@ impl HashStrategy { pub fn get_url(&self, url: &Url) -> HashPolicy { match self { Self::None => HashPolicy::None, - Self::Generate => HashPolicy::Generate, + Self::Generate(mode) => HashPolicy::Generate(*mode), Self::Verify(hashes) => { if let Some(hashes) = hashes.get(&VersionId::from_url(url)) { HashPolicy::Validate(hashes.as_slice()) @@ -100,7 +101,7 @@ impl HashStrategy { pub fn allows_package(&self, name: &PackageName, version: &Version) -> bool { match self { Self::None => true, - Self::Generate => true, + Self::Generate(_) => true, Self::Verify(_) => true, Self::Require(hashes) => { hashes.contains_key(&VersionId::from_registry(name.clone(), version.clone())) @@ -112,7 +113,7 @@ impl HashStrategy { pub fn allows_url(&self, url: &Url) -> bool { match self { Self::None => true, - Self::Generate => true, + Self::Generate(_) => true, Self::Verify(_) => true, Self::Require(hashes) => hashes.contains_key(&VersionId::from_url(url)), } diff --git a/crates/uv-types/src/traits.rs b/crates/uv-types/src/traits.rs index fc88cab097fd..bfc2bf8a0e23 100644 --- a/crates/uv-types/src/traits.rs +++ b/crates/uv-types/src/traits.rs @@ -2,17 +2,18 @@ use std::fmt::{Debug, Display, Formatter}; use std::future::Future; use std::ops::Deref; use std::path::{Path, PathBuf}; -use uv_distribution_filename::DistFilename; use anyhow::Result; +use rustc_hash::FxHashSet; use uv_cache::Cache; use uv_configuration::{ BuildKind, BuildOptions, BuildOutput, ConfigSettings, LowerBound, SourceStrategy, }; +use uv_distribution_filename::DistFilename; use uv_distribution_types::{ - CachedDist, DependencyMetadata, IndexCapabilities, IndexLocations, InstalledDist, - IsBuildBackendError, Resolution, SourceDist, + CachedDist, DependencyMetadata, DistributionId, IndexCapabilities, IndexLocations, + InstalledDist, IsBuildBackendError, Resolution, SourceDist, }; use uv_git::GitResolver; use uv_pep508::PackageName; @@ -96,6 +97,7 @@ pub trait BuildContext { fn resolve<'a>( &'a self, requirements: &'a [Requirement], + build_stack: &'a BuildStack, ) -> impl Future> + 'a; /// Install the given set of package versions into the virtual environment. The environment must @@ -104,6 +106,7 @@ pub trait BuildContext { &'a self, resolution: &'a Resolution, venv: &'a PythonEnvironment, + build_stack: &'a BuildStack, ) -> impl Future, impl IsBuildBackendError>> + 'a; /// Set up a source distribution build by installing the required dependencies. A wrapper for @@ -123,6 +126,7 @@ pub trait BuildContext { sources: SourceStrategy, build_kind: BuildKind, build_output: BuildOutput, + build_stack: BuildStack, ) -> impl Future> + 'a; /// Build by calling directly into the uv build backend without PEP 517, if possible. @@ -244,3 +248,23 @@ impl Deref for AnyErrorBuild { &*self.0 } } + +/// The stack of packages being built. +#[derive(Debug, Clone, Default)] +pub struct BuildStack(FxHashSet); + +impl BuildStack { + /// Return an empty stack. + pub fn empty() -> Self { + Self(FxHashSet::default()) + } + + pub fn contains(&self, id: &DistributionId) -> bool { + self.0.contains(id) + } + + /// Push a package onto the stack. + pub fn insert(&mut self, id: DistributionId) -> bool { + self.0.insert(id) + } +} diff --git a/crates/uv-version/Cargo.toml b/crates/uv-version/Cargo.toml index 401e0a20c5eb..876e2d578ab0 100644 --- a/crates/uv-version/Cargo.toml +++ b/crates/uv-version/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-version" -version = "0.5.13" +version = "0.5.18" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } diff --git a/crates/uv-workspace/src/pyproject_mut.rs b/crates/uv-workspace/src/pyproject_mut.rs index cae147295100..10d7591ed19b 100644 --- a/crates/uv-workspace/src/pyproject_mut.rs +++ b/crates/uv-workspace/src/pyproject_mut.rs @@ -303,7 +303,7 @@ impl PyProjectTomlMut { .and_then(|url| Url::parse(url).ok()) .is_none_or(|url| CanonicalUrl::new(&url) != CanonicalUrl::new(index.url.url())) { - let mut formatted = Formatted::new(index.url.to_string()); + let mut formatted = Formatted::new(index.url.redacted().to_string()); if let Some(value) = table.get("url").and_then(Item::as_value) { if let Some(prefix) = value.decor().prefix() { formatted.decor_mut().set_prefix(prefix.clone()); @@ -1272,15 +1272,11 @@ fn reformat_array_multiline(deps: &mut Array) { .map(|(s, _)| s) .unwrap_or(decor_prefix); - // If there is no indentation, use four-space. - indentation_prefix = Some(if decor_prefix.is_empty() { - " ".to_string() - } else { - decor_prefix.to_string() - }); + indentation_prefix = (!decor_prefix.is_empty()).then_some(decor_prefix.to_string()); } - let indentation_prefix_str = format!("\n{}", indentation_prefix.as_ref().unwrap()); + let indentation_prefix_str = + format!("\n{}", indentation_prefix.as_deref().unwrap_or(" ")); for comment in find_comments(decor.prefix()).chain(find_comments(decor.suffix())) { match comment.comment_type { @@ -1306,7 +1302,7 @@ fn reformat_array_multiline(deps: &mut Array) { match comment.comment_type { CommentType::OwnLine => { let indentation_prefix_str = - format!("\n{}", indentation_prefix.as_ref().unwrap()); + format!("\n{}", indentation_prefix.as_deref().unwrap_or(" ")); rv.push_str(&indentation_prefix_str); } CommentType::EndOfLine => { diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml index 32adeb45bb3e..de27506f98d4 100644 --- a/crates/uv/Cargo.toml +++ b/crates/uv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv" -version = "0.5.13" +version = "0.5.18" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } @@ -142,6 +142,7 @@ performance = [ ] performance-memory-allocator = ["dep:uv-performance-memory-allocator"] performance-flate2-backend = ["dep:uv-performance-flate2-backend"] +tracing-durations-export = ["dep:tracing-durations-export", "uv-resolver/tracing-durations-export"] # Introduces a dependency on a local Python installation. python = [] diff --git a/crates/uv/src/bin/uv.rs b/crates/uv/src/bin/uv.rs index dd3a216e3394..261618ca826e 100644 --- a/crates/uv/src/bin/uv.rs +++ b/crates/uv/src/bin/uv.rs @@ -1,3 +1,8 @@ +// Don't optimize the alloc crate away due to it being otherwise unused. +// https://github.com/rust-lang/rust/issues/64402 +#[cfg(feature = "performance-memory-allocator")] +extern crate uv_performance_memory_allocator; + use std::process::ExitCode; use uv::main as uv_main; diff --git a/crates/uv/src/commands/build_frontend.rs b/crates/uv/src/commands/build_frontend.rs index be22faa5166b..a9a9fa718107 100644 --- a/crates/uv/src/commands/build_frontend.rs +++ b/crates/uv/src/commands/build_frontend.rs @@ -42,7 +42,7 @@ use uv_python::{ use uv_requirements::RequirementsSource; use uv_resolver::{ExcludeNewer, FlatIndex, RequiresPython}; use uv_settings::PythonInstallMirrors; -use uv_types::{AnyErrorBuild, BuildContext, BuildIsolation, HashStrategy}; +use uv_types::{AnyErrorBuild, BuildContext, BuildIsolation, BuildStack, HashStrategy}; use uv_workspace::{DiscoveryOptions, Workspace, WorkspaceError}; #[derive(Debug, Error)] @@ -921,6 +921,7 @@ async fn build_sdist( sources, BuildKind::Sdist, build_output, + BuildStack::default(), ) .await .map_err(|err| Error::BuildDispatch(err.into()))?; @@ -1018,6 +1019,7 @@ async fn build_wheel( sources, BuildKind::Wheel, build_output, + BuildStack::default(), ) .await .map_err(|err| Error::BuildDispatch(err.into()))?; diff --git a/crates/uv/src/commands/diagnostics.rs b/crates/uv/src/commands/diagnostics.rs index 4767cff090b3..2d375e6f7201 100644 --- a/crates/uv/src/commands/diagnostics.rs +++ b/crates/uv/src/commands/diagnostics.rs @@ -5,7 +5,9 @@ use owo_colors::OwoColorize; use rustc_hash::FxHashMap; use version_ranges::Ranges; -use uv_distribution_types::{DerivationChain, DerivationStep, Dist, DistErrorKind, Name}; +use uv_distribution_types::{ + DerivationChain, DerivationStep, Dist, DistErrorKind, Name, RequestedDist, +}; use uv_normalize::PackageName; use uv_pep440::Version; use uv_resolver::SentinelRange; @@ -78,7 +80,7 @@ impl OperationDiagnostic { chain, err, )) => { - dist_error(kind, dist, &chain, err); + requested_dist_error(kind, dist, &chain, err); None } pip::operations::Error::Requirements(uv_requirements::Error::Dist(kind, dist, err)) => { @@ -154,6 +156,51 @@ pub(crate) fn dist_error( anstream::eprint!("{report:?}"); } +/// Render a requested distribution failure (read, download or build) with a help message. +pub(crate) fn requested_dist_error( + kind: DistErrorKind, + dist: Box, + chain: &DerivationChain, + cause: Arc, +) { + #[derive(Debug, miette::Diagnostic, thiserror::Error)] + #[error("{kind} `{dist}`")] + #[diagnostic()] + struct Diagnostic { + kind: DistErrorKind, + dist: Box, + #[source] + cause: Arc, + #[help] + help: Option, + } + + let help = SUGGESTIONS + .get(dist.name()) + .map(|suggestion| { + format!( + "`{}` is often confused for `{}` Did you mean to install `{}` instead?", + dist.name().cyan(), + suggestion.cyan(), + suggestion.cyan(), + ) + }) + .or_else(|| { + if chain.is_empty() { + None + } else { + Some(format_chain(dist.name(), dist.version(), chain)) + } + }); + let report = miette::Report::new(Diagnostic { + kind, + dist, + cause, + help, + }); + anstream::eprint!("{report:?}"); +} + /// Render a [`uv_resolver::NoSolutionError`]. pub(crate) fn no_solution(err: &uv_resolver::NoSolutionError) { let report = miette::Report::msg(format!("{err}")).context(err.header()); diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs index fd86f068ad9d..d259750fee77 100644 --- a/crates/uv/src/commands/pip/compile.rs +++ b/crates/uv/src/commands/pip/compile.rs @@ -17,8 +17,8 @@ use uv_configuration::{ use uv_configuration::{KeyringProviderType, TargetTriple}; use uv_dispatch::{BuildDispatch, SharedState}; use uv_distribution_types::{ - DependencyMetadata, Index, IndexLocations, NameRequirementSpecification, Origin, - UnresolvedRequirementSpecification, Verbatim, + DependencyMetadata, HashGeneration, Index, IndexLocations, NameRequirementSpecification, + Origin, UnresolvedRequirementSpecification, Verbatim, }; use uv_fs::Simplified; use uv_install_wheel::linker::LinkMode; @@ -266,7 +266,7 @@ pub(crate) async fn pip_compile( // Generate, but don't enforce hashes for the requirements. let hasher = if generate_hashes { - HashStrategy::Generate + HashStrategy::Generate(HashGeneration::All) } else { HashStrategy::None }; diff --git a/crates/uv/src/commands/pip/latest.rs b/crates/uv/src/commands/pip/latest.rs index 1331c29f5bc1..abc028a0076b 100644 --- a/crates/uv/src/commands/pip/latest.rs +++ b/crates/uv/src/commands/pip/latest.rs @@ -21,7 +21,7 @@ pub(crate) struct LatestClient<'env> { pub(crate) requires_python: &'env RequiresPython, } -impl<'env> LatestClient<'env> { +impl LatestClient<'_> { /// Find the latest version of a package from an index. pub(crate) async fn find_latest( &self, diff --git a/crates/uv/src/commands/pip/operations.rs b/crates/uv/src/commands/pip/operations.rs index 0e854eb54eb7..34604c3506e3 100644 --- a/crates/uv/src/commands/pip/operations.rs +++ b/crates/uv/src/commands/pip/operations.rs @@ -6,6 +6,7 @@ use owo_colors::OwoColorize; use std::collections::{BTreeSet, HashSet}; use std::fmt::Write; use std::path::PathBuf; +use std::sync::Arc; use tracing::debug; use uv_tool::InstalledTools; @@ -138,7 +139,7 @@ pub(crate) async fn resolve( index, DistributionDatabase::new(client, build_dispatch, concurrency.downloads), ) - .with_reporter(ResolverReporter::from(printer)) + .with_reporter(Arc::new(ResolverReporter::from(printer))) .resolve(unnamed.into_iter()) .await?, ); @@ -152,7 +153,7 @@ pub(crate) async fn resolve( index, DistributionDatabase::new(client, build_dispatch, concurrency.downloads), ) - .with_reporter(ResolverReporter::from(printer)) + .with_reporter(Arc::new(ResolverReporter::from(printer))) .resolve(source_trees.iter().map(PathBuf::as_path)) .await?; @@ -221,7 +222,7 @@ pub(crate) async fn resolve( index, DistributionDatabase::new(client, build_dispatch, concurrency.downloads), ) - .with_reporter(ResolverReporter::from(printer)) + .with_reporter(Arc::new(ResolverReporter::from(printer))) .resolve(unnamed.into_iter()) .await?, ); @@ -251,7 +252,7 @@ pub(crate) async fn resolve( index, DistributionDatabase::new(client, build_dispatch, concurrency.downloads), ) - .with_reporter(ResolverReporter::from(printer)) + .with_reporter(Arc::new(ResolverReporter::from(printer))) .resolve(&resolver_env) .await? } @@ -297,7 +298,7 @@ pub(crate) async fn resolve( installed_packages, DistributionDatabase::new(client, build_dispatch, concurrency.downloads), )? - .with_reporter(reporter); + .with_reporter(Arc::new(reporter)); resolver.resolve().await? }; @@ -460,7 +461,9 @@ pub(crate) async fn install( build_options, DistributionDatabase::new(client, build_dispatch, concurrency.downloads), ) - .with_reporter(PrepareReporter::from(printer).with_length(remote.len() as u64)); + .with_reporter(Arc::new( + PrepareReporter::from(printer).with_length(remote.len() as u64), + )); let wheels = preparer .prepare(remote.clone(), in_flight, resolution) @@ -519,7 +522,9 @@ pub(crate) async fn install( .with_link_mode(link_mode) .with_cache(cache) .with_installer_metadata(installer_metadata) - .with_reporter(InstallReporter::from(printer).with_length(installs.len() as u64)) + .with_reporter(Arc::new( + InstallReporter::from(printer).with_length(installs.len() as u64), + )) // This technically can block the runtime, but we are on the main thread and // have no other running tasks at this point, so this lets us avoid spawning a blocking // task. diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index fb195a835f25..11a09b397e2e 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -2,6 +2,8 @@ use std::collections::hash_map::Entry; use std::fmt::Write; use std::io; use std::path::{Path, PathBuf}; +use std::str::FromStr; +use std::sync::Arc; use anyhow::{bail, Context, Result}; use itertools::Itertools; @@ -29,7 +31,7 @@ use uv_pypi_types::{redact_credentials, ParsedUrl, RequirementSource, VerbatimPa use uv_python::{Interpreter, PythonDownloads, PythonEnvironment, PythonPreference, PythonRequest}; use uv_requirements::{NamedRequirementsResolver, RequirementsSource, RequirementsSpecification}; use uv_resolver::FlatIndex; -use uv_scripts::{Pep723ItemRef, Pep723Script}; +use uv_scripts::{Pep723ItemRef, Pep723Metadata, Pep723Script}; use uv_settings::PythonInstallMirrors; use uv_types::{BuildIsolation, HashStrategy}; use uv_warnings::warn_user_once; @@ -96,11 +98,6 @@ pub(crate) async fn add( RequirementsSource::SetupCfg(_) => { bail!("Adding requirements from a `setup.cfg` is not supported in `uv add`"); } - RequirementsSource::RequirementsTxt(path) => { - if path == Path::new("-") { - bail!("Reading requirements from stdin is not supported in `uv add`"); - } - } _ => {} } } @@ -173,7 +170,7 @@ pub(crate) async fn add( .await? .into_interpreter(); - Target::Script(script, Box::new(interpreter)) + AddTarget::Script(script, Box::new(interpreter)) } else { // Find the project in the workspace. let project = if let Some(package) = package { @@ -221,7 +218,7 @@ pub(crate) async fn add( .await? .into_interpreter(); - Target::Project(project, Box::new(PythonTarget::Interpreter(interpreter))) + AddTarget::Project(project, Box::new(PythonTarget::Interpreter(interpreter))) } else { // Discover or create the virtual environment. let venv = project::get_or_init_environment( @@ -239,7 +236,7 @@ pub(crate) async fn add( ) .await?; - Target::Project(project, Box::new(PythonTarget::Environment(venv))) + AddTarget::Project(project, Box::new(PythonTarget::Environment(venv))) } }; @@ -387,7 +384,7 @@ pub(crate) async fn add( state.index(), DistributionDatabase::new(&client, &build_dispatch, concurrency.downloads), ) - .with_reporter(ResolverReporter::from(printer)) + .with_reporter(Arc::new(ResolverReporter::from(printer))) .resolve(unnamed.into_iter()) .await?, ); @@ -398,7 +395,7 @@ pub(crate) async fn add( // If any of the requirements are self-dependencies, bail. if matches!(dependency_type, DependencyType::Production) { - if let Target::Project(project, _) = &target { + if let AddTarget::Project(project, _) = &target { if let Some(project_name) = project.project_name() { for requirement in &requirements { if requirement.name == *project_name { @@ -426,10 +423,10 @@ pub(crate) async fn add( // Add the requirements to the `pyproject.toml` or script. let mut toml = match &target { - Target::Script(script, _) => { + AddTarget::Script(script, _) => { PyProjectTomlMut::from_toml(&script.metadata.raw, DependencyTarget::Script) } - Target::Project(project, _) => PyProjectTomlMut::from_toml( + AddTarget::Project(project, _) => PyProjectTomlMut::from_toml( &project.pyproject_toml().raw, DependencyTarget::PyProjectToml, ), @@ -442,10 +439,10 @@ pub(crate) async fn add( requirement.extras.dedup(); let (requirement, source) = match target { - Target::Script(_, _) | Target::Project(_, _) if raw_sources => { + AddTarget::Script(_, _) | AddTarget::Project(_, _) if raw_sources => { (uv_pep508::Requirement::from(requirement), None) } - Target::Script(ref script, _) => { + AddTarget::Script(ref script, _) => { let script_path = std::path::absolute(&script.path)?; let script_dir = script_path.parent().expect("script path has no parent"); resolve_requirement( @@ -459,7 +456,7 @@ pub(crate) async fn add( script_dir, )? } - Target::Project(ref project, _) => { + AddTarget::Project(ref project, _) => { let workspace = project .workspace() .packages() @@ -603,32 +600,17 @@ pub(crate) async fn add( let content = toml.to_string(); // Save the modified `pyproject.toml` or script. - let modified = match &target { - Target::Script(script, _) => { - if content == script.metadata.raw { - debug!("No changes to dependencies; skipping update"); - false - } else { - script.write(&content).await?; - true - } - } - Target::Project(project, _) => { - if content == *project.pyproject_toml().raw { - debug!("No changes to dependencies; skipping update"); - false - } else { - let pyproject_path = project.root().join("pyproject.toml"); - fs_err::write(pyproject_path, &content)?; - true - } - } - }; + let modified = target.write(&content)?; - let (project, environment) = match target { - Target::Project(project, environment) => (project, environment), - // If `--script`, exit early. There's no reason to lock and sync. - Target::Script(script, _) => { + // If `--frozen`, exit early. There's no reason to lock and sync, since we don't need a `uv.lock` + // to exist at all. + if frozen { + return Ok(ExitStatus::Success); + } + + // If we're modifying a script, and lockfile doesn't exist, don't create it. + if let AddTarget::Script(ref script, _) = target { + if !LockTarget::from(script).lock_path().is_file() { writeln!( printer.stderr(), "Updated `{}`", @@ -636,39 +618,20 @@ pub(crate) async fn add( )?; return Ok(ExitStatus::Success); } - }; - - // If `--frozen`, exit early. There's no reason to lock and sync, and we don't need a `uv.lock` - // to exist at all. - if frozen { - return Ok(ExitStatus::Success); } // Store the content prior to any modifications. - let project_root = project.root().to_path_buf(); - let workspace_root = project.workspace().install_path().clone(); - let existing_pyproject_toml = project.pyproject_toml().as_ref().to_vec(); - let existing_uv_lock = LockTarget::from(project.workspace()).read_bytes().await?; + let snapshot = target.snapshot().await?; // Update the `pypackage.toml` in-memory. - let project = project - .with_pyproject_toml(toml::from_str(&content).map_err(ProjectError::PyprojectTomlParse)?) - .ok_or(ProjectError::PyprojectTomlUpdate)?; + let target = target.update(&content)?; // Set the Ctrl-C handler to revert changes on exit. let _ = ctrlc::set_handler({ - let project_root = project_root.clone(); - let workspace_root = workspace_root.clone(); - let existing_pyproject_toml = existing_pyproject_toml.clone(); - let existing_uv_lock = existing_uv_lock.clone(); + let snapshot = snapshot.clone(); move || { if modified { - let _ = revert( - &project_root, - &workspace_root, - &existing_pyproject_toml, - existing_uv_lock.as_deref(), - ); + let _ = snapshot.revert(); } #[allow(clippy::exit, clippy::cast_possible_wrap)] @@ -681,10 +644,9 @@ pub(crate) async fn add( }); match lock_and_sync( - project, + target, &mut toml, &edits, - &environment, state, locked, &dependency_type, @@ -704,12 +666,7 @@ pub(crate) async fn add( Ok(()) => Ok(ExitStatus::Success), Err(err) => { if modified { - let _ = revert( - &project_root, - &workspace_root, - &existing_pyproject_toml, - existing_uv_lock.as_deref(), - ); + let _ = snapshot.revert(); } match err { ProjectError::Operation(err) => diagnostics::OperationDiagnostic::with_hint(format!("If you want to add the package regardless of the failed resolution, provide the `{}` flag to skip locking and syncing.", "--frozen".green())) @@ -724,10 +681,9 @@ pub(crate) async fn add( /// Re-lock and re-sync the project after a series of edits. #[allow(clippy::fn_params_excessive_bools)] async fn lock_and_sync( - mut project: VirtualProject, + mut target: AddTarget, toml: &mut PyProjectTomlMut, edits: &[DependencyEdit], - environment: &PythonTarget, state: SharedState, locked: bool, dependency_type: &DependencyType, @@ -742,15 +698,13 @@ async fn lock_and_sync( printer: Printer, preview: PreviewMode, ) -> Result<(), ProjectError> { - let mode = if locked { - LockMode::Locked(environment.interpreter()) - } else { - LockMode::Write(environment.interpreter()) - }; - let mut lock = project::lock::do_safe_lock( - mode, - project.workspace().into(), + if locked { + LockMode::Locked(target.interpreter()) + } else { + LockMode::Write(target.interpreter()) + }, + (&target).into(), settings.into(), LowerBound::default(), &state, @@ -847,17 +801,13 @@ async fn lock_and_sync( let content = toml.to_string(); // Write the updated `pyproject.toml` to disk. - fs_err::write(project.root().join("pyproject.toml"), &content)?; + target.write(&content)?; // Update the `pypackage.toml` in-memory. - project = project - .with_pyproject_toml( - toml::from_str(&content).map_err(ProjectError::PyprojectTomlParse)?, - ) - .ok_or(ProjectError::PyprojectTomlUpdate)?; + target = target.update(&content)?; // Invalidate the project metadata. - if let VirtualProject::Project(ref project) = project { + if let AddTarget::Project(VirtualProject::Project(ref project), _) = target { let url = Url::from_file_path(project.project_root()) .expect("project root is a valid URL"); let version_id = VersionId::from_url(&url); @@ -868,8 +818,12 @@ async fn lock_and_sync( // If the file was modified, we have to lock again, though the only expected change is // the addition of the minimum version specifiers. lock = project::lock::do_safe_lock( - mode, - project.workspace().into(), + if locked { + LockMode::Locked(target.interpreter()) + } else { + LockMode::Write(target.interpreter()) + }, + (&target).into(), settings.into(), LowerBound::default(), &state, @@ -887,7 +841,12 @@ async fn lock_and_sync( } } - let PythonTarget::Environment(venv) = environment else { + let AddTarget::Project(project, environment) = target else { + // If we're not adding to a project, exit early. + return Ok(()); + }; + + let PythonTarget::Environment(venv) = &*environment else { // If we're not syncing, exit early. return Ok(()); }; @@ -954,25 +913,6 @@ async fn lock_and_sync( Ok(()) } -/// Revert the changes to the `pyproject.toml` and `uv.lock`, if necessary. -fn revert( - project_root: &Path, - workspace_root: &Path, - pyproject_toml: &[u8], - uv_lock: Option<&[u8]>, -) -> Result<(), io::Error> { - debug!("Reverting changes to `pyproject.toml`"); - let () = fs_err::write(project_root.join("pyproject.toml"), pyproject_toml)?; - if let Some(uv_lock) = uv_lock.as_ref() { - debug!("Reverting changes to `uv.lock`"); - let () = fs_err::write(workspace_root.join("uv.lock"), uv_lock)?; - } else { - debug!("Removing `uv.lock`"); - let () = fs_err::remove_file(workspace_root.join("uv.lock"))?; - } - Ok(()) -} - /// Augment a user-provided requirement by attaching any specification data that was provided /// separately from the requirement itself (e.g., `--branch main`). fn augment_requirement( @@ -1082,9 +1022,27 @@ fn resolve_requirement( Ok((processed_requirement, source)) } +/// A Python [`Interpreter`] or [`PythonEnvironment`] for a project. +#[derive(Debug, Clone)] +#[allow(clippy::large_enum_variant)] +pub(super) enum PythonTarget { + Interpreter(Interpreter), + Environment(PythonEnvironment), +} + +impl PythonTarget { + /// Return the [`Interpreter`] for the project. + fn interpreter(&self) -> &Interpreter { + match self { + Self::Interpreter(interpreter) => interpreter, + Self::Environment(venv) => venv.interpreter(), + } + } +} + /// Represents the destination where dependencies are added, either to a project or a script. -#[derive(Debug)] -enum Target { +#[derive(Debug, Clone)] +pub(super) enum AddTarget { /// A PEP 723 script, with inline metadata. Script(Pep723Script, Box), @@ -1092,30 +1050,133 @@ enum Target { Project(VirtualProject, Box), } -impl Target { +impl<'lock> From<&'lock AddTarget> for LockTarget<'lock> { + fn from(value: &'lock AddTarget) -> Self { + match value { + AddTarget::Script(script, _) => Self::Script(script), + AddTarget::Project(project, _) => Self::Workspace(project.workspace()), + } + } +} + +impl AddTarget { /// Returns the [`Interpreter`] for the target. - fn interpreter(&self) -> &Interpreter { + pub(super) fn interpreter(&self) -> &Interpreter { match self { Self::Script(_, interpreter) => interpreter, Self::Project(_, venv) => venv.interpreter(), } } + + /// Write the updated content to the target. + /// + /// Returns `true` if the content was modified. + fn write(&self, content: &str) -> Result { + match self { + Self::Script(script, _) => { + if content == script.metadata.raw { + debug!("No changes to dependencies; skipping update"); + Ok(false) + } else { + script.write(content)?; + Ok(true) + } + } + Self::Project(project, _) => { + if content == project.pyproject_toml().raw { + debug!("No changes to dependencies; skipping update"); + Ok(false) + } else { + let pyproject_path = project.root().join("pyproject.toml"); + fs_err::write(pyproject_path, content)?; + Ok(true) + } + } + } + } + + /// Update the target in-memory to incorporate the new content. + #[allow(clippy::result_large_err)] + fn update(self, content: &str) -> Result { + match self { + Self::Script(mut script, interpreter) => { + script.metadata = Pep723Metadata::from_str(content) + .map_err(ProjectError::Pep723ScriptTomlParse)?; + Ok(Self::Script(script, interpreter)) + } + Self::Project(project, venv) => { + let project = project + .with_pyproject_toml( + toml::from_str(content).map_err(ProjectError::PyprojectTomlParse)?, + ) + .ok_or(ProjectError::PyprojectTomlUpdate)?; + Ok(Self::Project(project, venv)) + } + } + } + + /// Take a snapshot of the target. + async fn snapshot(&self) -> Result { + // Read the lockfile into memory. + let target = match self { + Self::Script(script, _) => LockTarget::from(script), + Self::Project(project, _) => LockTarget::Workspace(project.workspace()), + }; + let lock = target.read_bytes().await?; + + // Clone the target. + match self { + Self::Script(script, _) => Ok(AddTargetSnapshot::Script(script.clone(), lock)), + Self::Project(project, _) => Ok(AddTargetSnapshot::Project(project.clone(), lock)), + } + } } -/// A Python [`Interpreter`] or [`PythonEnvironment`] for a project. -#[derive(Debug)] -#[allow(clippy::large_enum_variant)] -enum PythonTarget { - Interpreter(Interpreter), - Environment(PythonEnvironment), +#[derive(Debug, Clone)] +enum AddTargetSnapshot { + Script(Pep723Script, Option>), + Project(VirtualProject, Option>), } -impl PythonTarget { - /// Return the [`Interpreter`] for the project. - fn interpreter(&self) -> &Interpreter { +impl AddTargetSnapshot { + /// Write the snapshot back to disk (e.g., to a `pyproject.toml` and `uv.lock`). + fn revert(&self) -> Result<(), io::Error> { match self { - Self::Interpreter(interpreter) => interpreter, - Self::Environment(venv) => venv.interpreter(), + Self::Script(script, lock) => { + // Write the PEP 723 script back to disk. + debug!("Reverting changes to PEP 723 script block"); + script.write(&script.metadata.raw)?; + + // Write the lockfile back to disk. + let target = LockTarget::from(script); + if let Some(lock) = lock { + debug!("Reverting changes to `uv.lock`"); + fs_err::write(target.lock_path(), lock)?; + } else { + debug!("Removing `uv.lock`"); + fs_err::remove_file(target.lock_path())?; + } + Ok(()) + } + Self::Project(project, lock) => { + // Write the `pyproject.toml` back to disk. + debug!("Reverting changes to `pyproject.toml`"); + fs_err::write( + project.root().join("pyproject.toml"), + project.pyproject_toml().as_ref(), + )?; + + // Write the lockfile back to disk. + let target = LockTarget::from(project.workspace()); + if let Some(lock) = lock { + debug!("Reverting changes to `uv.lock`"); + fs_err::write(target.lock_path(), lock)?; + } else { + debug!("Removing `uv.lock`"); + fs_err::remove_file(target.lock_path())?; + } + Ok(()) + } } } } diff --git a/crates/uv/src/commands/project/environment.rs b/crates/uv/src/commands/project/environment.rs index e4dd28ec2c01..0654ec6bc953 100644 --- a/crates/uv/src/commands/project/environment.rs +++ b/crates/uv/src/commands/project/environment.rs @@ -1,6 +1,7 @@ use tracing::debug; use crate::commands::pip::loggers::{InstallLogger, ResolveLogger}; +use crate::commands::project::install_target::InstallTarget; use crate::commands::project::{ resolve_environment, sync_environment, EnvironmentSpecification, ProjectError, }; @@ -9,10 +10,13 @@ use crate::settings::ResolverInstallerSettings; use uv_cache::{Cache, CacheBucket}; use uv_cache_key::{cache_digest, hash_digest}; use uv_client::Connectivity; -use uv_configuration::{Concurrency, PreviewMode, TrustedHost}; +use uv_configuration::{ + Concurrency, DevGroupsManifest, ExtrasSpecification, InstallOptions, PreviewMode, TrustedHost, +}; use uv_dispatch::SharedState; use uv_distribution_types::{Name, Resolution}; use uv_python::{Interpreter, PythonEnvironment}; +use uv_resolver::Installable; /// A [`PythonEnvironment`] stored in the cache. #[derive(Debug)] @@ -25,11 +29,10 @@ impl From for PythonEnvironment { } impl CachedEnvironment { - /// Get or create an [`CachedEnvironment`] based on a given set of requirements and a base - /// interpreter. - pub(crate) async fn get_or_create( + /// Get or create an [`CachedEnvironment`] based on a given set of requirements. + pub(crate) async fn from_spec( spec: EnvironmentSpecification<'_>, - interpreter: Interpreter, + interpreter: &Interpreter, settings: &ResolverInstallerSettings, state: &SharedState, resolve: Box, @@ -43,21 +46,7 @@ impl CachedEnvironment { printer: Printer, preview: PreviewMode, ) -> Result { - // When caching, always use the base interpreter, rather than that of the virtual - // environment. - let interpreter = if let Some(interpreter) = interpreter.to_base_interpreter(cache)? { - debug!( - "Caching via base interpreter: `{}`", - interpreter.sys_executable().display() - ); - interpreter - } else { - debug!( - "Caching via interpreter: `{}`", - interpreter.sys_executable().display() - ); - interpreter - }; + let interpreter = Self::base_interpreter(interpreter, cache)?; // Resolve the requirements with the interpreter. let resolution = Resolution::from( @@ -78,6 +67,93 @@ impl CachedEnvironment { .await?, ); + Self::from_resolution( + resolution, + interpreter, + settings, + state, + install, + installer_metadata, + connectivity, + concurrency, + native_tls, + allow_insecure_host, + cache, + printer, + preview, + ) + .await + } + + /// Get or create an [`CachedEnvironment`] based on a given [`InstallTarget`]. + pub(crate) async fn from_lock( + target: InstallTarget<'_>, + extras: &ExtrasSpecification, + dev: &DevGroupsManifest, + install_options: InstallOptions, + settings: &ResolverInstallerSettings, + interpreter: &Interpreter, + state: &SharedState, + install: Box, + installer_metadata: bool, + connectivity: Connectivity, + concurrency: Concurrency, + native_tls: bool, + allow_insecure_host: &[TrustedHost], + cache: &Cache, + printer: Printer, + preview: PreviewMode, + ) -> Result { + let interpreter = Self::base_interpreter(interpreter, cache)?; + + // Determine the tags, markers, and interpreter to use for resolution. + let tags = interpreter.tags()?; + let marker_env = interpreter.resolver_marker_environment(); + + // Read the lockfile. + let resolution = target.to_resolution( + &marker_env, + tags, + extras, + dev, + &settings.build_options, + &install_options, + )?; + + Self::from_resolution( + resolution, + interpreter, + settings, + state, + install, + installer_metadata, + connectivity, + concurrency, + native_tls, + allow_insecure_host, + cache, + printer, + preview, + ) + .await + } + + /// Get or create an [`CachedEnvironment`] based on a given [`Resolution`]. + pub(crate) async fn from_resolution( + resolution: Resolution, + interpreter: Interpreter, + settings: &ResolverInstallerSettings, + state: &SharedState, + install: Box, + installer_metadata: bool, + connectivity: Connectivity, + concurrency: Concurrency, + native_tls: bool, + allow_insecure_host: &[TrustedHost], + cache: &Cache, + printer: Printer, + preview: PreviewMode, + ) -> Result { // Hash the resolution by hashing the generated lockfile. // TODO(charlie): If the resolution contains any mutable metadata (like a path or URL // dependency), skip this step. @@ -144,4 +220,28 @@ impl CachedEnvironment { pub(crate) fn into_interpreter(self) -> Interpreter { self.0.into_interpreter() } + + /// Return the [`Interpreter`] to use for the cached environment, based on a given + /// [`Interpreter`]. + /// + /// When caching, always use the base interpreter, rather than that of the virtual + /// environment. + fn base_interpreter( + interpreter: &Interpreter, + cache: &Cache, + ) -> Result { + if let Some(interpreter) = interpreter.to_base_interpreter(cache)? { + debug!( + "Caching via base interpreter: `{}`", + interpreter.sys_executable().display() + ); + Ok(interpreter) + } else { + debug!( + "Caching via interpreter: `{}`", + interpreter.sys_executable().display() + ); + Ok(interpreter.clone()) + } + } } diff --git a/crates/uv/src/commands/project/export.rs b/crates/uv/src/commands/project/export.rs index eb1d0ebe3bb8..64b70d466915 100644 --- a/crates/uv/src/commands/project/export.rs +++ b/crates/uv/src/commands/project/export.rs @@ -16,19 +16,39 @@ use uv_dispatch::SharedState; use uv_normalize::PackageName; use uv_python::{PythonDownloads, PythonPreference, PythonRequest}; use uv_resolver::RequirementsTxtExport; +use uv_scripts::{Pep723ItemRef, Pep723Script}; use uv_workspace::{DiscoveryOptions, MemberDiscovery, VirtualProject, Workspace}; use crate::commands::pip::loggers::DefaultResolveLogger; use crate::commands::project::install_target::InstallTarget; use crate::commands::project::lock::{do_safe_lock, LockMode}; +use crate::commands::project::lock_target::LockTarget; use crate::commands::project::{ default_dependency_groups, detect_conflicts, DependencyGroupsTarget, ProjectError, - ProjectInterpreter, + ProjectInterpreter, ScriptInterpreter, }; use crate::commands::{diagnostics, ExitStatus, OutputWriter}; use crate::printer::Printer; use crate::settings::ResolverSettings; +#[derive(Debug, Clone)] +enum ExportTarget { + /// A PEP 723 script, with inline metadata. + Script(Pep723Script), + + /// A project with a `pyproject.toml`. + Project(VirtualProject), +} + +impl<'lock> From<&'lock ExportTarget> for LockTarget<'lock> { + fn from(value: &'lock ExportTarget) -> Self { + match value { + ExportTarget::Script(script) => Self::Script(script), + ExportTarget::Project(project) => Self::Workspace(project.workspace()), + } + } +} + /// Export the project's `uv.lock` in an alternate format. #[allow(clippy::fn_params_excessive_bools)] pub(crate) async fn export( @@ -46,6 +66,7 @@ pub(crate) async fn export( locked: bool, frozen: bool, include_header: bool, + script: Option, python: Option, install_mirrors: PythonInstallMirrors, settings: ResolverSettings, @@ -61,74 +82,108 @@ pub(crate) async fn export( printer: Printer, preview: PreviewMode, ) -> Result { - // Identify the project. - let project = if frozen { - VirtualProject::discover( - project_dir, - &DiscoveryOptions { - members: MemberDiscovery::None, - ..DiscoveryOptions::default() - }, - ) - .await? - } else if let Some(package) = package.as_ref() { - VirtualProject::Project( - Workspace::discover(project_dir, &DiscoveryOptions::default()) - .await? - .with_current_project(package.clone()) - .with_context(|| format!("Package `{package}` not found in workspace"))?, - ) + // Identify the target. + let target = if let Some(script) = script { + ExportTarget::Script(script) } else { - VirtualProject::discover(project_dir, &DiscoveryOptions::default()).await? + let project = if frozen { + VirtualProject::discover( + project_dir, + &DiscoveryOptions { + members: MemberDiscovery::None, + ..DiscoveryOptions::default() + }, + ) + .await? + } else if let Some(package) = package.as_ref() { + VirtualProject::Project( + Workspace::discover(project_dir, &DiscoveryOptions::default()) + .await? + .with_current_project(package.clone()) + .with_context(|| format!("Package `{package}` not found in workspace"))?, + ) + } else { + VirtualProject::discover(project_dir, &DiscoveryOptions::default()).await? + }; + ExportTarget::Project(project) }; // Validate that any referenced dependency groups are defined in the workspace. if !frozen { - let target = match &project { - VirtualProject::Project(project) => { + let target = match &target { + ExportTarget::Project(VirtualProject::Project(project)) => { if all_packages { DependencyGroupsTarget::Workspace(project.workspace()) } else { DependencyGroupsTarget::Project(project) } } - VirtualProject::NonProject(workspace) => DependencyGroupsTarget::Workspace(workspace), + ExportTarget::Project(VirtualProject::NonProject(workspace)) => { + DependencyGroupsTarget::Workspace(workspace) + } + ExportTarget::Script(_) => DependencyGroupsTarget::Script, }; target.validate(&dev)?; } // Determine the default groups to include. - let defaults = default_dependency_groups(project.pyproject_toml())?; + let defaults = match &target { + ExportTarget::Project(project) => default_dependency_groups(project.pyproject_toml())?, + ExportTarget::Script(_) => vec![], + }; let dev = dev.with_defaults(defaults); + // Find an interpreter for the project, unless `--frozen` is set. + let interpreter = if frozen { + None + } else { + Some(match &target { + ExportTarget::Script(script) => ScriptInterpreter::discover( + Pep723ItemRef::Script(script), + python.as_deref().map(PythonRequest::parse), + python_preference, + python_downloads, + connectivity, + native_tls, + allow_insecure_host, + &install_mirrors, + no_config, + cache, + printer, + ) + .await? + .into_interpreter(), + ExportTarget::Project(project) => ProjectInterpreter::discover( + project.workspace(), + project_dir, + python.as_deref().map(PythonRequest::parse), + python_preference, + python_downloads, + connectivity, + native_tls, + allow_insecure_host, + &install_mirrors, + no_config, + cache, + printer, + ) + .await? + .into_interpreter(), + }) + }; + // Determine the lock mode. - let interpreter; let mode = if frozen { LockMode::Frozen + } else if locked { + LockMode::Locked(interpreter.as_ref().unwrap()) + } else if matches!(target, ExportTarget::Script(_)) + && !LockTarget::from(&target).lock_path().is_file() + { + // If we're locking a script, avoid creating a lockfile if it doesn't already exist. + LockMode::DryRun(interpreter.as_ref().unwrap()) } else { - // Find an interpreter for the project - interpreter = ProjectInterpreter::discover( - project.workspace(), - project_dir, - python.as_deref().map(PythonRequest::parse), - python_preference, - python_downloads, - connectivity, - native_tls, - allow_insecure_host, - &install_mirrors, - no_config, - cache, - printer, - ) - .await? - .into_interpreter(); - - if locked { - LockMode::Locked(&interpreter) - } else { - LockMode::Write(&interpreter) - } + LockMode::Write(interpreter.as_ref().unwrap()) }; // Initialize any shared state. @@ -137,7 +192,7 @@ pub(crate) async fn export( // Lock the project. let lock = match do_safe_lock( mode, - project.workspace().into(), + (&target).into(), settings.as_ref(), LowerBound::Warn, &state, @@ -165,8 +220,8 @@ pub(crate) async fn export( detect_conflicts(&lock, &extras, &dev)?; // Identify the installation target. - let target = match &project { - VirtualProject::Project(project) => { + let target = match &target { + ExportTarget::Project(VirtualProject::Project(project)) => { if all_packages { InstallTarget::Workspace { workspace: project.workspace(), @@ -187,7 +242,7 @@ pub(crate) async fn export( } } } - VirtualProject::NonProject(workspace) => { + ExportTarget::Project(VirtualProject::NonProject(workspace)) => { if all_packages { InstallTarget::NonProjectWorkspace { workspace, @@ -207,6 +262,10 @@ pub(crate) async fn export( } } } + ExportTarget::Script(script) => InstallTarget::Script { + script, + lock: &lock, + }, }; // Write the resolved dependencies to the output channel. diff --git a/crates/uv/src/commands/project/init.rs b/crates/uv/src/commands/project/init.rs index 708a7dc0423a..75f8008a6a88 100644 --- a/crates/uv/src/commands/project/init.rs +++ b/crates/uv/src/commands/project/init.rs @@ -121,7 +121,10 @@ pub(crate) async fn init( .and_then(|path| path.to_str()) .context("Missing directory name")?; - PackageName::new(name.to_string())? + // Pre-normalize the package name by removing any leading or trailing + // whitespace, and replacing any internal whitespace with hyphens. + let name = name.trim().replace(' ', "-"); + PackageName::new(name)? } }; @@ -1003,7 +1006,7 @@ fn pyproject_build_backend_prerequisites( if !build_file.try_exists()? { fs_err::write( build_file, - indoc::formatdoc! {r#" + indoc::formatdoc! {r" cmake_minimum_required(VERSION 3.15) project(${{SKBUILD_PROJECT_NAME}} LANGUAGES CXX) @@ -1012,7 +1015,7 @@ fn pyproject_build_backend_prerequisites( pybind11_add_module(_core MODULE src/main.cpp) install(TARGETS _core DESTINATION ${{SKBUILD_PROJECT_NAME}}) - "#}, + "}, )?; } } @@ -1049,25 +1052,25 @@ fn generate_package_scripts( // Python script for binary-based packaged apps or libs let binary_call_script = if is_lib { - indoc::formatdoc! {r#" + indoc::formatdoc! {r" from {module_name}._core import hello_from_bin + def hello() -> str: return hello_from_bin() - "#} + "} } else { - indoc::formatdoc! {r#" + indoc::formatdoc! {r" from {module_name}._core import hello_from_bin + def main() -> None: print(hello_from_bin()) - "#} + "} }; // .pyi file for binary script let pyi_contents = indoc::indoc! {r" - from __future__ import annotations - def hello_from_bin() -> str: ... "}; diff --git a/crates/uv/src/commands/project/install_target.rs b/crates/uv/src/commands/project/install_target.rs index 69999d4d77cf..78d88640960f 100644 --- a/crates/uv/src/commands/project/install_target.rs +++ b/crates/uv/src/commands/project/install_target.rs @@ -1,9 +1,14 @@ +use std::borrow::Cow; use std::path::Path; +use std::str::FromStr; use itertools::Either; use uv_normalize::PackageName; +use uv_pypi_types::{LenientRequirement, VerbatimParsedUrl}; use uv_resolver::{Installable, Lock, Package}; +use uv_scripts::Pep723Script; +use uv_workspace::pyproject::{DependencyGroupSpecifier, Source, Sources, ToolUvSources}; use uv_workspace::Workspace; /// A target that can be installed from a lockfile. @@ -25,6 +30,11 @@ pub(crate) enum InstallTarget<'lock> { workspace: &'lock Workspace, lock: &'lock Lock, }, + /// A PEP 723 script. + Script { + script: &'lock Pep723Script, + lock: &'lock Lock, + }, } impl<'lock> Installable<'lock> for InstallTarget<'lock> { @@ -33,6 +43,7 @@ impl<'lock> Installable<'lock> for InstallTarget<'lock> { Self::Project { workspace, .. } => workspace.install_path(), Self::Workspace { workspace, .. } => workspace.install_path(), Self::NonProjectWorkspace { workspace, .. } => workspace.install_path(), + Self::Script { script, .. } => script.path.parent().unwrap(), } } @@ -41,24 +52,28 @@ impl<'lock> Installable<'lock> for InstallTarget<'lock> { Self::Project { lock, .. } => lock, Self::Workspace { lock, .. } => lock, Self::NonProjectWorkspace { lock, .. } => lock, + Self::Script { lock, .. } => lock, } } fn roots(&self) -> impl Iterator { match self { - Self::Project { name, .. } => Either::Right(Either::Left(std::iter::once(*name))), - Self::NonProjectWorkspace { lock, .. } => Either::Left(lock.members().iter()), + Self::Project { name, .. } => Either::Left(Either::Left(std::iter::once(*name))), + Self::NonProjectWorkspace { lock, .. } => { + Either::Left(Either::Right(lock.members().iter())) + } Self::Workspace { lock, .. } => { // Identify the workspace members. // // The members are encoded directly in the lockfile, unless the workspace contains a // single member at the root, in which case, we identify it by its source. if lock.members().is_empty() { - Either::Right(Either::Right(lock.root().into_iter().map(Package::name))) + Either::Right(Either::Left(lock.root().into_iter().map(Package::name))) } else { - Either::Left(lock.members().iter()) + Either::Left(Either::Right(lock.members().iter())) } } + Self::Script { .. } => Either::Right(Either::Right(std::iter::empty())), } } @@ -67,17 +82,120 @@ impl<'lock> Installable<'lock> for InstallTarget<'lock> { Self::Project { name, .. } => Some(name), Self::Workspace { .. } => None, Self::NonProjectWorkspace { .. } => None, + Self::Script { .. } => None, } } } impl<'lock> InstallTarget<'lock> { - /// Return the [`Workspace`] of the target. - pub(crate) fn workspace(&self) -> &'lock Workspace { + /// Return an iterator over all [`Sources`] defined by the target. + pub(crate) fn sources(&self) -> impl Iterator { + match self { + Self::Project { workspace, .. } + | Self::Workspace { workspace, .. } + | Self::NonProjectWorkspace { workspace, .. } => { + Either::Left(workspace.sources().values().flat_map(Sources::iter).chain( + workspace.packages().values().flat_map(|member| { + member + .pyproject_toml() + .tool + .as_ref() + .and_then(|tool| tool.uv.as_ref()) + .and_then(|uv| uv.sources.as_ref()) + .map(ToolUvSources::inner) + .into_iter() + .flat_map(|sources| sources.values().flat_map(Sources::iter)) + }), + )) + } + Self::Script { script, .. } => { + Either::Right(script.sources().values().flat_map(Sources::iter)) + } + } + } + + /// Return an iterator over all requirements defined by the target. + pub(crate) fn requirements( + &self, + ) -> impl Iterator>> { match self { - Self::Project { workspace, .. } => workspace, - Self::Workspace { workspace, .. } => workspace, - Self::NonProjectWorkspace { workspace, .. } => workspace, + Self::Project { workspace, .. } + | Self::Workspace { workspace, .. } + | Self::NonProjectWorkspace { workspace, .. } => { + Either::Left( + // Iterate over the non-member requirements in the workspace. + workspace + .requirements() + .into_iter() + .map(Cow::Owned) + .chain(workspace.dependency_groups().ok().into_iter().flat_map( + |dependency_groups| { + dependency_groups.into_values().flatten().map(Cow::Owned) + }, + )) + .chain(workspace.packages().values().flat_map(|member| { + // Iterate over all dependencies in each member. + let dependencies = member + .pyproject_toml() + .project + .as_ref() + .and_then(|project| project.dependencies.as_ref()) + .into_iter() + .flatten(); + let optional_dependencies = member + .pyproject_toml() + .project + .as_ref() + .and_then(|project| project.optional_dependencies.as_ref()) + .into_iter() + .flat_map(|optional| optional.values()) + .flatten(); + let dependency_groups = member + .pyproject_toml() + .dependency_groups + .as_ref() + .into_iter() + .flatten() + .flat_map(|(_, dependencies)| { + dependencies.iter().filter_map(|specifier| { + if let DependencyGroupSpecifier::Requirement(requirement) = + specifier + { + Some(requirement) + } else { + None + } + }) + }); + let dev_dependencies = member + .pyproject_toml() + .tool + .as_ref() + .and_then(|tool| tool.uv.as_ref()) + .and_then(|uv| uv.dev_dependencies.as_ref()) + .into_iter() + .flatten(); + dependencies + .chain(optional_dependencies) + .chain(dependency_groups) + .filter_map(|requires_dist| { + LenientRequirement::::from_str(requires_dist) + .map(uv_pep508::Requirement::from) + .map(Cow::Owned) + .ok() + }) + .chain(dev_dependencies.map(Cow::Borrowed)) + })), + ) + } + Self::Script { script, .. } => Either::Right( + script + .metadata + .dependencies + .iter() + .flatten() + .map(Cow::Borrowed), + ), } } } diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index 364a35f8d43a..a58d4c959f02 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -3,6 +3,7 @@ use std::collections::{BTreeMap, BTreeSet}; use std::fmt::Write; use std::path::Path; +use std::sync::Arc; use owo_colors::OwoColorize; use rustc_hash::{FxBuildHasher, FxHashMap}; @@ -17,7 +18,7 @@ use uv_configuration::{ use uv_dispatch::{BuildDispatch, SharedState}; use uv_distribution::DistributionDatabase; use uv_distribution_types::{ - DependencyMetadata, Index, IndexLocations, NameRequirementSpecification, + DependencyMetadata, HashGeneration, Index, IndexLocations, NameRequirementSpecification, UnresolvedRequirementSpecification, }; use uv_git::ResolvedRepositoryReference; @@ -31,6 +32,7 @@ use uv_resolver::{ FlatIndex, InMemoryIndex, Lock, Options, OptionsBuilder, PythonRequirement, RequiresPython, ResolverEnvironment, ResolverManifest, SatisfiesResult, UniversalMarker, }; +use uv_scripts::{Pep723ItemRef, Pep723Script}; use uv_settings::PythonInstallMirrors; use uv_types::{BuildContext, BuildIsolation, EmptyInstalledPackages, HashStrategy}; use uv_warnings::{warn_user, warn_user_once}; @@ -38,7 +40,7 @@ use uv_workspace::{DiscoveryOptions, Workspace, WorkspaceMember}; use crate::commands::pip::loggers::{DefaultResolveLogger, ResolveLogger, SummaryResolveLogger}; use crate::commands::project::lock_target::LockTarget; -use crate::commands::project::{ProjectError, ProjectInterpreter}; +use crate::commands::project::{ProjectError, ProjectInterpreter, ScriptInterpreter}; use crate::commands::reporters::ResolverReporter; use crate::commands::{diagnostics, pip, ExitStatus}; use crate::printer::Printer; @@ -79,6 +81,7 @@ pub(crate) async fn lock( python: Option, install_mirrors: PythonInstallMirrors, settings: ResolverSettings, + script: Option, python_preference: PythonPreference, python_downloads: PythonDownloads, connectivity: Connectivity, @@ -91,29 +94,52 @@ pub(crate) async fn lock( preview: PreviewMode, ) -> anyhow::Result { // Find the project requirements. - let workspace = Workspace::discover(project_dir, &DiscoveryOptions::default()).await?; + let workspace; + let target = if let Some(script) = script.as_ref() { + LockTarget::Script(script) + } else { + workspace = Workspace::discover(project_dir, &DiscoveryOptions::default()).await?; + LockTarget::Workspace(&workspace) + }; // Determine the lock mode. let interpreter; let mode = if frozen { LockMode::Frozen } else { - interpreter = ProjectInterpreter::discover( - &workspace, - project_dir, - python.as_deref().map(PythonRequest::parse), - python_preference, - python_downloads, - connectivity, - native_tls, - allow_insecure_host, - &install_mirrors, - no_config, - cache, - printer, - ) - .await? - .into_interpreter(); + interpreter = match target { + LockTarget::Workspace(workspace) => ProjectInterpreter::discover( + workspace, + project_dir, + python.as_deref().map(PythonRequest::parse), + python_preference, + python_downloads, + connectivity, + native_tls, + allow_insecure_host, + &install_mirrors, + no_config, + cache, + printer, + ) + .await? + .into_interpreter(), + LockTarget::Script(script) => ScriptInterpreter::discover( + Pep723ItemRef::Script(script), + python.as_deref().map(PythonRequest::parse), + python_preference, + python_downloads, + connectivity, + native_tls, + allow_insecure_host, + &install_mirrors, + no_config, + cache, + printer, + ) + .await? + .into_interpreter(), + }; if locked { LockMode::Locked(&interpreter) @@ -130,7 +156,7 @@ pub(crate) async fn lock( // Perform the lock operation. match do_safe_lock( mode, - (&workspace).into(), + target, settings.as_ref(), LowerBound::Warn, &state, @@ -472,7 +498,7 @@ async fn do_lock( .index_strategy(index_strategy) .build_options(build_options.clone()) .build(); - let hasher = HashStrategy::Generate; + let hasher = HashStrategy::Generate(HashGeneration::Url); // TODO(charlie): These are all default values. We should consider whether we want to make them // optional on the downstream APIs. @@ -625,7 +651,7 @@ async fn do_lock( // Resolve the requirements. let resolution = pip::operations::resolve( ExtrasResolver::new(&hasher, state.index(), database) - .with_reporter(ResolverReporter::from(printer)) + .with_reporter(Arc::new(ResolverReporter::from(printer))) .resolve(target.members_requirements()) .await .map_err(|err| ProjectError::Operation(err.into()))? diff --git a/crates/uv/src/commands/project/lock_target.rs b/crates/uv/src/commands/project/lock_target.rs index 6b1dee8271e8..ba3e9727b88c 100644 --- a/crates/uv/src/commands/project/lock_target.rs +++ b/crates/uv/src/commands/project/lock_target.rs @@ -1,12 +1,16 @@ use std::collections::BTreeMap; use std::path::{Path, PathBuf}; +use itertools::Either; + use uv_configuration::{LowerBound, SourceStrategy}; +use uv_distribution::LoweredRequirement; use uv_distribution_types::IndexLocations; use uv_normalize::{GroupName, PackageName}; use uv_pep508::RequirementOrigin; use uv_pypi_types::{Conflicts, Requirement, SupportedEnvironments, VerbatimParsedUrl}; use uv_resolver::{Lock, LockVersion, RequiresPython, VERSION}; +use uv_scripts::Pep723Script; use uv_workspace::dependency_groups::DependencyGroupError; use uv_workspace::{Workspace, WorkspaceMember}; @@ -16,6 +20,7 @@ use crate::commands::project::{find_requires_python, ProjectError}; #[derive(Debug, Copy, Clone)] pub(crate) enum LockTarget<'lock> { Workspace(&'lock Workspace), + Script(&'lock Pep723Script), } impl<'lock> From<&'lock Workspace> for LockTarget<'lock> { @@ -24,12 +29,19 @@ impl<'lock> From<&'lock Workspace> for LockTarget<'lock> { } } +impl<'lock> From<&'lock Pep723Script> for LockTarget<'lock> { + fn from(script: &'lock Pep723Script) -> Self { + LockTarget::Script(script) + } +} + impl<'lock> LockTarget<'lock> { /// Return the set of requirements that are attached to the target directly, as opposed to being /// attached to any members within the target. pub(crate) fn requirements(self) -> Vec> { match self { Self::Workspace(workspace) => workspace.requirements(), + Self::Script(script) => script.metadata.dependencies.clone().unwrap_or_default(), } } @@ -37,6 +49,16 @@ impl<'lock> LockTarget<'lock> { pub(crate) fn overrides(self) -> Vec> { match self { Self::Workspace(workspace) => workspace.overrides(), + Self::Script(script) => script + .metadata + .tool + .as_ref() + .and_then(|tool| tool.uv.as_ref()) + .and_then(|uv| uv.override_dependencies.as_ref()) + .into_iter() + .flatten() + .cloned() + .collect(), } } @@ -44,6 +66,16 @@ impl<'lock> LockTarget<'lock> { pub(crate) fn constraints(self) -> Vec> { match self { Self::Workspace(workspace) => workspace.constraints(), + Self::Script(script) => script + .metadata + .tool + .as_ref() + .and_then(|tool| tool.uv.as_ref()) + .and_then(|uv| uv.constraint_dependencies.as_ref()) + .into_iter() + .flatten() + .cloned() + .collect(), } } @@ -57,20 +89,23 @@ impl<'lock> LockTarget<'lock> { > { match self { Self::Workspace(workspace) => workspace.dependency_groups(), + Self::Script(_) => Ok(BTreeMap::new()), } } /// Returns the set of all members within the target. pub(crate) fn members_requirements(self) -> impl Iterator + 'lock { match self { - Self::Workspace(workspace) => workspace.members_requirements(), + Self::Workspace(workspace) => Either::Left(workspace.members_requirements()), + Self::Script(_) => Either::Right(std::iter::empty()), } } /// Returns the set of all dependency groups within the target. pub(crate) fn group_requirements(self) -> impl Iterator + 'lock { match self { - Self::Workspace(workspace) => workspace.group_requirements(), + Self::Workspace(workspace) => Either::Left(workspace.group_requirements()), + Self::Script(_) => Either::Right(std::iter::empty()), } } @@ -90,6 +125,7 @@ impl<'lock> LockTarget<'lock> { members } + Self::Script(_) => Vec::new(), } } @@ -97,6 +133,10 @@ impl<'lock> LockTarget<'lock> { pub(crate) fn packages(self) -> &'lock BTreeMap { match self { Self::Workspace(workspace) => workspace.packages(), + Self::Script(_) => { + static EMPTY: BTreeMap = BTreeMap::new(); + &EMPTY + } } } @@ -104,6 +144,10 @@ impl<'lock> LockTarget<'lock> { pub(crate) fn environments(self) -> Option<&'lock SupportedEnvironments> { match self { Self::Workspace(workspace) => workspace.environments(), + Self::Script(_) => { + // TODO(charlie): Add support for environments in scripts. + None + } } } @@ -111,6 +155,7 @@ impl<'lock> LockTarget<'lock> { pub(crate) fn conflicts(self) -> Conflicts { match self { Self::Workspace(workspace) => workspace.conflicts(), + Self::Script(_) => Conflicts::empty(), } } @@ -118,6 +163,11 @@ impl<'lock> LockTarget<'lock> { pub(crate) fn requires_python(self) -> Option { match self { Self::Workspace(workspace) => find_requires_python(workspace), + Self::Script(script) => script + .metadata + .requires_python + .as_ref() + .map(RequiresPython::from_specifiers), } } @@ -125,13 +175,24 @@ impl<'lock> LockTarget<'lock> { pub(crate) fn install_path(self) -> &'lock Path { match self { Self::Workspace(workspace) => workspace.install_path(), + Self::Script(script) => script.path.parent().unwrap(), } } /// Return the path to the lockfile. pub(crate) fn lock_path(self) -> PathBuf { match self { + // `uv.lock` Self::Workspace(workspace) => workspace.install_path().join("uv.lock"), + // `script.py.lock` + Self::Script(script) => { + let mut file_name = match script.path.file_name() { + Some(f) => f.to_os_string(), + None => panic!("Script path has no file name"), + }; + file_name.push(".lock"); + script.path.with_file_name(file_name) + } } } @@ -174,11 +235,11 @@ impl<'lock> LockTarget<'lock> { } /// Read the lockfile from the workspace as bytes. - pub(crate) async fn read_bytes(self) -> Result>, ProjectError> { + pub(crate) async fn read_bytes(self) -> Result>, std::io::Error> { match fs_err::tokio::read(self.lock_path()).await { Ok(encoded) => Ok(Some(encoded)), Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None), - Err(err) => Err(err.into()), + Err(err) => Err(err), } } @@ -223,6 +284,55 @@ impl<'lock> LockTarget<'lock> { .map(|requirement| requirement.with_origin(RequirementOrigin::Workspace)) .collect::>()) } + Self::Script(script) => { + // Collect any `tool.uv.index` from the script. + let empty = Vec::default(); + let indexes = match sources { + SourceStrategy::Enabled => script + .metadata + .tool + .as_ref() + .and_then(|tool| tool.uv.as_ref()) + .and_then(|uv| uv.top_level.index.as_deref()) + .unwrap_or(&empty), + SourceStrategy::Disabled => &empty, + }; + + // Collect any `tool.uv.sources` from the script. + let empty = BTreeMap::default(); + let sources = match sources { + SourceStrategy::Enabled => script + .metadata + .tool + .as_ref() + .and_then(|tool| tool.uv.as_ref()) + .and_then(|uv| uv.sources.as_ref()) + .unwrap_or(&empty), + SourceStrategy::Disabled => &empty, + }; + + Ok(requirements + .into_iter() + .flat_map(|requirement| { + let requirement_name = requirement.name.clone(); + LoweredRequirement::from_non_workspace_requirement( + requirement, + script.path.parent().unwrap(), + sources, + indexes, + locations, + LowerBound::Allow, + ) + .map(move |requirement| match requirement { + Ok(requirement) => Ok(requirement.into_inner()), + Err(err) => Err(uv_distribution::MetadataError::LoweringError( + requirement_name.clone(), + Box::new(err), + )), + }) + }) + .collect::>()?) + } } } } diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index beb9d0793ce3..c9c9dc0fc3f2 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -1,6 +1,7 @@ use std::collections::BTreeSet; use std::fmt::Write; use std::path::{Path, PathBuf}; +use std::sync::Arc; use itertools::Itertools; use owo_colors::OwoColorize; @@ -142,6 +143,9 @@ pub(crate) enum ProjectError { #[error("Group `{0}` is not defined in any project's `dependency-group` table")] MissingGroupWorkspace(GroupName), + #[error("PEP 723 scripts do not support dependency groups, but group `{0}` was specified")] + MissingGroupScript(GroupName), + #[error("Default group `{0}` (from `tool.uv.default-groups`) is not defined in the project's `dependency-group` table")] MissingDefaultGroup(GroupName), @@ -166,6 +170,9 @@ pub(crate) enum ProjectError { #[error("Failed to update `pyproject.toml`")] PyprojectTomlUpdate, + #[error("Failed to parse PEP 723 script metadata")] + Pep723ScriptTomlParse(#[source] toml::de::Error), + #[error(transparent)] DependencyGroup(#[from] DependencyGroupError), @@ -271,7 +278,7 @@ impl std::fmt::Display for ConflictError { self.conflicts .iter() .map(|conflict| match conflict { - ConflictPackage::Group(ref group) if self.dev.default(group) => + ConflictPackage::Group(ref group) if self.dev.is_default(group) => format!("`{group}` (enabled by default)"), ConflictPackage::Group(ref group) => format!("`{group}`"), ConflictPackage::Extra(..) => unreachable!(), @@ -290,7 +297,7 @@ impl std::fmt::Display for ConflictError { .map(|(i, conflict)| { let conflict = match conflict { ConflictPackage::Extra(ref extra) => format!("extra `{extra}`"), - ConflictPackage::Group(ref group) if self.dev.default(group) => { + ConflictPackage::Group(ref group) if self.dev.is_default(group) => { format!("group `{group}` (enabled by default)") } ConflictPackage::Group(ref group) => format!("group `{group}`"), @@ -1077,7 +1084,7 @@ pub(crate) async fn resolve_names( state.index(), DistributionDatabase::new(&client, &build_dispatch, concurrency.downloads), ) - .with_reporter(ResolverReporter::from(printer)) + .with_reporter(Arc::new(ResolverReporter::from(printer))) .resolve(unnamed.into_iter()) .await?, ); @@ -1085,7 +1092,7 @@ pub(crate) async fn resolve_names( Ok(requirements) } -#[derive(Debug)] +#[derive(Debug, Clone)] pub(crate) struct EnvironmentSpecification<'lock> { /// The requirements to include in the environment. requirements: RequirementsSpecification, @@ -1110,7 +1117,7 @@ impl<'lock> EnvironmentSpecification<'lock> { } /// Run dependency resolution for an interpreter, returning the [`ResolverOutput`]. -pub(crate) async fn resolve_environment<'a>( +pub(crate) async fn resolve_environment( spec: EnvironmentSpecification<'_>, interpreter: &Interpreter, settings: ResolverSettingsRef<'_>, @@ -1726,6 +1733,8 @@ pub(crate) enum DependencyGroupsTarget<'env> { Workspace(&'env Workspace), /// The dependency groups must be defined in the target project. Project(&'env ProjectWorkspace), + /// The dependency groups must be defined in the target script. + Script, } impl DependencyGroupsTarget<'_> { @@ -1756,6 +1765,9 @@ impl DependencyGroupsTarget<'_> { return Err(ProjectError::MissingGroupProject(group.clone())); } } + Self::Script => { + return Err(ProjectError::MissingGroupScript(group.clone())); + } } } Ok(()) diff --git a/crates/uv/src/commands/project/remove.rs b/crates/uv/src/commands/project/remove.rs index 5a669b74c3cf..6c6243410db1 100644 --- a/crates/uv/src/commands/project/remove.rs +++ b/crates/uv/src/commands/project/remove.rs @@ -1,8 +1,11 @@ use std::fmt::Write; +use std::io; use std::path::Path; +use std::str::FromStr; use anyhow::{Context, Result}; use owo_colors::OwoColorize; +use tracing::debug; use uv_cache::Cache; use uv_client::Connectivity; @@ -15,7 +18,7 @@ use uv_fs::Simplified; use uv_normalize::DEV_DEPENDENCIES; use uv_pep508::PackageName; use uv_python::{PythonDownloads, PythonPreference, PythonRequest}; -use uv_scripts::Pep723Script; +use uv_scripts::{Pep723ItemRef, Pep723Metadata, Pep723Script}; use uv_settings::PythonInstallMirrors; use uv_warnings::warn_user_once; use uv_workspace::pyproject::DependencyType; @@ -24,9 +27,13 @@ use uv_workspace::{DiscoveryOptions, VirtualProject, Workspace}; use crate::commands::pip::loggers::{DefaultInstallLogger, DefaultResolveLogger}; use crate::commands::pip::operations::Modifications; +use crate::commands::project::add::{AddTarget, PythonTarget}; use crate::commands::project::install_target::InstallTarget; use crate::commands::project::lock::LockMode; -use crate::commands::project::{default_dependency_groups, ProjectError}; +use crate::commands::project::lock_target::LockTarget; +use crate::commands::project::{ + default_dependency_groups, ProjectError, ProjectInterpreter, ScriptInterpreter, +}; use crate::commands::{diagnostics, project, ExitStatus}; use crate::printer::Printer; use crate::settings::ResolverInstallerSettings; @@ -79,7 +86,7 @@ pub(crate) async fn remove( "`--no-sync` is a no-op for Python scripts with inline metadata, which always run in isolation" ); } - Target::Script(script) + RemoveTarget::Script(script) } else { // Find the project in the workspace. let project = if let Some(package) = package { @@ -93,14 +100,14 @@ pub(crate) async fn remove( VirtualProject::discover(project_dir, &DiscoveryOptions::default()).await? }; - Target::Project(project) + RemoveTarget::Project(project) }; let mut toml = match &target { - Target::Script(script) => { + RemoveTarget::Script(script) => { PyProjectTomlMut::from_toml(&script.metadata.raw, DependencyTarget::Script) } - Target::Project(project) => PyProjectTomlMut::from_toml( + RemoveTarget::Project(project) => PyProjectTomlMut::from_toml( project.pyproject_toml().raw.as_ref(), DependencyTarget::PyProjectToml, ), @@ -161,27 +168,20 @@ pub(crate) async fn remove( } } - // Save the modified dependencies. - match &target { - Target::Script(script) => { - script.write(&toml.to_string()).await?; - } - Target::Project(project) => { - let pyproject_path = project.root().join("pyproject.toml"); - fs_err::write(pyproject_path, toml.to_string())?; - } - }; + let content = toml.to_string(); + + // Save the modified `pyproject.toml` or script. + target.write(&content)?; - // If `--frozen`, exit early. There's no reason to lock and sync, and we don't need a `uv.lock` + // If `--frozen`, exit early. There's no reason to lock and sync, since we don't need a `uv.lock` // to exist at all. if frozen { return Ok(ExitStatus::Success); } - let project = match target { - Target::Project(project) => project, - // If `--script`, exit early. There's no reason to lock and sync. - Target::Script(script) => { + // If we're modifying a script, and lockfile doesn't exist, don't create it. + if let RemoveTarget::Script(ref script) = target { + if !LockTarget::from(script).lock_path().is_file() { writeln!( printer.stderr(), "Updated `{}`", @@ -189,31 +189,80 @@ pub(crate) async fn remove( )?; return Ok(ExitStatus::Success); } - }; + } - // Discover or create the virtual environment. - let venv = project::get_or_init_environment( - project.workspace(), - python.as_deref().map(PythonRequest::parse), - &install_mirrors, - python_preference, - python_downloads, - connectivity, - native_tls, - allow_insecure_host, - no_config, - cache, - printer, - ) - .await?; + // Update the `pypackage.toml` in-memory. + let target = target.update(&content)?; + + // Convert to an `AddTarget` by attaching the appropriate interpreter or environment. + let target = match target { + RemoveTarget::Project(project) => { + if no_sync { + // Discover the interpreter. + let interpreter = ProjectInterpreter::discover( + project.workspace(), + project_dir, + python.as_deref().map(PythonRequest::parse), + python_preference, + python_downloads, + connectivity, + native_tls, + allow_insecure_host, + &install_mirrors, + no_config, + cache, + printer, + ) + .await? + .into_interpreter(); + + AddTarget::Project(project, Box::new(PythonTarget::Interpreter(interpreter))) + } else { + // Discover or create the virtual environment. + let venv = project::get_or_init_environment( + project.workspace(), + python.as_deref().map(PythonRequest::parse), + &install_mirrors, + python_preference, + python_downloads, + connectivity, + native_tls, + allow_insecure_host, + no_config, + cache, + printer, + ) + .await?; + + AddTarget::Project(project, Box::new(PythonTarget::Environment(venv))) + } + } + RemoveTarget::Script(script) => { + let interpreter = ScriptInterpreter::discover( + Pep723ItemRef::Script(&script), + python.as_deref().map(PythonRequest::parse), + python_preference, + python_downloads, + connectivity, + native_tls, + allow_insecure_host, + &install_mirrors, + no_config, + cache, + printer, + ) + .await? + .into_interpreter(); + + AddTarget::Script(script, Box::new(interpreter)) + } + }; // Determine the lock mode. - let mode = if frozen { - LockMode::Frozen - } else if locked { - LockMode::Locked(venv.interpreter()) + let mode = if locked { + LockMode::Locked(target.interpreter()) } else { - LockMode::Write(venv.interpreter()) + LockMode::Write(target.interpreter()) }; // Initialize any shared state. @@ -222,7 +271,7 @@ pub(crate) async fn remove( // Lock and sync the environment, if necessary. let lock = match project::lock::do_safe_lock( mode, - project.workspace().into(), + (&target).into(), settings.as_ref().into(), LowerBound::Allow, &state, @@ -246,9 +295,15 @@ pub(crate) async fn remove( Err(err) => return Err(err.into()), }; - if no_sync { + let AddTarget::Project(project, environment) = target else { + // If we're not adding to a project, exit early. return Ok(ExitStatus::Success); - } + }; + + let PythonTarget::Environment(venv) = &*environment else { + // If we're not syncing, exit early. + return Ok(ExitStatus::Success); + }; // Perform a full sync, because we don't know what exactly is affected by the removal. // TODO(ibraheem): Should we accept CLI overrides for this? Should we even sync here? @@ -273,7 +328,7 @@ pub(crate) async fn remove( match project::sync::do_sync( target, - &venv, + venv, &extras, &DevGroupsManifest::from_defaults(defaults), EditableMode::Editable, @@ -306,13 +361,62 @@ pub(crate) async fn remove( /// Represents the destination where dependencies are added, either to a project or a script. #[derive(Debug)] -enum Target { +enum RemoveTarget { /// A PEP 723 script, with inline metadata. Project(VirtualProject), /// A project with a `pyproject.toml`. Script(Pep723Script), } +impl RemoveTarget { + /// Write the updated content to the target. + /// + /// Returns `true` if the content was modified. + fn write(&self, content: &str) -> Result { + match self { + Self::Script(script) => { + if content == script.metadata.raw { + debug!("No changes to dependencies; skipping update"); + Ok(false) + } else { + script.write(content)?; + Ok(true) + } + } + Self::Project(project) => { + if content == project.pyproject_toml().raw { + debug!("No changes to dependencies; skipping update"); + Ok(false) + } else { + let pyproject_path = project.root().join("pyproject.toml"); + fs_err::write(pyproject_path, content)?; + Ok(true) + } + } + } + } + + /// Update the target in-memory to incorporate the new content. + #[allow(clippy::result_large_err)] + fn update(self, content: &str) -> Result { + match self { + Self::Script(mut script) => { + script.metadata = Pep723Metadata::from_str(content) + .map_err(ProjectError::Pep723ScriptTomlParse)?; + Ok(Self::Script(script)) + } + Self::Project(project) => { + let project = project + .with_pyproject_toml( + toml::from_str(content).map_err(ProjectError::PyprojectTomlParse)?, + ) + .ok_or(ProjectError::PyprojectTomlUpdate)?; + Ok(Self::Project(project)) + } + } + } +} + /// Show a hint if a dependency with the given name is present as any dependency type. /// /// This is useful when a dependency of the user-specified type was not found, but it may be present diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index e08ecb95ff37..16cd4b8f01ed 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -17,8 +17,8 @@ use uv_cache::Cache; use uv_cli::ExternalCommand; use uv_client::{BaseClientBuilder, Connectivity}; use uv_configuration::{ - Concurrency, DevGroupsSpecification, EditableMode, ExtrasSpecification, GroupsSpecification, - InstallOptions, LowerBound, PreviewMode, SourceStrategy, TrustedHost, + Concurrency, DevGroupsManifest, DevGroupsSpecification, EditableMode, ExtrasSpecification, + GroupsSpecification, InstallOptions, LowerBound, PreviewMode, SourceStrategy, TrustedHost, }; use uv_dispatch::SharedState; use uv_distribution::LoweredRequirement; @@ -93,6 +93,7 @@ pub(crate) async fn run( ) -> anyhow::Result { // These cases seem quite complex because (in theory) they should change the "current package". // Let's ban them entirely for now. + let mut requirements_from_stdin: bool = false; for source in &requirements { match source { RequirementsSource::PyprojectToml(_) => { @@ -106,13 +107,22 @@ pub(crate) async fn run( } RequirementsSource::RequirementsTxt(path) => { if path == Path::new("-") { - bail!("Reading requirements from stdin is not supported in `uv run`"); + requirements_from_stdin = true; } } _ => {} } } + // Fail early if stdin is used for multiple purposes. + if matches!( + command, + Some(RunCommand::PythonStdin(..) | RunCommand::PythonGuiStdin(..)) + ) && requirements_from_stdin + { + bail!("Cannot read both requirements file and script from stdin"); + } + // Initialize any shared state. let state = SharedState::default(); @@ -169,6 +179,9 @@ pub(crate) async fn run( )?; } Pep723Item::Stdin(_) => { + if requirements_from_stdin { + bail!("Cannot read both requirements file and script from stdin"); + } writeln!( printer.stderr(), "Reading inline script metadata from `{}`", @@ -201,109 +214,57 @@ pub(crate) async fn run( .await? .into_interpreter(); - // Determine the working directory for the script. - let script_dir = match &script { - Pep723Item::Script(script) => std::path::absolute(&script.path)? - .parent() - .expect("script path has no parent") - .to_owned(), - Pep723Item::Stdin(..) | Pep723Item::Remote(..) => std::env::current_dir()?, - }; - let script = script.into_metadata(); - - // Install the script requirements, if necessary. Otherwise, use an isolated environment. - if let Some(dependencies) = script.dependencies { - // Collect any `tool.uv.index` from the script. - let empty = Vec::default(); - let script_indexes = match settings.sources { - SourceStrategy::Enabled => script - .tool - .as_ref() - .and_then(|tool| tool.uv.as_ref()) - .and_then(|uv| uv.top_level.index.as_deref()) - .unwrap_or(&empty), - SourceStrategy::Disabled => &empty, - }; + // If a lockfile already exists, lock the script. + if let Some(target) = script + .as_script() + .map(LockTarget::from) + .filter(|target| target.lock_path().is_file()) + { + debug!("Found existing lockfile for script"); - // Collect any `tool.uv.sources` from the script. - let empty = BTreeMap::default(); - let script_sources = match settings.sources { - SourceStrategy::Enabled => script - .tool - .as_ref() - .and_then(|tool| tool.uv.as_ref()) - .and_then(|uv| uv.sources.as_ref()) - .unwrap_or(&empty), - SourceStrategy::Disabled => &empty, + // Determine the lock mode. + let mode = if frozen { + LockMode::Frozen + } else if locked { + LockMode::Locked(&interpreter) + } else { + LockMode::Write(&interpreter) }; - let requirements = dependencies - .into_iter() - .flat_map(|requirement| { - LoweredRequirement::from_non_workspace_requirement( - requirement, - script_dir.as_ref(), - script_sources, - script_indexes, - &settings.index_locations, - LowerBound::Allow, - ) - .map_ok(LoweredRequirement::into_inner) - }) - .collect::>()?; - let constraints = script - .tool - .as_ref() - .and_then(|tool| tool.uv.as_ref()) - .and_then(|uv| uv.constraint_dependencies.as_ref()) - .into_iter() - .flatten() - .cloned() - .flat_map(|requirement| { - LoweredRequirement::from_non_workspace_requirement( - requirement, - script_dir.as_ref(), - script_sources, - script_indexes, - &settings.index_locations, - LowerBound::Allow, - ) - .map_ok(LoweredRequirement::into_inner) - }) - .collect::, _>>()?; - let overrides = script - .tool - .as_ref() - .and_then(|tool| tool.uv.as_ref()) - .and_then(|uv| uv.override_dependencies.as_ref()) - .into_iter() - .flatten() - .cloned() - .flat_map(|requirement| { - LoweredRequirement::from_non_workspace_requirement( - requirement, - script_dir.as_ref(), - script_sources, - script_indexes, - &settings.index_locations, - LowerBound::Allow, - ) - .map_ok(LoweredRequirement::into_inner) - }) - .collect::, _>>()?; - - let spec = - RequirementsSpecification::from_overrides(requirements, constraints, overrides); - let result = CachedEnvironment::get_or_create( - EnvironmentSpecification::from(spec), - interpreter, - &settings, + // Generate a lockfile. + let lock = project::lock::do_safe_lock( + mode, + target, + settings.as_ref().into(), + LowerBound::Allow, &state, if show_resolution { Box::new(DefaultResolveLogger) } else { Box::new(SummaryResolveLogger) }, + connectivity, + concurrency, + native_tls, + allow_insecure_host, + cache, + printer, + preview, + ) + .await? + .into_lock(); + + let result = CachedEnvironment::from_lock( + InstallTarget::Script { + script: script.as_script().unwrap(), + lock: &lock, + }, + &ExtrasSpecification::default(), + &DevGroupsManifest::default(), + InstallOptions::default(), + &settings, + &interpreter, + &state, if show_resolution { Box::new(DefaultInstallLogger) } else { @@ -332,19 +293,151 @@ pub(crate) async fn run( Some(environment.into_interpreter()) } else { - // Create a virtual environment. - temp_dir = cache.venv_dir()?; - let environment = uv_virtualenv::create_venv( - temp_dir.path(), - interpreter, - uv_virtualenv::Prompt::None, - false, - false, - false, - false, - )?; + // Determine the working directory for the script. + let script_dir = match &script { + Pep723Item::Script(script) => std::path::absolute(&script.path)? + .parent() + .expect("script path has no parent") + .to_owned(), + Pep723Item::Stdin(..) | Pep723Item::Remote(..) => std::env::current_dir()?, + }; + let script = script.into_metadata(); + + // Install the script requirements, if necessary. Otherwise, use an isolated environment. + if let Some(dependencies) = script.dependencies { + // Collect any `tool.uv.index` from the script. + let empty = Vec::default(); + let script_indexes = match settings.sources { + SourceStrategy::Enabled => script + .tool + .as_ref() + .and_then(|tool| tool.uv.as_ref()) + .and_then(|uv| uv.top_level.index.as_deref()) + .unwrap_or(&empty), + SourceStrategy::Disabled => &empty, + }; - Some(environment.into_interpreter()) + // Collect any `tool.uv.sources` from the script. + let empty = BTreeMap::default(); + let script_sources = match settings.sources { + SourceStrategy::Enabled => script + .tool + .as_ref() + .and_then(|tool| tool.uv.as_ref()) + .and_then(|uv| uv.sources.as_ref()) + .unwrap_or(&empty), + SourceStrategy::Disabled => &empty, + }; + + let requirements = dependencies + .into_iter() + .flat_map(|requirement| { + LoweredRequirement::from_non_workspace_requirement( + requirement, + script_dir.as_ref(), + script_sources, + script_indexes, + &settings.index_locations, + LowerBound::Allow, + ) + .map_ok(LoweredRequirement::into_inner) + }) + .collect::>()?; + let constraints = script + .tool + .as_ref() + .and_then(|tool| tool.uv.as_ref()) + .and_then(|uv| uv.constraint_dependencies.as_ref()) + .into_iter() + .flatten() + .cloned() + .flat_map(|requirement| { + LoweredRequirement::from_non_workspace_requirement( + requirement, + script_dir.as_ref(), + script_sources, + script_indexes, + &settings.index_locations, + LowerBound::Allow, + ) + .map_ok(LoweredRequirement::into_inner) + }) + .collect::, _>>()?; + let overrides = script + .tool + .as_ref() + .and_then(|tool| tool.uv.as_ref()) + .and_then(|uv| uv.override_dependencies.as_ref()) + .into_iter() + .flatten() + .cloned() + .flat_map(|requirement| { + LoweredRequirement::from_non_workspace_requirement( + requirement, + script_dir.as_ref(), + script_sources, + script_indexes, + &settings.index_locations, + LowerBound::Allow, + ) + .map_ok(LoweredRequirement::into_inner) + }) + .collect::, _>>()?; + + let spec = + RequirementsSpecification::from_overrides(requirements, constraints, overrides); + let result = CachedEnvironment::from_spec( + EnvironmentSpecification::from(spec), + &interpreter, + &settings, + &state, + if show_resolution { + Box::new(DefaultResolveLogger) + } else { + Box::new(SummaryResolveLogger) + }, + if show_resolution { + Box::new(DefaultInstallLogger) + } else { + Box::new(SummaryInstallLogger) + }, + installer_metadata, + connectivity, + concurrency, + native_tls, + allow_insecure_host, + cache, + printer, + preview, + ) + .await; + + let environment = match result { + Ok(resolution) => resolution, + Err(ProjectError::Operation(err)) => { + return diagnostics::OperationDiagnostic::with_context("script") + .report(err) + .map_or(Ok(ExitStatus::Failure), |err| Err(err.into())) + } + Err(err) => return Err(err.into()), + }; + + Some(environment.into_interpreter()) + } else { + // Create a virtual environment. + temp_dir = cache.venv_dir()?; + let environment = uv_virtualenv::create_venv( + temp_dir.path(), + interpreter, + uv_virtualenv::Prompt::None, + false, + false, + false, + false, + )?; + + Some(environment.into_interpreter()) + } } } else { None @@ -847,12 +940,12 @@ pub(crate) async fn run( Some(spec) => { debug!("Syncing ephemeral requirements"); - let result = CachedEnvironment::get_or_create( + let result = CachedEnvironment::from_spec( EnvironmentSpecification::from(spec).with_lock( lock.as_ref() .map(|(lock, install_path)| (lock, install_path.as_ref())), ), - base_interpreter.clone(), + &base_interpreter, &settings, &state, if show_resolution { @@ -1442,8 +1535,8 @@ impl RunCommand { } let metadata = target_path.metadata(); - let is_file = metadata.as_ref().map_or(false, std::fs::Metadata::is_file); - let is_dir = metadata.as_ref().map_or(false, std::fs::Metadata::is_dir); + let is_file = metadata.as_ref().is_ok_and(std::fs::Metadata::is_file); + let is_dir = metadata.as_ref().is_ok_and(std::fs::Metadata::is_dir); if target.eq_ignore_ascii_case("python") { Ok(Self::Python(args.to_vec())) @@ -1476,9 +1569,7 @@ impl RunCommand { fn is_python_zipapp(target: &Path) -> bool { if let Ok(file) = fs_err::File::open(target) { if let Ok(mut archive) = zip::ZipArchive::new(file) { - return archive - .by_name("__main__.py") - .map_or(false, |f| f.is_file()); + return archive.by_name("__main__.py").is_ok_and(|f| f.is_file()); } } false diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index 1d9693d87dbf..c4f062c6a28c 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -1,6 +1,4 @@ -use std::borrow::Cow; use std::path::Path; -use std::str::FromStr; use anyhow::{Context, Result}; use itertools::Itertools; @@ -18,16 +16,14 @@ use uv_distribution_types::{ }; use uv_installer::SitePackages; use uv_normalize::PackageName; -use uv_pep508::{MarkerTree, Requirement, VersionOrUrl}; -use uv_pypi_types::{ - LenientRequirement, ParsedArchiveUrl, ParsedGitUrl, ParsedUrl, VerbatimParsedUrl, -}; +use uv_pep508::{MarkerTree, VersionOrUrl}; +use uv_pypi_types::{ParsedArchiveUrl, ParsedGitUrl, ParsedUrl}; use uv_python::{PythonDownloads, PythonEnvironment, PythonPreference, PythonRequest}; use uv_resolver::{FlatIndex, Installable}; use uv_settings::PythonInstallMirrors; use uv_types::{BuildIsolation, HashStrategy}; use uv_warnings::warn_user; -use uv_workspace::pyproject::{DependencyGroupSpecifier, Source, Sources, ToolUvSources}; +use uv_workspace::pyproject::Source; use uv_workspace::{DiscoveryOptions, MemberDiscovery, VirtualProject, Workspace}; use crate::commands::pip::loggers::{DefaultInstallLogger, DefaultResolveLogger, InstallLogger}; @@ -368,7 +364,7 @@ pub(super) async fn do_sync( } // Populate credentials from the workspace. - store_credentials_from_workspace(target.workspace()); + store_credentials_from_workspace(target); // Initialize the registry client. let client = RegistryClientBuilder::new(cache.clone()) @@ -526,9 +522,9 @@ fn apply_editable_mode(resolution: Resolution, editable: EditableMode) -> Resolu /// /// These credentials can come from any of `tool.uv.sources`, `tool.uv.dev-dependencies`, /// `project.dependencies`, and `project.optional-dependencies`. -fn store_credentials_from_workspace(workspace: &Workspace) { - // Iterate over any sources in the workspace root. - for source in workspace.sources().values().flat_map(Sources::iter) { +fn store_credentials_from_workspace(target: InstallTarget<'_>) { + // Iterate over any sources in the target. + for source in target.sources() { match source { Source::Git { git, .. } => { uv_git::store_credentials_from_url(git); @@ -540,29 +536,8 @@ fn store_credentials_from_workspace(workspace: &Workspace) { } } - // Iterate over any dependencies defined in the workspace root. - for requirement in &workspace.requirements() { - let Some(VersionOrUrl::Url(url)) = &requirement.version_or_url else { - continue; - }; - match &url.parsed_url { - ParsedUrl::Git(ParsedGitUrl { url, .. }) => { - uv_git::store_credentials_from_url(url.repository()); - } - ParsedUrl::Archive(ParsedArchiveUrl { url, .. }) => { - uv_auth::store_credentials_from_url(url); - } - _ => {} - } - } - - // Iterate over any dependency groups defined in the workspace root. - for requirement in workspace - .dependency_groups() - .ok() - .iter() - .flat_map(|groups| groups.values().flat_map(|group| group.iter())) - { + // Iterate over any dependencies defined in the target. + for requirement in target.requirements() { let Some(VersionOrUrl::Url(url)) = &requirement.version_or_url else { continue; }; @@ -576,94 +551,4 @@ fn store_credentials_from_workspace(workspace: &Workspace) { _ => {} } } - - // Iterate over each workspace member. - for member in workspace.packages().values() { - // Iterate over the `tool.uv.sources`. - for source in member - .pyproject_toml() - .tool - .as_ref() - .and_then(|tool| tool.uv.as_ref()) - .and_then(|uv| uv.sources.as_ref()) - .map(ToolUvSources::inner) - .iter() - .flat_map(|sources| sources.values().flat_map(Sources::iter)) - { - match source { - Source::Git { git, .. } => { - uv_git::store_credentials_from_url(git); - } - Source::Url { url, .. } => { - uv_auth::store_credentials_from_url(url); - } - _ => {} - } - } - - // Iterate over all dependencies. - let dependencies = member - .pyproject_toml() - .project - .as_ref() - .and_then(|project| project.dependencies.as_ref()) - .into_iter() - .flatten(); - let optional_dependencies = member - .pyproject_toml() - .project - .as_ref() - .and_then(|project| project.optional_dependencies.as_ref()) - .into_iter() - .flat_map(|optional| optional.values()) - .flatten(); - let dependency_groups = member - .pyproject_toml() - .dependency_groups - .as_ref() - .into_iter() - .flatten() - .flat_map(|(_, dependencies)| { - dependencies.iter().filter_map(|specifier| { - if let DependencyGroupSpecifier::Requirement(requirement) = specifier { - Some(requirement) - } else { - None - } - }) - }); - let dev_dependencies = member - .pyproject_toml() - .tool - .as_ref() - .and_then(|tool| tool.uv.as_ref()) - .and_then(|uv| uv.dev_dependencies.as_ref()) - .into_iter() - .flatten(); - - for requirement in dependencies - .chain(optional_dependencies) - .chain(dependency_groups) - .filter_map(|requires_dist| { - LenientRequirement::::from_str(requires_dist) - .map(Requirement::from) - .map(Cow::Owned) - .ok() - }) - .chain(dev_dependencies.map(Cow::Borrowed)) - { - let Some(VersionOrUrl::Url(url)) = &requirement.version_or_url else { - continue; - }; - match &url.parsed_url { - ParsedUrl::Git(ParsedGitUrl { url, .. }) => { - uv_git::store_credentials_from_url(url.repository()); - } - ParsedUrl::Archive(ParsedArchiveUrl { url, .. }) => { - uv_auth::store_credentials_from_url(url); - } - _ => {} - } - } - } } diff --git a/crates/uv/src/commands/project/tree.rs b/crates/uv/src/commands/project/tree.rs index 1994862fa7fb..1c612cceabfe 100644 --- a/crates/uv/src/commands/project/tree.rs +++ b/crates/uv/src/commands/project/tree.rs @@ -15,6 +15,7 @@ use uv_distribution_types::IndexCapabilities; use uv_pep508::PackageName; use uv_python::{PythonDownloads, PythonPreference, PythonRequest, PythonVersion}; use uv_resolver::{PackageMap, TreeDisplay}; +use uv_scripts::{Pep723ItemRef, Pep723Script}; use uv_settings::PythonInstallMirrors; use uv_workspace::{DiscoveryOptions, Workspace}; @@ -22,8 +23,10 @@ use crate::commands::pip::latest::LatestClient; use crate::commands::pip::loggers::DefaultResolveLogger; use crate::commands::pip::resolution_markers; use crate::commands::project::lock::{do_safe_lock, LockMode}; +use crate::commands::project::lock_target::LockTarget; use crate::commands::project::{ default_dependency_groups, DependencyGroupsTarget, ProjectError, ProjectInterpreter, + ScriptInterpreter, }; use crate::commands::reporters::LatestVersionReporter; use crate::commands::{diagnostics, ExitStatus}; @@ -49,6 +52,7 @@ pub(crate) async fn tree( python: Option, install_mirrors: PythonInstallMirrors, settings: ResolverSettings, + script: Option, python_preference: PythonPreference, python_downloads: PythonDownloads, connectivity: Connectivity, @@ -61,24 +65,51 @@ pub(crate) async fn tree( preview: PreviewMode, ) -> Result { // Find the project requirements. - let workspace = Workspace::discover(project_dir, &DiscoveryOptions::default()).await?; + let workspace; + let target = if let Some(script) = script.as_ref() { + LockTarget::Script(script) + } else { + workspace = Workspace::discover(project_dir, &DiscoveryOptions::default()).await?; + LockTarget::Workspace(&workspace) + }; - // Validate that any referenced dependency groups are defined in the workspace. + // Validate that any referenced dependency groups are defined in the target. if !frozen { - let target = DependencyGroupsTarget::Workspace(&workspace); + let target = match &target { + LockTarget::Workspace(workspace) => DependencyGroupsTarget::Workspace(workspace), + LockTarget::Script(..) => DependencyGroupsTarget::Script, + }; target.validate(&dev)?; } // Determine the default groups to include. - let defaults = default_dependency_groups(workspace.pyproject_toml())?; + let defaults = match target { + LockTarget::Workspace(workspace) => default_dependency_groups(workspace.pyproject_toml())?, + LockTarget::Script(_) => vec![], + }; // Find an interpreter for the project, unless `--frozen` and `--universal` are both set. let interpreter = if frozen && universal { None } else { - Some( - ProjectInterpreter::discover( - &workspace, + Some(match target { + LockTarget::Script(script) => ScriptInterpreter::discover( + Pep723ItemRef::Script(script), + python.as_deref().map(PythonRequest::parse), + python_preference, + python_downloads, + connectivity, + native_tls, + allow_insecure_host, + &install_mirrors, + no_config, + cache, + printer, + ) + .await? + .into_interpreter(), + LockTarget::Workspace(workspace) => ProjectInterpreter::discover( + workspace, project_dir, python.as_deref().map(PythonRequest::parse), python_preference, @@ -93,7 +124,7 @@ pub(crate) async fn tree( ) .await? .into_interpreter(), - ) + }) }; // Determine the lock mode. @@ -101,6 +132,9 @@ pub(crate) async fn tree( LockMode::Frozen } else if locked { LockMode::Locked(interpreter.as_ref().unwrap()) + } else if matches!(target, LockTarget::Script(_)) && !target.lock_path().is_file() { + // If we're locking a script, avoid creating a lockfile if it doesn't already exist. + LockMode::DryRun(interpreter.as_ref().unwrap()) } else { LockMode::Write(interpreter.as_ref().unwrap()) }; @@ -111,7 +145,7 @@ pub(crate) async fn tree( // Update the lockfile, if necessary. let lock = match do_safe_lock( mode, - (&workspace).into(), + target, settings.as_ref(), LowerBound::Allow, &state, @@ -151,7 +185,7 @@ pub(crate) async fn tree( .packages() .iter() .filter_map(|package| { - let index = match package.index(workspace.install_path()) { + let index = match package.index(target.install_path()) { Ok(Some(index)) => index, Ok(None) => return None, Err(err) => return Some(Err(err)), diff --git a/crates/uv/src/commands/publish.rs b/crates/uv/src/commands/publish.rs index 88dcdf684245..b3405ccd9d07 100644 --- a/crates/uv/src/commands/publish.rs +++ b/crates/uv/src/commands/publish.rs @@ -11,7 +11,9 @@ use std::time::Duration; use tracing::{debug, info}; use url::Url; use uv_cache::Cache; -use uv_client::{AuthIntegration, BaseClientBuilder, Connectivity, RegistryClientBuilder}; +use uv_client::{ + AuthIntegration, BaseClient, BaseClientBuilder, Connectivity, RegistryClientBuilder, +}; use uv_configuration::{KeyringProviderType, TrustedHost, TrustedPublishing}; use uv_distribution_types::{Index, IndexCapabilities, IndexLocations, IndexUrl}; use uv_publish::{ @@ -68,6 +70,19 @@ pub(crate) async fn publish( .auth_integration(AuthIntegration::NoAuthMiddleware) .wrap_existing(&upload_client); + let (publish_url, username, password) = gather_credentials( + publish_url, + username, + password, + trusted_publishing, + keyring_provider, + &oidc_client, + check_url.as_ref(), + Prompt::Enabled, + printer, + ) + .await?; + // Initialize the registry client. let check_url_client = if let Some(index_url) = &check_url { let index_urls = IndexLocations::new( @@ -93,6 +108,92 @@ pub(crate) async fn publish( None }; + for (file, raw_filename, filename) in files { + if let Some(check_url_client) = &check_url_client { + if uv_publish::check_url(check_url_client, &file, &filename).await? { + writeln!(printer.stderr(), "File {filename} already exists, skipping")?; + continue; + } + } + + let size = fs_err::metadata(&file)?.len(); + let (bytes, unit) = human_readable_bytes(size); + writeln!( + printer.stderr(), + "{} {filename} {}", + "Uploading".bold().green(), + format!("({bytes:.1}{unit})").dimmed() + )?; + let reporter = PublishReporter::single(printer); + let uploaded = upload( + &file, + &raw_filename, + &filename, + &publish_url, + &upload_client, + username.as_deref(), + password.as_deref(), + check_url_client.as_ref(), + // Needs to be an `Arc` because the reqwest `Body` static lifetime requirement + Arc::new(reporter), + ) + .await?; // Filename and/or URL are already attached, if applicable. + info!("Upload succeeded"); + if !uploaded { + writeln!( + printer.stderr(), + "{}", + "File already exists, skipping".dimmed() + )?; + } + } + + Ok(ExitStatus::Success) +} + +/// Whether to allow prompting for username and password. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Prompt { + Enabled, + #[allow(dead_code)] + Disabled, +} + +/// Unify the different possible source for username and password information. +/// +/// Returns the publish URL, the username and the password. +async fn gather_credentials( + mut publish_url: Url, + mut username: Option, + mut password: Option, + trusted_publishing: TrustedPublishing, + keyring_provider: KeyringProviderType, + oidc_client: &BaseClient, + check_url: Option<&IndexUrl>, + prompt: Prompt, + printer: Printer, +) -> Result<(Url, Option, Option)> { + // Support reading username and password from the URL, for symmetry with the index API. + if let Some(url_password) = publish_url.password() { + if password.is_some_and(|password| password != url_password) { + bail!("The password can't be set both in the publish URL and in the CLI"); + } + password = Some(url_password.to_string()); + publish_url + .set_password(None) + .expect("Failed to clear publish URL password"); + } + + if !publish_url.username().is_empty() { + if username.is_some_and(|username| username != publish_url.username()) { + bail!("The username can't be set both in the publish URL and in the CLI"); + } + username = Some(publish_url.username().to_string()); + publish_url + .set_username("") + .expect("Failed to clear publish URL username"); + } + // If applicable, attempt obtaining a token for trusted publishing. let trusted_publishing_token = check_trusted_publishing( username.as_deref(), @@ -100,7 +201,7 @@ pub(crate) async fn publish( keyring_provider, trusted_publishing, &publish_url, - &oidc_client, + oidc_client, ) .await?; @@ -109,7 +210,10 @@ pub(crate) async fn publish( (Some("__token__".to_string()), Some(password.to_string())) } else { if username.is_none() && password.is_none() { - prompt_username_and_password()? + match prompt { + Prompt::Enabled => prompt_username_and_password()?, + Prompt::Disabled => (None, None), + } } else { (username, password) } @@ -179,48 +283,7 @@ pub(crate) async fn publish( // We may be using the keyring for the simple index. } } - - for (file, raw_filename, filename) in files { - if let Some(check_url_client) = &check_url_client { - if uv_publish::check_url(check_url_client, &file, &filename).await? { - writeln!(printer.stderr(), "File {filename} already exists, skipping")?; - continue; - } - } - - let size = fs_err::metadata(&file)?.len(); - let (bytes, unit) = human_readable_bytes(size); - writeln!( - printer.stderr(), - "{} {filename} {}", - "Uploading".bold().green(), - format!("({bytes:.1}{unit})").dimmed() - )?; - let reporter = PublishReporter::single(printer); - let uploaded = upload( - &file, - &raw_filename, - &filename, - &publish_url, - &upload_client, - username.as_deref(), - password.as_deref(), - check_url_client.as_ref(), - // Needs to be an `Arc` because the reqwest `Body` static lifetime requirement - Arc::new(reporter), - ) - .await?; // Filename and/or URL are already attached, if applicable. - info!("Upload succeeded"); - if !uploaded { - writeln!( - printer.stderr(), - "{}", - "File already exists, skipping".dimmed() - )?; - } - } - - Ok(ExitStatus::Success) + Ok((publish_url, username, password)) } fn prompt_username_and_password() -> Result<(Option, Option)> { @@ -235,3 +298,113 @@ fn prompt_username_and_password() -> Result<(Option, Option)> { uv_console::password(password_prompt, &term).context("Failed to read password")?; Ok((Some(username), Some(password))) } + +#[cfg(test)] +mod tests { + use super::*; + + use std::str::FromStr; + + use insta::assert_snapshot; + use url::Url; + + async fn credentials( + url: Url, + username: Option, + password: Option, + ) -> Result<(Url, Option, Option)> { + let client = BaseClientBuilder::new().build(); + gather_credentials( + url, + username, + password, + TrustedPublishing::Never, + KeyringProviderType::Disabled, + &client, + None, + Prompt::Disabled, + Printer::Quiet, + ) + .await + } + + #[tokio::test] + async fn username_password_sources() { + let example_url = Url::from_str("https://example.com").unwrap(); + let example_url_username = Url::from_str("https://ferris@example.com").unwrap(); + let example_url_username_password = + Url::from_str("https://ferris:f3rr1s@example.com").unwrap(); + + let (publish_url, username, password) = + credentials(example_url.clone(), None, None).await.unwrap(); + assert_eq!(publish_url, example_url); + assert_eq!(username, None); + assert_eq!(password, None); + + let (publish_url, username, password) = + credentials(example_url_username.clone(), None, None) + .await + .unwrap(); + assert_eq!(publish_url, example_url); + assert_eq!(username.as_deref(), Some("ferris")); + assert_eq!(password, None); + + let (publish_url, username, password) = + credentials(example_url_username_password.clone(), None, None) + .await + .unwrap(); + assert_eq!(publish_url, example_url); + assert_eq!(username.as_deref(), Some("ferris")); + assert_eq!(password.as_deref(), Some("f3rr1s")); + + // Ok: The username is the same between CLI/env vars and URL + let (publish_url, username, password) = credentials( + example_url_username_password.clone(), + Some("ferris".to_string()), + None, + ) + .await + .unwrap(); + assert_eq!(publish_url, example_url); + assert_eq!(username.as_deref(), Some("ferris")); + assert_eq!(password.as_deref(), Some("f3rr1s")); + + // Err: There are two different usernames between CLI/env vars and URL + let err = credentials( + example_url_username_password.clone(), + Some("packaging-platypus".to_string()), + None, + ) + .await + .unwrap_err(); + assert_snapshot!( + err.to_string(), + @"The username can't be set both in the publish URL and in the CLI" + ); + + // Ok: The username and password are the same between CLI/env vars and URL + let (publish_url, username, password) = credentials( + example_url_username_password.clone(), + Some("ferris".to_string()), + Some("f3rr1s".to_string()), + ) + .await + .unwrap(); + assert_eq!(publish_url, example_url); + assert_eq!(username.as_deref(), Some("ferris")); + assert_eq!(password.as_deref(), Some("f3rr1s")); + + // Err: There are two different passwords between CLI/env vars and URL + let err = credentials( + example_url_username_password.clone(), + Some("ferris".to_string()), + Some("secret".to_string()), + ) + .await + .unwrap_err(); + assert_snapshot!( + err.to_string(), + @"The password can't be set both in the publish URL and in the CLI" + ); + } +} diff --git a/crates/uv/src/commands/python/list.rs b/crates/uv/src/commands/python/list.rs index 899028801d4a..44951c3483fb 100644 --- a/crates/uv/src/commands/python/list.rs +++ b/crates/uv/src/commands/python/list.rs @@ -132,11 +132,11 @@ pub(crate) async fn list( // Only show the latest patch version for each download unless all were requested if !matches!(kind, Kind::System) { - if let [major, minor, ..] = key.version().release() { + if let [major, minor, ..] = *key.version().release() { if !seen_minor.insert(( *key.os(), - *major, - *minor, + major, + minor, key.variant(), key.implementation(), *key.arch(), @@ -147,12 +147,12 @@ pub(crate) async fn list( } } } - if let [major, minor, patch] = key.version().release() { + if let [major, minor, patch] = *key.version().release() { if !seen_patch.insert(( *key.os(), - *major, - *minor, - *patch, + major, + minor, + patch, key.variant(), key.implementation(), *key.arch(), diff --git a/crates/uv/src/commands/reporters.rs b/crates/uv/src/commands/reporters.rs index 0ceef7f8dac3..1e3fdb1e50cd 100644 --- a/crates/uv/src/commands/reporters.rs +++ b/crates/uv/src/commands/reporters.rs @@ -6,6 +6,7 @@ use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; use owo_colors::OwoColorize; use rustc_hash::FxHashMap; use url::Url; + use uv_cache::Removal; use uv_distribution_types::{ BuildableSource, CachedDist, DistributionMetadata, Name, SourceDist, VersionOrUrlRef, diff --git a/crates/uv/src/commands/tool/common.rs b/crates/uv/src/commands/tool/common.rs index 495e7467a161..3e2f283eb75b 100644 --- a/crates/uv/src/commands/tool/common.rs +++ b/crates/uv/src/commands/tool/common.rs @@ -1,25 +1,32 @@ -use std::fmt::Write; -use std::{collections::BTreeSet, ffi::OsString}; - use anyhow::{bail, Context}; use itertools::Itertools; use owo_colors::OwoColorize; +use std::collections::Bound; +use std::fmt::Write; +use std::{collections::BTreeSet, ffi::OsString}; use tracing::{debug, warn}; - +use uv_cache::Cache; +use uv_client::BaseClientBuilder; use uv_distribution_types::{InstalledDist, Name}; #[cfg(unix)] use uv_fs::replace_symlink; use uv_fs::Simplified; use uv_installer::SitePackages; +use uv_pep440::{Version, VersionSpecifier, VersionSpecifiers}; use uv_pep508::PackageName; use uv_pypi_types::Requirement; -use uv_python::PythonEnvironment; -use uv_settings::ToolOptions; +use uv_python::{ + EnvironmentPreference, Interpreter, PythonDownloads, PythonEnvironment, PythonInstallation, + PythonPreference, PythonRequest, PythonVariant, VersionRequest, +}; +use uv_settings::{PythonInstallMirrors, ToolOptions}; use uv_shell::Shell; use uv_tool::{entrypoint_paths, tool_executable_dir, InstalledTools, Tool, ToolEntrypoint}; use uv_warnings::warn_user; -use crate::commands::ExitStatus; +use crate::commands::project::ProjectError; +use crate::commands::reporters::PythonDownloadReporter; +use crate::commands::{pip, ExitStatus}; use crate::printer::Printer; /// Return all packages which contain an executable with the given name. @@ -61,6 +68,95 @@ pub(crate) fn remove_entrypoints(tool: &Tool) { } } +/// Given a no-solution error and the [`Interpreter`] that was used during the solve, attempt to +/// discover an alternate [`Interpreter`] that satisfies the `requires-python` constraint. +pub(crate) async fn refine_interpreter( + interpreter: &Interpreter, + python_request: Option<&PythonRequest>, + err: &pip::operations::Error, + client_builder: &BaseClientBuilder<'_>, + reporter: &PythonDownloadReporter, + install_mirrors: &PythonInstallMirrors, + python_preference: PythonPreference, + python_downloads: PythonDownloads, + cache: &Cache, +) -> anyhow::Result, ProjectError> { + let pip::operations::Error::Resolve(uv_resolver::ResolveError::NoSolution(ref no_solution_err)) = + err + else { + return Ok(None); + }; + + // Infer the `requires-python` constraint from the error. + let requires_python = no_solution_err.find_requires_python(); + + // If the existing interpreter already satisfies the `requires-python` constraint, we don't need + // to refine it. We'd expect to fail again anyway. + if requires_python.contains(interpreter.python_version()) { + return Ok(None); + } + + // If the user passed a `--python` request, and the refined interpreter is incompatible, we + // can't use it. + if let Some(python_request) = python_request { + if !python_request.satisfied(interpreter, cache) { + return Ok(None); + } + } + + // We want an interpreter that's as close to the required version as possible. If we choose the + // "latest" Python, we risk choosing a version that lacks wheels for the tool's requirements + // (assuming those requirements don't publish source distributions). + // + // TODO(charlie): Solve for the Python version iteratively (or even, within the resolver + // itself). The current strategy can also fail if the tool's requirements have greater + // `requires-python` constraints, and we didn't see them in the initial solve. It can also fail + // if the tool's requirements don't publish wheels for this interpreter version, though that's + // rarer. + let lower_bound = match requires_python.as_ref() { + Bound::Included(version) => VersionSpecifier::greater_than_equal_version(version.clone()), + Bound::Excluded(version) => VersionSpecifier::greater_than_version(version.clone()), + Bound::Unbounded => unreachable!("`requires-python` should never be unbounded"), + }; + + let upper_bound = match requires_python.as_ref() { + Bound::Included(version) => { + let major = version.release().first().copied().unwrap_or(0); + let minor = version.release().get(1).copied().unwrap_or(0); + VersionSpecifier::less_than_version(Version::new([major, minor + 1])) + } + Bound::Excluded(version) => { + let major = version.release().first().copied().unwrap_or(0); + let minor = version.release().get(1).copied().unwrap_or(0); + VersionSpecifier::less_than_version(Version::new([major, minor + 1])) + } + Bound::Unbounded => unreachable!("`requires-python` should never be unbounded"), + }; + + let python_request = PythonRequest::Version(VersionRequest::Range( + VersionSpecifiers::from_iter([lower_bound, upper_bound]), + PythonVariant::default(), + )); + + debug!("Refining interpreter with: {python_request}"); + + let interpreter = PythonInstallation::find_or_download( + Some(&python_request), + EnvironmentPreference::OnlySystem, + python_preference, + python_downloads, + client_builder, + cache, + Some(reporter), + install_mirrors.python_install_mirror.as_deref(), + install_mirrors.pypy_install_mirror.as_deref(), + ) + .await? + .into_interpreter(); + + Ok(Some(interpreter)) +} + /// Installs tool executables for a given package and handles any conflicts. pub(crate) fn install_executables( environment: &PythonEnvironment, diff --git a/crates/uv/src/commands/tool/install.rs b/crates/uv/src/commands/tool/install.rs index f36d9a19bf5f..958dd30bcb50 100644 --- a/crates/uv/src/commands/tool/install.rs +++ b/crates/uv/src/commands/tool/install.rs @@ -29,12 +29,10 @@ use crate::commands::project::{ resolve_environment, resolve_names, sync_environment, update_environment, EnvironmentSpecification, ProjectError, }; -use crate::commands::tool::common::remove_entrypoints; +use crate::commands::tool::common::{install_executables, refine_interpreter, remove_entrypoints}; use crate::commands::tool::Target; use crate::commands::ExitStatus; -use crate::commands::{ - diagnostics, reporters::PythonDownloadReporter, tool::common::install_executables, -}; +use crate::commands::{diagnostics, reporters::PythonDownloadReporter}; use crate::printer::Printer; use crate::settings::ResolverInstallerSettings; @@ -448,10 +446,12 @@ pub(crate) async fn install( environment } else { + let spec = EnvironmentSpecification::from(spec); + // If we're creating a new environment, ensure that we can resolve the requirements prior // to removing any existing tools. - let resolution = match resolve_environment( - EnvironmentSpecification::from(spec), + let resolution = resolve_environment( + spec.clone(), &interpreter, settings.as_ref().into(), &state, @@ -464,15 +464,71 @@ pub(crate) async fn install( printer, preview, ) - .await - { + .await; + + // If the resolution failed, retry with the inferred `requires-python` constraint. + let resolution = match resolution { Ok(resolution) => resolution, - Err(ProjectError::Operation(err)) => { - return diagnostics::OperationDiagnostic::default() - .report(err) - .map_or(Ok(ExitStatus::Failure), |err| Err(err.into())) - } - Err(err) => return Err(err.into()), + Err(err) => match err { + ProjectError::Operation(err) => { + // If the resolution failed due to the discovered interpreter not satisfying the + // `requires-python` constraint, we can try to refine the interpreter. + // + // For example, if we discovered a Python 3.8 interpreter on the user's machine, + // but the tool requires Python 3.10 or later, we can try to download a + // Python 3.10 interpreter and re-resolve. + let Some(interpreter) = refine_interpreter( + &interpreter, + python_request.as_ref(), + &err, + &client_builder, + &reporter, + &install_mirrors, + python_preference, + python_downloads, + &cache, + ) + .await + .ok() + .flatten() else { + return diagnostics::OperationDiagnostic::default() + .report(err) + .map_or(Ok(ExitStatus::Failure), |err| Err(err.into())); + }; + + debug!( + "Re-resolving with Python {} (`{}`)", + interpreter.python_version(), + interpreter.sys_executable().display() + ); + + match resolve_environment( + spec, + &interpreter, + settings.as_ref().into(), + &state, + Box::new(DefaultResolveLogger), + connectivity, + concurrency, + native_tls, + allow_insecure_host, + &cache, + printer, + preview, + ) + .await + { + Ok(resolution) => resolution, + Err(ProjectError::Operation(err)) => { + return diagnostics::OperationDiagnostic::default() + .report(err) + .map_or(Ok(ExitStatus::Failure), |err| Err(err.into())); + } + Err(err) => return Err(err.into()), + } + } + err => return Err(err.into()), + }, }; let environment = installed_tools.create_environment(&from.name, interpreter)?; diff --git a/crates/uv/src/commands/tool/list.rs b/crates/uv/src/commands/tool/list.rs index 35e5860af239..721ed9de25be 100644 --- a/crates/uv/src/commands/tool/list.rs +++ b/crates/uv/src/commands/tool/list.rs @@ -51,7 +51,14 @@ pub(crate) async fn list( let version = match installed_tools.version(&name, cache) { Ok(version) => version, Err(e) => { - writeln!(printer.stderr(), "{e}")?; + if let uv_tool::Error::EnvironmentError(e) = e { + warn_user!( + "{e} (run `{}` to reinstall)", + format!("uv tool install {name} --reinstall").green() + ); + } else { + writeln!(printer.stderr(), "{e}")?; + } continue; } }; diff --git a/crates/uv/src/commands/tool/run.rs b/crates/uv/src/commands/tool/run.rs index 8d97bc10485d..79a7a30dab3e 100644 --- a/crates/uv/src/commands/tool/run.rs +++ b/crates/uv/src/commands/tool/run.rs @@ -37,11 +37,10 @@ use crate::commands::pip::loggers::{ }; use crate::commands::project::{resolve_names, EnvironmentSpecification, ProjectError}; use crate::commands::reporters::PythonDownloadReporter; +use crate::commands::tool::common::{matching_packages, refine_interpreter}; use crate::commands::tool::Target; use crate::commands::ExitStatus; -use crate::commands::{ - diagnostics, project::environment::CachedEnvironment, tool::common::matching_packages, -}; +use crate::commands::{diagnostics, project::environment::CachedEnvironment}; use crate::printer::Printer; use crate::settings::ResolverInstallerSettings; @@ -610,20 +609,20 @@ async fn get_or_create_environment( } // Create a `RequirementsSpecification` from the resolved requirements, to avoid re-resolving. - let spec = RequirementsSpecification { + let spec = EnvironmentSpecification::from(RequirementsSpecification { requirements: requirements .into_iter() .map(UnresolvedRequirementSpecification::from) .collect(), ..spec - }; + }); // TODO(zanieb): When implementing project-level tools, discover the project and check if it has the tool. // TODO(zanieb): Determine if we should layer on top of the project environment if it is present. - let environment = CachedEnvironment::get_or_create( - EnvironmentSpecification::from(spec), - interpreter, + let result = CachedEnvironment::from_spec( + spec.clone(), + &interpreter, settings, &state, if show_resolution { @@ -645,7 +644,70 @@ async fn get_or_create_environment( printer, preview, ) - .await?; + .await; + + let environment = match result { + Ok(environment) => environment, + Err(err) => match err { + ProjectError::Operation(err) => { + // If the resolution failed due to the discovered interpreter not satisfying the + // `requires-python` constraint, we can try to refine the interpreter. + // + // For example, if we discovered a Python 3.8 interpreter on the user's machine, + // but the tool requires Python 3.10 or later, we can try to download a + // Python 3.10 interpreter and re-resolve. + let Some(interpreter) = refine_interpreter( + &interpreter, + python_request.as_ref(), + &err, + &client_builder, + &reporter, + &install_mirrors, + python_preference, + python_downloads, + cache, + ) + .await + .ok() + .flatten() else { + return Err(err.into()); + }; + + debug!( + "Re-resolving with Python {} (`{}`)", + interpreter.python_version(), + interpreter.sys_executable().display() + ); + + CachedEnvironment::from_spec( + spec, + &interpreter, + settings, + &state, + if show_resolution { + Box::new(DefaultResolveLogger) + } else { + Box::new(SummaryResolveLogger) + }, + if show_resolution { + Box::new(DefaultInstallLogger) + } else { + Box::new(SummaryInstallLogger) + }, + installer_metadata, + connectivity, + concurrency, + native_tls, + allow_insecure_host, + cache, + printer, + preview, + ) + .await? + } + err => return Err(err), + }, + }; Ok((from, environment.into())) } diff --git a/crates/uv/src/commands/venv.rs b/crates/uv/src/commands/venv.rs index e8f06088453d..325f3cda5ff8 100644 --- a/crates/uv/src/commands/venv.rs +++ b/crates/uv/src/commands/venv.rs @@ -26,7 +26,7 @@ use uv_python::{ use uv_resolver::{ExcludeNewer, FlatIndex}; use uv_settings::PythonInstallMirrors; use uv_shell::{shlex_posix, shlex_windows, Shell}; -use uv_types::{AnyErrorBuild, BuildContext, BuildIsolation, HashStrategy}; +use uv_types::{AnyErrorBuild, BuildContext, BuildIsolation, BuildStack, HashStrategy}; use uv_warnings::{warn_user, warn_user_once}; use uv_workspace::{DiscoveryOptions, VirtualProject, WorkspaceError}; @@ -353,16 +353,18 @@ async fn venv_impl( )] }; + let build_stack = BuildStack::default(); + // Resolve and install the requirements. // // Since the virtual environment is empty, and the set of requirements is trivial (no // constraints, no editables, etc.), we can use the build dispatch APIs directly. let resolution = build_dispatch - .resolve(&requirements) + .resolve(&requirements, &build_stack) .await .map_err(|err| VenvError::Seed(err.into()))?; let installed = build_dispatch - .install(&resolution, &venv) + .install(&resolution, &venv, &build_stack) .await .map_err(|err| VenvError::Seed(err.into()))?; diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 4f06914eaf3a..aeabb69cde8c 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -4,6 +4,7 @@ use std::fmt::Write; use std::io::stdout; use std::path::Path; use std::process::ExitCode; +use std::str::FromStr; use std::sync::atomic::Ordering; use anstream::eprintln; @@ -11,6 +12,7 @@ use anyhow::{bail, Context, Result}; use clap::error::{ContextKind, ContextValue}; use clap::{CommandFactory, Parser}; use commands::{add_credentials, list_credentials, unset_credentials}; +use futures::FutureExt; use owo_colors::OwoColorize; use settings::{ IndexAddCredentialsSettings, IndexListCredentialsSettings, IndexUnsetCredentialsSettings, @@ -124,7 +126,7 @@ async fn run(mut cli: Cli) -> Result { Some(FilesystemOptions::from_file(config_file)?) } else if deprecated_isolated || cli.top_level.no_config { None - } else if matches!(&*cli.command, Commands::Tool(_)) { + } else if matches!(&*cli.command, Commands::Tool(_) | Commands::Self_(_)) { // For commands that operate at the user-level, ignore local configuration. FilesystemOptions::user()?.combine(FilesystemOptions::system()?) } else if let Ok(workspace) = @@ -190,6 +192,24 @@ async fn run(mut cli: Cli) -> Result { script: Some(script), .. }) = &**command + { + Pep723Script::read(&script).await?.map(Pep723Item::Script) + } else if let ProjectCommand::Lock(uv_cli::LockArgs { + script: Some(script), + .. + }) = &**command + { + Pep723Script::read(&script).await?.map(Pep723Item::Script) + } else if let ProjectCommand::Tree(uv_cli::TreeArgs { + script: Some(script), + .. + }) = &**command + { + Pep723Script::read(&script).await?.map(Pep723Item::Script) + } else if let ProjectCommand::Export(uv_cli::ExportArgs { + script: Some(script), + .. + }) = &**command { Pep723Script::read(&script).await?.map(Pep723Item::Script) } else { @@ -215,6 +235,16 @@ async fn run(mut cli: Cli) -> Result { // Resolve the cache settings. let cache_settings = CacheSettings::resolve(*cli.top_level.cache_args, filesystem.as_ref()); + // Enforce the required version. + if let Some(required_version) = globals.required_version.as_ref() { + let package_version = uv_pep440::Version::from_str(uv_version::version())?; + if !required_version.contains(&package_version) { + return Err(anyhow::anyhow!( + "Required version `{required_version}` does not match the running version `{package_version}`", + )); + } + } + // Configure the `tracing` crate, which controls internal logging. #[cfg(feature = "tracing-durations-export")] let (duration_layer, _duration_guard) = logging::setup_duration()?; @@ -934,7 +964,7 @@ async fn run(mut cli: Cli) -> Result { ) .collect::>(); - commands::tool_run( + Box::pin(commands::tool_run( args.command, args.from, &requirements, @@ -954,7 +984,7 @@ async fn run(mut cli: Cli) -> Result { cache, printer, globals.preview, - ) + )) .await } Commands::Tool(ToolNamespace { @@ -1559,7 +1589,14 @@ async fn run_project( .combine(Refresh::from(args.settings.upgrade.clone())), ); - commands::lock( + // Unwrap the script. + let script = script.map(|script| match script { + Pep723Item::Script(script) => script, + Pep723Item::Stdin(_) => unreachable!("`uv lock` does not support stdin"), + Pep723Item::Remote(_) => unreachable!("`uv lock` does not support remote files"), + }); + + Box::pin(commands::lock( project_dir, args.locked, args.frozen, @@ -1567,6 +1604,7 @@ async fn run_project( args.python, args.install_mirrors, args.settings, + script, globals.python_preference, globals.python_downloads, globals.connectivity, @@ -1577,7 +1615,7 @@ async fn run_project( &cache, printer, globals.preview, - ) + )) .await } ProjectCommand::Add(args) => { @@ -1689,7 +1727,14 @@ async fn run_project( // Initialize the cache. let cache = cache.init()?; - commands::tree( + // Unwrap the script. + let script = script.map(|script| match script { + Pep723Item::Script(script) => script, + Pep723Item::Stdin(_) => unreachable!("`uv tree` does not support stdin"), + Pep723Item::Remote(_) => unreachable!("`uv tree` does not support remote files"), + }); + + Box::pin(commands::tree( project_dir, args.dev, args.locked, @@ -1706,6 +1751,7 @@ async fn run_project( args.python, args.install_mirrors, args.resolver, + script, globals.python_preference, globals.python_downloads, globals.connectivity, @@ -1716,7 +1762,7 @@ async fn run_project( &cache, printer, globals.preview, - ) + )) .await } ProjectCommand::Export(args) => { @@ -1727,6 +1773,13 @@ async fn run_project( // Initialize the cache. let cache = cache.init()?; + // Unwrap the script. + let script = script.map(|script| match script { + Pep723Item::Script(script) => script, + Pep723Item::Stdin(_) => unreachable!("`uv export` does not support stdin"), + Pep723Item::Remote(_) => unreachable!("`uv export` does not support remote files"), + }); + commands::export( project_dir, args.format, @@ -1742,6 +1795,7 @@ async fn run_project( args.locked, args.frozen, args.include_header, + script, args.python, args.install_mirrors, args.settings, @@ -1757,6 +1811,7 @@ async fn run_project( printer, globals.preview, ) + .boxed_local() .await } } diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index e25da1800038..a04f2e01c7d9 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -5,6 +5,7 @@ use std::process; use std::str::FromStr; use url::Url; + use uv_cache::{CacheArgs, Refresh}; use uv_cli::comma::CommaSeparatedRequirements; use uv_cli::{ @@ -24,8 +25,8 @@ use uv_client::Connectivity; use uv_configuration::{ BuildOptions, Concurrency, ConfigSettings, DevGroupsSpecification, EditableMode, ExportFormat, ExtrasSpecification, HashCheckingMode, IndexStrategy, InstallOptions, KeyringProviderType, - NoBinary, NoBuild, PreviewMode, ProjectBuildBackend, Reinstall, SourceStrategy, TargetTriple, - TrustedHost, TrustedPublishing, Upgrade, VersionControlSystem, + NoBinary, NoBuild, PreviewMode, ProjectBuildBackend, Reinstall, RequiredVersion, + SourceStrategy, TargetTriple, TrustedHost, TrustedPublishing, Upgrade, VersionControlSystem, }; use uv_distribution_types::{DependencyMetadata, Index, IndexLocations, IndexUrl}; use uv_install_wheel::linker::LinkMode; @@ -54,6 +55,7 @@ const PYPI_PUBLISH_URL: &str = "https://upload.pypi.org/legacy/"; #[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct GlobalSettings { + pub(crate) required_version: Option, pub(crate) quiet: bool, pub(crate) verbose: u8, pub(crate) color: ColorChoice, @@ -73,6 +75,8 @@ impl GlobalSettings { /// Resolve the [`GlobalSettings`] from the CLI and filesystem configuration. pub(crate) fn resolve(args: &GlobalArgs, workspace: Option<&FilesystemOptions>) -> Self { Self { + required_version: workspace + .and_then(|workspace| workspace.globals.required_version.clone()), quiet: args.quiet, verbose: args.verbose, color: if let Some(color_choice) = args.color { @@ -1020,6 +1024,7 @@ pub(crate) struct LockSettings { pub(crate) locked: bool, pub(crate) frozen: bool, pub(crate) dry_run: bool, + pub(crate) script: Option, pub(crate) python: Option, pub(crate) install_mirrors: PythonInstallMirrors, pub(crate) refresh: Refresh, @@ -1034,6 +1039,7 @@ impl LockSettings { check, check_exists, dry_run, + script, resolver, build, refresh, @@ -1049,6 +1055,7 @@ impl LockSettings { locked: check, frozen: check_exists, dry_run, + script, python: python.and_then(Maybe::into_option), refresh: Refresh::from(refresh), settings: ResolverSettings::combine(resolver_options(resolver, build), filesystem), @@ -1249,6 +1256,11 @@ impl RemoveSettings { .map(|fs| fs.install_mirrors.clone()) .unwrap_or_default(); + let packages = packages + .into_iter() + .map(|requirement| requirement.name) + .collect(); + Self { locked, frozen, @@ -1282,6 +1294,8 @@ pub(crate) struct TreeSettings { pub(crate) no_dedupe: bool, pub(crate) invert: bool, pub(crate) outdated: bool, + #[allow(dead_code)] + pub(crate) script: Option, pub(crate) python_version: Option, pub(crate) python_platform: Option, pub(crate) python: Option, @@ -1306,6 +1320,7 @@ impl TreeSettings { frozen, build, resolver, + script, python_version, python_platform, python, @@ -1328,6 +1343,7 @@ impl TreeSettings { no_dedupe: tree.no_dedupe, invert: tree.invert, outdated: tree.outdated, + script, python_version, python_platform, python: python.and_then(Maybe::into_option), @@ -1354,6 +1370,7 @@ pub(crate) struct ExportSettings { pub(crate) locked: bool, pub(crate) frozen: bool, pub(crate) include_header: bool, + pub(crate) script: Option, pub(crate) python: Option, pub(crate) install_mirrors: PythonInstallMirrors, pub(crate) refresh: Refresh, @@ -1394,6 +1411,7 @@ impl ExportSettings { resolver, build, refresh, + script, python, } = args; let install_mirrors = filesystem @@ -1425,6 +1443,7 @@ impl ExportSettings { locked, frozen, include_header: flag(header, no_header).unwrap_or(true), + script, python: python.and_then(Maybe::into_option), refresh: Refresh::from(refresh), settings: ResolverSettings::combine(resolver_options(resolver, build), filesystem), diff --git a/crates/uv/tests/it/common/mod.rs b/crates/uv/tests/it/common/mod.rs index b34a6faaf585..6a20c930e1a0 100644 --- a/crates/uv/tests/it/common/mod.rs +++ b/crates/uv/tests/it/common/mod.rs @@ -32,7 +32,7 @@ use uv_static::EnvVars; // Exclude any packages uploaded after this date. static EXCLUDE_NEWER: &str = "2024-03-25T00:00:00Z"; -pub const PACKSE_VERSION: &str = "0.3.42"; +pub const PACKSE_VERSION: &str = "0.3.44"; /// Using a find links url allows using `--index-url` instead of `--extra-index-url` in tests /// to prevent dependency confusion attacks against our test suite. @@ -922,7 +922,7 @@ impl TestContext { /// For when we add pypy to the test suite. #[allow(clippy::unused_self)] - pub fn python_kind(&self) -> &str { + pub fn python_kind(&self) -> &'static str { "python" } diff --git a/crates/uv/tests/it/edit.rs b/crates/uv/tests/it/edit.rs index 7d57985dec5a..84f8cd2cef51 100644 --- a/crates/uv/tests/it/edit.rs +++ b/crates/uv/tests/it/edit.rs @@ -1,3 +1,5 @@ +#![allow(clippy::disallowed_types)] + use anyhow::Result; use assert_cmd::assert::OutputAssertExt; use assert_fs::prelude::*; @@ -4611,6 +4613,17 @@ fn add_requirements_file() -> Result<()> { ); }); + // Passing stdin should succeed + uv_snapshot!(context.filters(), context.add().arg("-r").arg("-").stdin(std::fs::File::open(requirements_txt)?), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved [N] packages in [TIME] + Audited [N] packages in [TIME] + "###); + // Passing a `setup.py` should fail. uv_snapshot!(context.filters(), context.add().arg("-r").arg("setup.py"), @r###" success: false @@ -4929,6 +4942,10 @@ fn add_script() -> Result<()> { "### ); }); + + // Adding to a script without a lockfile shouldn't create a lockfile. + assert!(!context.temp_dir.join("script.py.lock").exists()); + Ok(()) } @@ -5330,9 +5347,9 @@ fn remove_repeated() -> Result<()> { Ok(()) } -/// Remove from a PEP732 script, +/// Add to (and remove from) a PEP 732 script with a lockfile. #[test] -fn remove_script() -> Result<()> { +fn add_remove_script_lock() -> Result<()> { let context = TestContext::new("3.12"); let script = context.temp_dir.child("script.py"); @@ -5342,7 +5359,6 @@ fn remove_script() -> Result<()> { # dependencies = [ # "requests<3", # "rich", - # "anyio", # ] # /// @@ -5354,71 +5370,170 @@ fn remove_script() -> Result<()> { pprint([(k, v["title"]) for k, v in data.items()][:10]) "#})?; - uv_snapshot!(context.filters(), context.remove().arg("anyio").arg("--script").arg("script.py"), @r###" + // Explicitly lock the script. + uv_snapshot!(context.filters(), context.lock().arg("--script").arg("script.py"), @r###" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - Updated `script.py` + Resolved 9 packages in [TIME] "###); - let script_content = context.read("script.py"); + let lock = context.read("script.py.lock"); insta::with_settings!({ filters => context.filters(), }, { assert_snapshot!( - script_content, @r###" - # /// script - # requires-python = ">=3.11" - # dependencies = [ - # "requests<3", - # "rich", - # ] - # /// + lock, @r###" + version = 1 + requires-python = ">=3.11" - import requests - from rich.pretty import pprint + [options] + exclude-newer = "2024-03-25T00:00:00Z" - resp = requests.get("https://peps.python.org/api/peps.json") - data = resp.json() - pprint([(k, v["title"]) for k, v in data.items()][:10]) - "### - ); - }); - Ok(()) -} + [manifest] + requirements = [ + { name = "requests", specifier = "<3" }, + { name = "rich" }, + ] -/// Remove last dependency PEP732 script -#[test] -fn remove_last_dep_script() -> Result<()> { - let context = TestContext::new("3.12"); + [[package]] + name = "certifi" + version = "2024.2.2" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/71/da/e94e26401b62acd6d91df2b52954aceb7f561743aa5ccc32152886c76c96/certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f", size = 164886 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/06/a07f096c664aeb9f01624f858c3add0a4e913d6c96257acb4fce61e7de14/certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1", size = 163774 }, + ] - let script = context.temp_dir.child("script.py"); - script.write_str(indoc! {r#" - # /// script - # requires-python = ">=3.11" - # dependencies = [ - # "rich", - # ] - # /// + [[package]] + name = "charset-normalizer" + version = "3.3.2" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/63/09/c1bc53dab74b1816a00d8d030de5bf98f724c52c1635e07681d312f20be8/charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", size = 104809 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/68/77/02839016f6fbbf808e8b38601df6e0e66c17bbab76dff4613f7511413597/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", size = 191647 }, + { url = "https://files.pythonhosted.org/packages/3e/33/21a875a61057165e92227466e54ee076b73af1e21fe1b31f1e292251aa1e/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", size = 121434 }, + { url = "https://files.pythonhosted.org/packages/dd/51/68b61b90b24ca35495956b718f35a9756ef7d3dd4b3c1508056fa98d1a1b/charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", size = 118979 }, + { url = "https://files.pythonhosted.org/packages/e4/a6/7ee57823d46331ddc37dd00749c95b0edec2c79b15fc0d6e6efb532e89ac/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", size = 136582 }, + { url = "https://files.pythonhosted.org/packages/74/f1/0d9fe69ac441467b737ba7f48c68241487df2f4522dd7246d9426e7c690e/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", size = 146645 }, + { url = "https://files.pythonhosted.org/packages/05/31/e1f51c76db7be1d4aef220d29fbfa5dbb4a99165d9833dcbf166753b6dc0/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", size = 139398 }, + { url = "https://files.pythonhosted.org/packages/40/26/f35951c45070edc957ba40a5b1db3cf60a9dbb1b350c2d5bef03e01e61de/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", size = 140273 }, + { url = "https://files.pythonhosted.org/packages/07/07/7e554f2bbce3295e191f7e653ff15d55309a9ca40d0362fcdab36f01063c/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", size = 142577 }, + { url = "https://files.pythonhosted.org/packages/d8/b5/eb705c313100defa57da79277d9207dc8d8e45931035862fa64b625bfead/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", size = 137747 }, + { url = "https://files.pythonhosted.org/packages/19/28/573147271fd041d351b438a5665be8223f1dd92f273713cb882ddafe214c/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", size = 143375 }, + { url = "https://files.pythonhosted.org/packages/cf/7c/f3b682fa053cc21373c9a839e6beba7705857075686a05c72e0f8c4980ca/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", size = 148474 }, + { url = "https://files.pythonhosted.org/packages/1e/49/7ab74d4ac537ece3bc3334ee08645e231f39f7d6df6347b29a74b0537103/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", size = 140232 }, + { url = "https://files.pythonhosted.org/packages/2d/dc/9dacba68c9ac0ae781d40e1a0c0058e26302ea0660e574ddf6797a0347f7/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", size = 140859 }, + { url = "https://files.pythonhosted.org/packages/6c/c2/4a583f800c0708dd22096298e49f887b49d9746d0e78bfc1d7e29816614c/charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", size = 92509 }, + { url = "https://files.pythonhosted.org/packages/57/ec/80c8d48ac8b1741d5b963797b7c0c869335619e13d4744ca2f67fc11c6fc/charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", size = 99870 }, + { url = "https://files.pythonhosted.org/packages/d1/b2/fcedc8255ec42afee97f9e6f0145c734bbe104aac28300214593eb326f1d/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", size = 192892 }, + { url = "https://files.pythonhosted.org/packages/2e/7d/2259318c202f3d17f3fe6438149b3b9e706d1070fe3fcbb28049730bb25c/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", size = 122213 }, + { url = "https://files.pythonhosted.org/packages/3a/52/9f9d17c3b54dc238de384c4cb5a2ef0e27985b42a0e5cc8e8a31d918d48d/charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", size = 119404 }, + { url = "https://files.pythonhosted.org/packages/99/b0/9c365f6d79a9f0f3c379ddb40a256a67aa69c59609608fe7feb6235896e1/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", size = 137275 }, + { url = "https://files.pythonhosted.org/packages/91/33/749df346e93d7a30cdcb90cbfdd41a06026317bfbfb62cd68307c1a3c543/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", size = 147518 }, + { url = "https://files.pythonhosted.org/packages/72/1a/641d5c9f59e6af4c7b53da463d07600a695b9824e20849cb6eea8a627761/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", size = 140182 }, + { url = "https://files.pythonhosted.org/packages/ee/fb/14d30eb4956408ee3ae09ad34299131fb383c47df355ddb428a7331cfa1e/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", size = 141869 }, + { url = "https://files.pythonhosted.org/packages/df/3e/a06b18788ca2eb6695c9b22325b6fde7dde0f1d1838b1792a0076f58fe9d/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", size = 144042 }, + { url = "https://files.pythonhosted.org/packages/45/59/3d27019d3b447a88fe7e7d004a1e04be220227760264cc41b405e863891b/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", size = 138275 }, + { url = "https://files.pythonhosted.org/packages/7b/ef/5eb105530b4da8ae37d506ccfa25057961b7b63d581def6f99165ea89c7e/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", size = 144819 }, + { url = "https://files.pythonhosted.org/packages/a2/51/e5023f937d7f307c948ed3e5c29c4b7a3e42ed2ee0b8cdf8f3a706089bf0/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", size = 149415 }, + { url = "https://files.pythonhosted.org/packages/24/9d/2e3ef673dfd5be0154b20363c5cdcc5606f35666544381bee15af3778239/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", size = 141212 }, + { url = "https://files.pythonhosted.org/packages/5b/ae/ce2c12fcac59cb3860b2e2d76dc405253a4475436b1861d95fe75bdea520/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", size = 142167 }, + { url = "https://files.pythonhosted.org/packages/ed/3a/a448bf035dce5da359daf9ae8a16b8a39623cc395a2ffb1620aa1bce62b0/charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", size = 93041 }, + { url = "https://files.pythonhosted.org/packages/b6/7c/8debebb4f90174074b827c63242c23851bdf00a532489fba57fef3416e40/charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", size = 100397 }, + { url = "https://files.pythonhosted.org/packages/28/76/e6222113b83e3622caa4bb41032d0b1bf785250607392e1b778aca0b8a7d/charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", size = 48543 }, + ] - import requests - from rich.pretty import pprint + [[package]] + name = "idna" + version = "3.6" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", size = 175426 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f", size = 61567 }, + ] - resp = requests.get("https://peps.python.org/api/peps.json") - data = resp.json() - pprint([(k, v["title"]) for k, v in data.items()][:10]) - "#})?; + [[package]] + name = "markdown-it-py" + version = "3.0.0" + source = { registry = "https://pypi.org/simple" } + dependencies = [ + { name = "mdurl" }, + ] + sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, + ] - uv_snapshot!(context.filters(), context.remove().arg("rich").arg("--script").arg("script.py"), @r###" + [[package]] + name = "mdurl" + version = "0.1.2" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, + ] + + [[package]] + name = "pygments" + version = "2.17.2" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/55/59/8bccf4157baf25e4aa5a0bb7fa3ba8600907de105ebc22b0c78cfbf6f565/pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367", size = 4827772 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/97/9c/372fef8377a6e340b1704768d20daaded98bf13282b5327beb2e2fe2c7ef/pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c", size = 1179756 }, + ] + + [[package]] + name = "requests" + version = "2.31.0" + source = { registry = "https://pypi.org/simple" } + dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, + ] + sdist = { url = "https://files.pythonhosted.org/packages/9d/be/10918a2eac4ae9f02f6cfe6414b7a155ccd8f7f9d4380d62fd5b955065c3/requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1", size = 110794 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/70/8e/0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44/requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", size = 62574 }, + ] + + [[package]] + name = "rich" + version = "13.7.1" + source = { registry = "https://pypi.org/simple" } + dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, + ] + sdist = { url = "https://files.pythonhosted.org/packages/b3/01/c954e134dc440ab5f96952fe52b4fdc64225530320a910473c1fe270d9aa/rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432", size = 221248 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/87/67/a37f6214d0e9fe57f6ae54b2956d550ca8365857f42a1ce0392bb21d9410/rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222", size = 240681 }, + ] + + [[package]] + name = "urllib3" + version = "2.2.1" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/7a/50/7fd50a27caa0652cd4caf224aa87741ea41d3265ad13f010886167cfcc79/urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19", size = 291020 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/73/a68704750a7679d0b6d3ad7aa8d4da8e14e151ae82e6fee774e6e0d05ec8/urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d", size = 121067 }, + ] + "### + ); + }); + + // Adding to a locked script should update the lockfile. + uv_snapshot!(context.filters(), context.add().arg("anyio").arg("--script").arg("script.py"), @r###" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - Updated `script.py` + Resolved 11 packages in [TIME] "###); let script_content = context.read("script.py"); @@ -5430,7 +5545,11 @@ fn remove_last_dep_script() -> Result<()> { script_content, @r###" # /// script # requires-python = ">=3.11" - # dependencies = [] + # dependencies = [ + # "anyio>=4.3.0", + # "requests<3", + # "rich", + # ] # /// import requests @@ -5442,94 +5561,560 @@ fn remove_last_dep_script() -> Result<()> { "### ); }); - Ok(()) -} - -/// Add a Git requirement to PEP732 script. -#[test] -#[cfg(feature = "git")] -fn add_git_to_script() -> Result<()> { - let context = TestContext::new("3.12"); - let script = context.temp_dir.child("script.py"); - script.write_str(indoc! {r#" - # /// script - # requires-python = ">=3.11" - # dependencies = [ - # "anyio", - # ] - # /// - - import anyio - import uv_public_pypackage - "#})?; - - uv_snapshot!(context.filters(), context - .add() - .arg("uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage") - .arg("--tag=0.0.1") - .arg("--script") - .arg("script.py"), @r###" - success: true - exit_code: 0 - ----- stdout ----- - - ----- stderr ----- - Updated `script.py` - "###); - - let script_content = context.read("script.py"); + let lock = context.read("script.py.lock"); insta::with_settings!({ filters => context.filters(), }, { assert_snapshot!( - script_content, @r###" - # /// script - # requires-python = ">=3.11" - # dependencies = [ - # "anyio", - # "uv-public-pypackage", - # ] - # - # [tool.uv.sources] - # uv-public-pypackage = { git = "https://github.com/astral-test/uv-public-pypackage", tag = "0.0.1" } - # /// - - import anyio - import uv_public_pypackage - "### - ); - }); + lock, @r###" + version = 1 + requires-python = ">=3.11" - // Ensure that the script runs without error. - context.run().arg("script.py").assert().success(); + [options] + exclude-newer = "2024-03-25T00:00:00Z" - Ok(()) -} + [manifest] + requirements = [ + { name = "anyio", specifier = ">=4.3.0" }, + { name = "requests", specifier = "<3" }, + { name = "rich" }, + ] -/// Revert changes to the `pyproject.toml` and `uv.lock` when the `add` operation fails. -#[test] -fn fail_to_add_revert_project() -> Result<()> { - let context = TestContext::new("3.12"); + [[package]] + name = "anyio" + version = "4.3.0" + source = { registry = "https://pypi.org/simple" } + dependencies = [ + { name = "idna" }, + { name = "sniffio" }, + ] + sdist = { url = "https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6", size = 159642 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8", size = 85584 }, + ] - context - .temp_dir - .child("pyproject.toml") - .write_str(indoc! {r#" - [project] - name = "parent" - version = "0.1.0" - requires-python = ">=3.12" - dependencies = [] - "#})?; + [[package]] + name = "certifi" + version = "2024.2.2" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/71/da/e94e26401b62acd6d91df2b52954aceb7f561743aa5ccc32152886c76c96/certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f", size = 164886 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/06/a07f096c664aeb9f01624f858c3add0a4e913d6c96257acb4fce61e7de14/certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1", size = 163774 }, + ] - // Add a dependency on a package that declares static metadata (so can always resolve), but - // can't be installed. - let pyproject_toml = context.temp_dir.child("child/pyproject.toml"); - pyproject_toml.write_str(indoc! {r#" - [project] - name = "child" + [[package]] + name = "charset-normalizer" + version = "3.3.2" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/63/09/c1bc53dab74b1816a00d8d030de5bf98f724c52c1635e07681d312f20be8/charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", size = 104809 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/68/77/02839016f6fbbf808e8b38601df6e0e66c17bbab76dff4613f7511413597/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", size = 191647 }, + { url = "https://files.pythonhosted.org/packages/3e/33/21a875a61057165e92227466e54ee076b73af1e21fe1b31f1e292251aa1e/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", size = 121434 }, + { url = "https://files.pythonhosted.org/packages/dd/51/68b61b90b24ca35495956b718f35a9756ef7d3dd4b3c1508056fa98d1a1b/charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", size = 118979 }, + { url = "https://files.pythonhosted.org/packages/e4/a6/7ee57823d46331ddc37dd00749c95b0edec2c79b15fc0d6e6efb532e89ac/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", size = 136582 }, + { url = "https://files.pythonhosted.org/packages/74/f1/0d9fe69ac441467b737ba7f48c68241487df2f4522dd7246d9426e7c690e/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", size = 146645 }, + { url = "https://files.pythonhosted.org/packages/05/31/e1f51c76db7be1d4aef220d29fbfa5dbb4a99165d9833dcbf166753b6dc0/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", size = 139398 }, + { url = "https://files.pythonhosted.org/packages/40/26/f35951c45070edc957ba40a5b1db3cf60a9dbb1b350c2d5bef03e01e61de/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", size = 140273 }, + { url = "https://files.pythonhosted.org/packages/07/07/7e554f2bbce3295e191f7e653ff15d55309a9ca40d0362fcdab36f01063c/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", size = 142577 }, + { url = "https://files.pythonhosted.org/packages/d8/b5/eb705c313100defa57da79277d9207dc8d8e45931035862fa64b625bfead/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", size = 137747 }, + { url = "https://files.pythonhosted.org/packages/19/28/573147271fd041d351b438a5665be8223f1dd92f273713cb882ddafe214c/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", size = 143375 }, + { url = "https://files.pythonhosted.org/packages/cf/7c/f3b682fa053cc21373c9a839e6beba7705857075686a05c72e0f8c4980ca/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", size = 148474 }, + { url = "https://files.pythonhosted.org/packages/1e/49/7ab74d4ac537ece3bc3334ee08645e231f39f7d6df6347b29a74b0537103/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", size = 140232 }, + { url = "https://files.pythonhosted.org/packages/2d/dc/9dacba68c9ac0ae781d40e1a0c0058e26302ea0660e574ddf6797a0347f7/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", size = 140859 }, + { url = "https://files.pythonhosted.org/packages/6c/c2/4a583f800c0708dd22096298e49f887b49d9746d0e78bfc1d7e29816614c/charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", size = 92509 }, + { url = "https://files.pythonhosted.org/packages/57/ec/80c8d48ac8b1741d5b963797b7c0c869335619e13d4744ca2f67fc11c6fc/charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", size = 99870 }, + { url = "https://files.pythonhosted.org/packages/d1/b2/fcedc8255ec42afee97f9e6f0145c734bbe104aac28300214593eb326f1d/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", size = 192892 }, + { url = "https://files.pythonhosted.org/packages/2e/7d/2259318c202f3d17f3fe6438149b3b9e706d1070fe3fcbb28049730bb25c/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", size = 122213 }, + { url = "https://files.pythonhosted.org/packages/3a/52/9f9d17c3b54dc238de384c4cb5a2ef0e27985b42a0e5cc8e8a31d918d48d/charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", size = 119404 }, + { url = "https://files.pythonhosted.org/packages/99/b0/9c365f6d79a9f0f3c379ddb40a256a67aa69c59609608fe7feb6235896e1/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", size = 137275 }, + { url = "https://files.pythonhosted.org/packages/91/33/749df346e93d7a30cdcb90cbfdd41a06026317bfbfb62cd68307c1a3c543/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", size = 147518 }, + { url = "https://files.pythonhosted.org/packages/72/1a/641d5c9f59e6af4c7b53da463d07600a695b9824e20849cb6eea8a627761/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", size = 140182 }, + { url = "https://files.pythonhosted.org/packages/ee/fb/14d30eb4956408ee3ae09ad34299131fb383c47df355ddb428a7331cfa1e/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", size = 141869 }, + { url = "https://files.pythonhosted.org/packages/df/3e/a06b18788ca2eb6695c9b22325b6fde7dde0f1d1838b1792a0076f58fe9d/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", size = 144042 }, + { url = "https://files.pythonhosted.org/packages/45/59/3d27019d3b447a88fe7e7d004a1e04be220227760264cc41b405e863891b/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", size = 138275 }, + { url = "https://files.pythonhosted.org/packages/7b/ef/5eb105530b4da8ae37d506ccfa25057961b7b63d581def6f99165ea89c7e/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", size = 144819 }, + { url = "https://files.pythonhosted.org/packages/a2/51/e5023f937d7f307c948ed3e5c29c4b7a3e42ed2ee0b8cdf8f3a706089bf0/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", size = 149415 }, + { url = "https://files.pythonhosted.org/packages/24/9d/2e3ef673dfd5be0154b20363c5cdcc5606f35666544381bee15af3778239/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", size = 141212 }, + { url = "https://files.pythonhosted.org/packages/5b/ae/ce2c12fcac59cb3860b2e2d76dc405253a4475436b1861d95fe75bdea520/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", size = 142167 }, + { url = "https://files.pythonhosted.org/packages/ed/3a/a448bf035dce5da359daf9ae8a16b8a39623cc395a2ffb1620aa1bce62b0/charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", size = 93041 }, + { url = "https://files.pythonhosted.org/packages/b6/7c/8debebb4f90174074b827c63242c23851bdf00a532489fba57fef3416e40/charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", size = 100397 }, + { url = "https://files.pythonhosted.org/packages/28/76/e6222113b83e3622caa4bb41032d0b1bf785250607392e1b778aca0b8a7d/charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", size = 48543 }, + ] + + [[package]] + name = "idna" + version = "3.6" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", size = 175426 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f", size = 61567 }, + ] + + [[package]] + name = "markdown-it-py" + version = "3.0.0" + source = { registry = "https://pypi.org/simple" } + dependencies = [ + { name = "mdurl" }, + ] + sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, + ] + + [[package]] + name = "mdurl" + version = "0.1.2" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, + ] + + [[package]] + name = "pygments" + version = "2.17.2" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/55/59/8bccf4157baf25e4aa5a0bb7fa3ba8600907de105ebc22b0c78cfbf6f565/pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367", size = 4827772 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/97/9c/372fef8377a6e340b1704768d20daaded98bf13282b5327beb2e2fe2c7ef/pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c", size = 1179756 }, + ] + + [[package]] + name = "requests" + version = "2.31.0" + source = { registry = "https://pypi.org/simple" } + dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, + ] + sdist = { url = "https://files.pythonhosted.org/packages/9d/be/10918a2eac4ae9f02f6cfe6414b7a155ccd8f7f9d4380d62fd5b955065c3/requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1", size = 110794 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/70/8e/0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44/requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", size = 62574 }, + ] + + [[package]] + name = "rich" + version = "13.7.1" + source = { registry = "https://pypi.org/simple" } + dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, + ] + sdist = { url = "https://files.pythonhosted.org/packages/b3/01/c954e134dc440ab5f96952fe52b4fdc64225530320a910473c1fe270d9aa/rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432", size = 221248 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/87/67/a37f6214d0e9fe57f6ae54b2956d550ca8365857f42a1ce0392bb21d9410/rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222", size = 240681 }, + ] + + [[package]] + name = "sniffio" + version = "1.3.1" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, + ] + + [[package]] + name = "urllib3" + version = "2.2.1" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/7a/50/7fd50a27caa0652cd4caf224aa87741ea41d3265ad13f010886167cfcc79/urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19", size = 291020 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/73/a68704750a7679d0b6d3ad7aa8d4da8e14e151ae82e6fee774e6e0d05ec8/urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d", size = 121067 }, + ] + "### + ); + }); + + // Removing from a locked script should update the lockfile. + uv_snapshot!(context.filters(), context.remove().arg("anyio").arg("--script").arg("script.py"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 9 packages in [TIME] + "###); + + let script_content = context.read("script.py"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + script_content, @r###" + # /// script + # requires-python = ">=3.11" + # dependencies = [ + # "requests<3", + # "rich", + # ] + # /// + + import requests + from rich.pretty import pprint + + resp = requests.get("https://peps.python.org/api/peps.json") + data = resp.json() + pprint([(k, v["title"]) for k, v in data.items()][:10]) + "### + ); + }); + + let lock = context.read("script.py.lock"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + lock, @r###" + version = 1 + requires-python = ">=3.11" + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [manifest] + requirements = [ + { name = "requests", specifier = "<3" }, + { name = "rich" }, + ] + + [[package]] + name = "certifi" + version = "2024.2.2" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/71/da/e94e26401b62acd6d91df2b52954aceb7f561743aa5ccc32152886c76c96/certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f", size = 164886 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/06/a07f096c664aeb9f01624f858c3add0a4e913d6c96257acb4fce61e7de14/certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1", size = 163774 }, + ] + + [[package]] + name = "charset-normalizer" + version = "3.3.2" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/63/09/c1bc53dab74b1816a00d8d030de5bf98f724c52c1635e07681d312f20be8/charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", size = 104809 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/68/77/02839016f6fbbf808e8b38601df6e0e66c17bbab76dff4613f7511413597/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", size = 191647 }, + { url = "https://files.pythonhosted.org/packages/3e/33/21a875a61057165e92227466e54ee076b73af1e21fe1b31f1e292251aa1e/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", size = 121434 }, + { url = "https://files.pythonhosted.org/packages/dd/51/68b61b90b24ca35495956b718f35a9756ef7d3dd4b3c1508056fa98d1a1b/charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", size = 118979 }, + { url = "https://files.pythonhosted.org/packages/e4/a6/7ee57823d46331ddc37dd00749c95b0edec2c79b15fc0d6e6efb532e89ac/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", size = 136582 }, + { url = "https://files.pythonhosted.org/packages/74/f1/0d9fe69ac441467b737ba7f48c68241487df2f4522dd7246d9426e7c690e/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", size = 146645 }, + { url = "https://files.pythonhosted.org/packages/05/31/e1f51c76db7be1d4aef220d29fbfa5dbb4a99165d9833dcbf166753b6dc0/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", size = 139398 }, + { url = "https://files.pythonhosted.org/packages/40/26/f35951c45070edc957ba40a5b1db3cf60a9dbb1b350c2d5bef03e01e61de/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", size = 140273 }, + { url = "https://files.pythonhosted.org/packages/07/07/7e554f2bbce3295e191f7e653ff15d55309a9ca40d0362fcdab36f01063c/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", size = 142577 }, + { url = "https://files.pythonhosted.org/packages/d8/b5/eb705c313100defa57da79277d9207dc8d8e45931035862fa64b625bfead/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", size = 137747 }, + { url = "https://files.pythonhosted.org/packages/19/28/573147271fd041d351b438a5665be8223f1dd92f273713cb882ddafe214c/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", size = 143375 }, + { url = "https://files.pythonhosted.org/packages/cf/7c/f3b682fa053cc21373c9a839e6beba7705857075686a05c72e0f8c4980ca/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", size = 148474 }, + { url = "https://files.pythonhosted.org/packages/1e/49/7ab74d4ac537ece3bc3334ee08645e231f39f7d6df6347b29a74b0537103/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", size = 140232 }, + { url = "https://files.pythonhosted.org/packages/2d/dc/9dacba68c9ac0ae781d40e1a0c0058e26302ea0660e574ddf6797a0347f7/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", size = 140859 }, + { url = "https://files.pythonhosted.org/packages/6c/c2/4a583f800c0708dd22096298e49f887b49d9746d0e78bfc1d7e29816614c/charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", size = 92509 }, + { url = "https://files.pythonhosted.org/packages/57/ec/80c8d48ac8b1741d5b963797b7c0c869335619e13d4744ca2f67fc11c6fc/charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", size = 99870 }, + { url = "https://files.pythonhosted.org/packages/d1/b2/fcedc8255ec42afee97f9e6f0145c734bbe104aac28300214593eb326f1d/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", size = 192892 }, + { url = "https://files.pythonhosted.org/packages/2e/7d/2259318c202f3d17f3fe6438149b3b9e706d1070fe3fcbb28049730bb25c/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", size = 122213 }, + { url = "https://files.pythonhosted.org/packages/3a/52/9f9d17c3b54dc238de384c4cb5a2ef0e27985b42a0e5cc8e8a31d918d48d/charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", size = 119404 }, + { url = "https://files.pythonhosted.org/packages/99/b0/9c365f6d79a9f0f3c379ddb40a256a67aa69c59609608fe7feb6235896e1/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", size = 137275 }, + { url = "https://files.pythonhosted.org/packages/91/33/749df346e93d7a30cdcb90cbfdd41a06026317bfbfb62cd68307c1a3c543/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", size = 147518 }, + { url = "https://files.pythonhosted.org/packages/72/1a/641d5c9f59e6af4c7b53da463d07600a695b9824e20849cb6eea8a627761/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", size = 140182 }, + { url = "https://files.pythonhosted.org/packages/ee/fb/14d30eb4956408ee3ae09ad34299131fb383c47df355ddb428a7331cfa1e/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", size = 141869 }, + { url = "https://files.pythonhosted.org/packages/df/3e/a06b18788ca2eb6695c9b22325b6fde7dde0f1d1838b1792a0076f58fe9d/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", size = 144042 }, + { url = "https://files.pythonhosted.org/packages/45/59/3d27019d3b447a88fe7e7d004a1e04be220227760264cc41b405e863891b/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", size = 138275 }, + { url = "https://files.pythonhosted.org/packages/7b/ef/5eb105530b4da8ae37d506ccfa25057961b7b63d581def6f99165ea89c7e/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", size = 144819 }, + { url = "https://files.pythonhosted.org/packages/a2/51/e5023f937d7f307c948ed3e5c29c4b7a3e42ed2ee0b8cdf8f3a706089bf0/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", size = 149415 }, + { url = "https://files.pythonhosted.org/packages/24/9d/2e3ef673dfd5be0154b20363c5cdcc5606f35666544381bee15af3778239/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", size = 141212 }, + { url = "https://files.pythonhosted.org/packages/5b/ae/ce2c12fcac59cb3860b2e2d76dc405253a4475436b1861d95fe75bdea520/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", size = 142167 }, + { url = "https://files.pythonhosted.org/packages/ed/3a/a448bf035dce5da359daf9ae8a16b8a39623cc395a2ffb1620aa1bce62b0/charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", size = 93041 }, + { url = "https://files.pythonhosted.org/packages/b6/7c/8debebb4f90174074b827c63242c23851bdf00a532489fba57fef3416e40/charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", size = 100397 }, + { url = "https://files.pythonhosted.org/packages/28/76/e6222113b83e3622caa4bb41032d0b1bf785250607392e1b778aca0b8a7d/charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", size = 48543 }, + ] + + [[package]] + name = "idna" + version = "3.6" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", size = 175426 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f", size = 61567 }, + ] + + [[package]] + name = "markdown-it-py" + version = "3.0.0" + source = { registry = "https://pypi.org/simple" } + dependencies = [ + { name = "mdurl" }, + ] + sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, + ] + + [[package]] + name = "mdurl" + version = "0.1.2" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, + ] + + [[package]] + name = "pygments" + version = "2.17.2" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/55/59/8bccf4157baf25e4aa5a0bb7fa3ba8600907de105ebc22b0c78cfbf6f565/pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367", size = 4827772 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/97/9c/372fef8377a6e340b1704768d20daaded98bf13282b5327beb2e2fe2c7ef/pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c", size = 1179756 }, + ] + + [[package]] + name = "requests" + version = "2.31.0" + source = { registry = "https://pypi.org/simple" } + dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, + ] + sdist = { url = "https://files.pythonhosted.org/packages/9d/be/10918a2eac4ae9f02f6cfe6414b7a155ccd8f7f9d4380d62fd5b955065c3/requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1", size = 110794 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/70/8e/0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44/requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", size = 62574 }, + ] + + [[package]] + name = "rich" + version = "13.7.1" + source = { registry = "https://pypi.org/simple" } + dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, + ] + sdist = { url = "https://files.pythonhosted.org/packages/b3/01/c954e134dc440ab5f96952fe52b4fdc64225530320a910473c1fe270d9aa/rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432", size = 221248 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/87/67/a37f6214d0e9fe57f6ae54b2956d550ca8365857f42a1ce0392bb21d9410/rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222", size = 240681 }, + ] + + [[package]] + name = "urllib3" + version = "2.2.1" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/7a/50/7fd50a27caa0652cd4caf224aa87741ea41d3265ad13f010886167cfcc79/urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19", size = 291020 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/73/a68704750a7679d0b6d3ad7aa8d4da8e14e151ae82e6fee774e6e0d05ec8/urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d", size = 121067 }, + ] + "### + ); + }); + + Ok(()) +} + +/// Remove from a PEP 723 script. +#[test] +fn remove_script() -> Result<()> { + let context = TestContext::new("3.12"); + + let script = context.temp_dir.child("script.py"); + script.write_str(indoc! {r#" + # /// script + # requires-python = ">=3.11" + # dependencies = [ + # "requests<3", + # "rich", + # "anyio", + # ] + # /// + + import requests + from rich.pretty import pprint + + resp = requests.get("https://peps.python.org/api/peps.json") + data = resp.json() + pprint([(k, v["title"]) for k, v in data.items()][:10]) + "#})?; + + uv_snapshot!(context.filters(), context.remove().arg("anyio").arg("--script").arg("script.py"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Updated `script.py` + "###); + + let script_content = context.read("script.py"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + script_content, @r###" + # /// script + # requires-python = ">=3.11" + # dependencies = [ + # "requests<3", + # "rich", + # ] + # /// + + import requests + from rich.pretty import pprint + + resp = requests.get("https://peps.python.org/api/peps.json") + data = resp.json() + pprint([(k, v["title"]) for k, v in data.items()][:10]) + "### + ); + }); + Ok(()) +} + +/// Remove last dependency PEP 723 script +#[test] +fn remove_last_dep_script() -> Result<()> { + let context = TestContext::new("3.12"); + + let script = context.temp_dir.child("script.py"); + script.write_str(indoc! {r#" + # /// script + # requires-python = ">=3.11" + # dependencies = [ + # "rich", + # ] + # /// + + import requests + from rich.pretty import pprint + + resp = requests.get("https://peps.python.org/api/peps.json") + data = resp.json() + pprint([(k, v["title"]) for k, v in data.items()][:10]) + "#})?; + + uv_snapshot!(context.filters(), context.remove().arg("rich").arg("--script").arg("script.py"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Updated `script.py` + "###); + + let script_content = context.read("script.py"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + script_content, @r###" + # /// script + # requires-python = ">=3.11" + # dependencies = [] + # /// + + import requests + from rich.pretty import pprint + + resp = requests.get("https://peps.python.org/api/peps.json") + data = resp.json() + pprint([(k, v["title"]) for k, v in data.items()][:10]) + "### + ); + }); + Ok(()) +} + +/// Add a Git requirement to PEP 723 script. +#[test] +#[cfg(feature = "git")] +fn add_git_to_script() -> Result<()> { + let context = TestContext::new("3.12"); + + let script = context.temp_dir.child("script.py"); + script.write_str(indoc! {r#" + # /// script + # requires-python = ">=3.11" + # dependencies = [ + # "anyio", + # ] + # /// + + import anyio + import uv_public_pypackage + "#})?; + + uv_snapshot!(context.filters(), context + .add() + .arg("uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage") + .arg("--tag=0.0.1") + .arg("--script") + .arg("script.py"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Updated `script.py` + "###); + + let script_content = context.read("script.py"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + script_content, @r###" + # /// script + # requires-python = ">=3.11" + # dependencies = [ + # "anyio", + # "uv-public-pypackage", + # ] + # + # [tool.uv.sources] + # uv-public-pypackage = { git = "https://github.com/astral-test/uv-public-pypackage", tag = "0.0.1" } + # /// + + import anyio + import uv_public_pypackage + "### + ); + }); + + // Ensure that the script runs without error. + context.run().arg("script.py").assert().success(); + + Ok(()) +} + +/// Revert changes to the `pyproject.toml` and `uv.lock` when the `add` operation fails. +#[test] +fn fail_to_add_revert_project() -> Result<()> { + let context = TestContext::new("3.12"); + + context + .temp_dir + .child("pyproject.toml") + .write_str(indoc! {r#" + [project] + name = "parent" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + "#})?; + + // Add a dependency on a package that declares static metadata (so can always resolve), but + // can't be installed. + let pyproject_toml = context.temp_dir.child("child/pyproject.toml"); + pyproject_toml.write_str(indoc! {r#" + [project] + name = "child" version = "0.1.0" requires-python = ">=3.12" dependencies = ["iniconfig"] @@ -7039,6 +7624,93 @@ fn add_default_index_url() -> Result<()> { fn add_index_credentials() -> Result<()> { let context = TestContext::new("3.12"); + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + "#})?; + + // Provide credentials for the index via the environment variable. + uv_snapshot!(context.filters(), context.add().arg("iniconfig==2.0.0").env(EnvVars::UV_DEFAULT_INDEX, "https://public:heron@pypi-proxy.fly.dev/basic-auth/simple"), @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 + "###); + + let pyproject_toml = fs_err::read_to_string(context.temp_dir.join("pyproject.toml"))?; + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + pyproject_toml, @r###" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [ + "iniconfig==2.0.0", + ] + + [[tool.uv.index]] + url = "https://pypi-proxy.fly.dev/basic-auth/simple" + default = true + "### + ); + }); + + let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock"))?; + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + lock, @r###" + version = 1 + requires-python = ">=3.12" + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [[package]] + name = "iniconfig" + version = "2.0.0" + source = { registry = "https://pypi-proxy.fly.dev/basic-auth/simple" } + sdist = { url = "https://pypi-proxy.fly.dev/basic-auth/files/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } + wheels = [ + { url = "https://pypi-proxy.fly.dev/basic-auth/files/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, + ] + + [[package]] + name = "project" + version = "0.1.0" + source = { virtual = "." } + dependencies = [ + { name = "iniconfig" }, + ] + + [package.metadata] + requires-dist = [{ name = "iniconfig", specifier = "==2.0.0" }] + "### + ); + }); + + Ok(()) +} + +#[test] +fn existing_index_credentials() -> Result<()> { + let context = TestContext::new("3.12"); + let pyproject_toml = context.temp_dir.child("pyproject.toml"); pyproject_toml.write_str(indoc! {r#" [project] @@ -8299,3 +8971,117 @@ fn add_no_indent() -> Result<()> { }); Ok(()) } + +/// Accept requirements, not just package names, in `uv remove`. +#[test] +fn remove_requirement() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["flask"] + + [build-system] + requires = ["setuptools>=42"] + build-backend = "setuptools.build_meta" + "#})?; + + uv_snapshot!(context.filters(), context.remove().arg("flask[dotenv]"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + project==0.1.0 (from file://[TEMP_DIR]/) + "###); + + let pyproject_toml = context.read("pyproject.toml"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + pyproject_toml, @r###" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + + [build-system] + requires = ["setuptools>=42"] + build-backend = "setuptools.build_meta" + "### + ); + }); + + Ok(()) +} + +/// Remove all dependencies with remaining comments +#[test] +fn remove_all_with_comments() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [ + "duct", + "minilog", + # foo + # bar + ] + + [build-system] + requires = ["setuptools>=42"] + build-backend = "setuptools.build_meta" + "#})?; + + uv_snapshot!(context.filters(), context.remove().arg("duct").arg("minilog"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + project==0.1.0 (from file://[TEMP_DIR]/) + "###); + + let pyproject_toml = context.read("pyproject.toml"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + pyproject_toml, @r###" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [ + # foo + # bar + ] + + [build-system] + requires = ["setuptools>=42"] + build-backend = "setuptools.build_meta" + "### + ); + }); + + Ok(()) +} diff --git a/crates/uv/tests/it/export.rs b/crates/uv/tests/it/export.rs index 19e7a388d937..aedf471d36f7 100644 --- a/crates/uv/tests/it/export.rs +++ b/crates/uv/tests/it/export.rs @@ -4,6 +4,8 @@ use crate::common::{apply_filters, uv_snapshot, TestContext}; use anyhow::{Ok, Result}; use assert_cmd::assert::OutputAssertExt; use assert_fs::prelude::*; +use indoc::indoc; +use insta::assert_snapshot; use std::process::Stdio; #[test] @@ -2054,6 +2056,263 @@ fn export_group() -> Result<()> { Ok(()) } +#[test] +fn script() -> Result<()> { + let context = TestContext::new("3.12"); + + let script = context.temp_dir.child("script.py"); + script.write_str(indoc! {r#" + # /// script + # requires-python = ">=3.11" + # dependencies = [ + # "anyio==2.0.0 ; sys_platform == 'win32'", + # "anyio==3.0.0 ; sys_platform == 'linux'" + # ] + # /// + "#})?; + + uv_snapshot!(context.filters(), context.export().arg("--script").arg(script.path()), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv export --cache-dir [CACHE_DIR] --script [TEMP_DIR]/script.py + anyio==2.0.0 ; sys_platform == 'win32' \ + --hash=sha256:0b8375c8fc665236cb4d143ea13e849eb9e074d727b1b5c27d88aba44ca8c547 \ + --hash=sha256:ceca4669ffa3f02bf20ef3d6c2a0c323b16cdc71d1ce0b0bc03c6f1f36054826 + anyio==3.0.0 ; sys_platform == 'linux' \ + --hash=sha256:b553598332c050af19f7d41f73a7790142f5bc3d5eb8bd82f5e515ec22019bd9 \ + --hash=sha256:e71c3d9d72291d12056c0265d07c6bbedf92332f78573e278aeb116f24f30395 + idna==3.6 ; sys_platform == 'linux' or sys_platform == 'win32' \ + --hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \ + --hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f + sniffio==1.3.1 ; sys_platform == 'linux' or sys_platform == 'win32' \ + --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \ + --hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc + + ----- stderr ----- + Resolved 4 packages in [TIME] + "###); + + // If the lockfile didn't exist already, it shouldn't be persisted to disk. + assert!(!context.temp_dir.child("uv.lock").exists()); + + // Explicitly lock the script. + uv_snapshot!(context.filters(), context.lock().arg("--script").arg(script.path()), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 4 packages in [TIME] + "###); + + let lock = context.read("script.py.lock"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + lock, @r###" + version = 1 + requires-python = ">=3.11" + resolution-markers = [ + "sys_platform == 'win32'", + "sys_platform == 'linux'", + "sys_platform != 'linux' and sys_platform != 'win32'", + ] + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [manifest] + requirements = [ + { name = "anyio", marker = "sys_platform == 'linux'", specifier = "==3.0.0" }, + { name = "anyio", marker = "sys_platform == 'win32'", specifier = "==2.0.0" }, + ] + + [[package]] + name = "anyio" + version = "2.0.0" + source = { registry = "https://pypi.org/simple" } + resolution-markers = [ + "sys_platform == 'win32'", + ] + dependencies = [ + { name = "idna", marker = "sys_platform == 'win32'" }, + { name = "sniffio", marker = "sys_platform == 'win32'" }, + ] + sdist = { url = "https://files.pythonhosted.org/packages/fe/dc/daeadb9b34093d3968afcc93946ee567cd6d2b402a96c608cb160f74d737/anyio-2.0.0.tar.gz", hash = "sha256:ceca4669ffa3f02bf20ef3d6c2a0c323b16cdc71d1ce0b0bc03c6f1f36054826", size = 91291 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/19/10fe682e962efd1610aa41376399fc3f3e002425449b02d0fb04749bb712/anyio-2.0.0-py3-none-any.whl", hash = "sha256:0b8375c8fc665236cb4d143ea13e849eb9e074d727b1b5c27d88aba44ca8c547", size = 62675 }, + ] + + [[package]] + name = "anyio" + version = "3.0.0" + source = { registry = "https://pypi.org/simple" } + resolution-markers = [ + "sys_platform == 'linux'", + ] + dependencies = [ + { name = "idna", marker = "sys_platform == 'linux'" }, + { name = "sniffio", marker = "sys_platform == 'linux'" }, + ] + sdist = { url = "https://files.pythonhosted.org/packages/99/0d/65165f99e5f4f3b4c43a5ed9db0fb7aa655f5a58f290727a30528a87eb45/anyio-3.0.0.tar.gz", hash = "sha256:b553598332c050af19f7d41f73a7790142f5bc3d5eb8bd82f5e515ec22019bd9", size = 116952 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/49/ebee263b69fe243bd1fd0a88bc6bb0f7732bf1794ba3273cb446351f9482/anyio-3.0.0-py3-none-any.whl", hash = "sha256:e71c3d9d72291d12056c0265d07c6bbedf92332f78573e278aeb116f24f30395", size = 72182 }, + ] + + [[package]] + name = "idna" + version = "3.6" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", size = 175426 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f", size = 61567 }, + ] + + [[package]] + name = "sniffio" + version = "1.3.1" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, + ] + "### + ); + }); + + // Update the dependencies. + script.write_str(indoc! {r#" + # /// script + # requires-python = ">=3.11" + # dependencies = [ + # "anyio==2.0.0 ; sys_platform == 'win32'", + # "anyio==3.0.0 ; sys_platform == 'linux'", + # "iniconfig", + # ] + # /// + "#})?; + + // `uv tree` should update the lockfile. + uv_snapshot!(context.filters(), context.export().arg("--script").arg(script.path()), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv export --cache-dir [CACHE_DIR] --script [TEMP_DIR]/script.py + anyio==2.0.0 ; sys_platform == 'win32' \ + --hash=sha256:0b8375c8fc665236cb4d143ea13e849eb9e074d727b1b5c27d88aba44ca8c547 \ + --hash=sha256:ceca4669ffa3f02bf20ef3d6c2a0c323b16cdc71d1ce0b0bc03c6f1f36054826 + anyio==3.0.0 ; sys_platform == 'linux' \ + --hash=sha256:b553598332c050af19f7d41f73a7790142f5bc3d5eb8bd82f5e515ec22019bd9 \ + --hash=sha256:e71c3d9d72291d12056c0265d07c6bbedf92332f78573e278aeb116f24f30395 + idna==3.6 ; sys_platform == 'linux' or sys_platform == 'win32' \ + --hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \ + --hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f + iniconfig==2.0.0 \ + --hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \ + --hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374 + sniffio==1.3.1 ; sys_platform == 'linux' or sys_platform == 'win32' \ + --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \ + --hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc + + ----- stderr ----- + Resolved 5 packages in [TIME] + "###); + + let lock = context.read("script.py.lock"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + lock, @r###" + version = 1 + requires-python = ">=3.11" + resolution-markers = [ + "sys_platform == 'win32'", + "sys_platform == 'linux'", + "sys_platform != 'linux' and sys_platform != 'win32'", + ] + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [manifest] + requirements = [ + { name = "anyio", marker = "sys_platform == 'linux'", specifier = "==3.0.0" }, + { name = "anyio", marker = "sys_platform == 'win32'", specifier = "==2.0.0" }, + { name = "iniconfig" }, + ] + + [[package]] + name = "anyio" + version = "2.0.0" + source = { registry = "https://pypi.org/simple" } + resolution-markers = [ + "sys_platform == 'win32'", + ] + dependencies = [ + { name = "idna", marker = "sys_platform == 'win32'" }, + { name = "sniffio", marker = "sys_platform == 'win32'" }, + ] + sdist = { url = "https://files.pythonhosted.org/packages/fe/dc/daeadb9b34093d3968afcc93946ee567cd6d2b402a96c608cb160f74d737/anyio-2.0.0.tar.gz", hash = "sha256:ceca4669ffa3f02bf20ef3d6c2a0c323b16cdc71d1ce0b0bc03c6f1f36054826", size = 91291 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/19/10fe682e962efd1610aa41376399fc3f3e002425449b02d0fb04749bb712/anyio-2.0.0-py3-none-any.whl", hash = "sha256:0b8375c8fc665236cb4d143ea13e849eb9e074d727b1b5c27d88aba44ca8c547", size = 62675 }, + ] + + [[package]] + name = "anyio" + version = "3.0.0" + source = { registry = "https://pypi.org/simple" } + resolution-markers = [ + "sys_platform == 'linux'", + ] + dependencies = [ + { name = "idna", marker = "sys_platform == 'linux'" }, + { name = "sniffio", marker = "sys_platform == 'linux'" }, + ] + sdist = { url = "https://files.pythonhosted.org/packages/99/0d/65165f99e5f4f3b4c43a5ed9db0fb7aa655f5a58f290727a30528a87eb45/anyio-3.0.0.tar.gz", hash = "sha256:b553598332c050af19f7d41f73a7790142f5bc3d5eb8bd82f5e515ec22019bd9", size = 116952 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/49/ebee263b69fe243bd1fd0a88bc6bb0f7732bf1794ba3273cb446351f9482/anyio-3.0.0-py3-none-any.whl", hash = "sha256:e71c3d9d72291d12056c0265d07c6bbedf92332f78573e278aeb116f24f30395", size = 72182 }, + ] + + [[package]] + name = "idna" + version = "3.6" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", size = 175426 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f", size = 61567 }, + ] + + [[package]] + name = "iniconfig" + version = "2.0.0" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, + ] + + [[package]] + name = "sniffio" + version = "1.3.1" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, + ] + "### + ); + }); + + Ok(()) +} + #[test] fn conflicts() -> Result<()> { let context = TestContext::new("3.12"); diff --git a/crates/uv/tests/it/help.rs b/crates/uv/tests/it/help.rs index 31e3e02d40cc..93b6ece0251f 100644 --- a/crates/uv/tests/it/help.rs +++ b/crates/uv/tests/it/help.rs @@ -52,7 +52,7 @@ fn help() { -v, --verbose... Use verbose output --color - Control colors in output [default: auto] [possible values: auto, always, never] + Control the use of color in output [possible values: auto, always, never] --native-tls Whether to load TLS certificates from the platform's native certificate store [env: UV_NATIVE_TLS=] @@ -132,7 +132,7 @@ fn help_flag() { -v, --verbose... Use verbose output --color - Control colors in output [default: auto] [possible values: auto, always, never] + Control the use of color in output [possible values: auto, always, never] --native-tls Whether to load TLS certificates from the platform's native certificate store [env: UV_NATIVE_TLS=] @@ -211,7 +211,7 @@ fn help_short_flag() { -v, --verbose... Use verbose output --color - Control colors in output [default: auto] [possible values: auto, always, never] + Control the use of color in output [possible values: auto, always, never] --native-tls Whether to load TLS certificates from the platform's native certificate store [env: UV_NATIVE_TLS=] @@ -347,9 +347,9 @@ fn help_subcommand() { () --color - Control colors in output + Control the use of color in output. - [default: auto] + By default, uv will automatically detect support for colors when writing to a terminal. Possible values: - auto: Enables colored output only when the output is going to a terminal or TTY with @@ -591,9 +591,9 @@ fn help_subsubcommand() { () --color - Control colors in output + Control the use of color in output. - [default: auto] + By default, uv will automatically detect support for colors when writing to a terminal. Possible values: - auto: Enables colored output only when the output is going to a terminal or TTY with @@ -728,7 +728,7 @@ fn help_flag_subcommand() { -v, --verbose... Use verbose output --color - Control colors in output [default: auto] [possible values: auto, always, never] + Control the use of color in output [possible values: auto, always, never] --native-tls Whether to load TLS certificates from the platform's native certificate store [env: UV_NATIVE_TLS=] @@ -801,7 +801,7 @@ fn help_flag_subsubcommand() { -v, --verbose... Use verbose output --color - Control colors in output [default: auto] [possible values: auto, always, never] + Control the use of color in output [possible values: auto, always, never] --native-tls Whether to load TLS certificates from the platform's native certificate store [env: UV_NATIVE_TLS=] @@ -958,7 +958,7 @@ fn help_with_global_option() { -v, --verbose... Use verbose output --color - Control colors in output [default: auto] [possible values: auto, always, never] + Control the use of color in output [possible values: auto, always, never] --native-tls Whether to load TLS certificates from the platform's native certificate store [env: UV_NATIVE_TLS=] @@ -1074,7 +1074,7 @@ fn help_with_no_pager() { -v, --verbose... Use verbose output --color - Control colors in output [default: auto] [possible values: auto, always, never] + Control the use of color in output [possible values: auto, always, never] --native-tls Whether to load TLS certificates from the platform's native certificate store [env: UV_NATIVE_TLS=] diff --git a/crates/uv/tests/it/init.rs b/crates/uv/tests/it/init.rs index d43246e8702a..9157d2f671a8 100644 --- a/crates/uv/tests/it/init.rs +++ b/crates/uv/tests/it/init.rs @@ -1218,7 +1218,7 @@ fn init_workspace_outside() -> Result<()> { fn init_normalized_names() -> Result<()> { let context = TestContext::new("3.12"); - // `foo-bar` module is normalized to `foo_bar`. + // `foo-bar` module is normalized to `foo-bar`. uv_snapshot!(context.filters(), context.init().current_dir(&context.temp_dir).arg("foo-bar").arg("--lib"), @r###" success: true exit_code: 0 @@ -1252,17 +1252,17 @@ fn init_normalized_names() -> Result<()> { ); }); - // `foo-bar` module is normalized to `foo_bar`. - uv_snapshot!(context.filters(), context.init().current_dir(&context.temp_dir).arg("foo-bar").arg("--app"), @r###" - success: false - exit_code: 2 + // `bar_baz` module is normalized to `bar-baz`. + uv_snapshot!(context.filters(), context.init().current_dir(&context.temp_dir).arg("bar_baz").arg("--app"), @r###" + success: true + exit_code: 0 ----- stdout ----- ----- stderr ----- - error: Project is already initialized in `[TEMP_DIR]/foo-bar` (`pyproject.toml` file exists) + Initialized project `bar-baz` at `[TEMP_DIR]/bar_baz` "###); - let child = context.temp_dir.child("foo-bar"); + let child = context.temp_dir.child("bar_baz"); let pyproject = fs_err::read_to_string(child.join("pyproject.toml"))?; insta::with_settings!({ @@ -1271,30 +1271,45 @@ fn init_normalized_names() -> Result<()> { assert_snapshot!( pyproject, @r###" [project] - name = "foo-bar" + name = "bar-baz" version = "0.1.0" description = "Add your description here" readme = "README.md" requires-python = ">=3.12" dependencies = [] - - [build-system] - requires = ["hatchling"] - build-backend = "hatchling.build" "### ); }); - // "bar baz" is not allowed. - uv_snapshot!(context.filters(), context.init().current_dir(&context.temp_dir).arg("bar baz"), @r###" - success: false - exit_code: 2 + // "baz bop" is normalized to "baz-bop". + uv_snapshot!(context.filters(), context.init().current_dir(&context.temp_dir).arg("baz bop"), @r###" + success: true + exit_code: 0 ----- stdout ----- ----- stderr ----- - error: Not a valid package or extra name: "bar baz". Names must start and end with a letter or digit and may only contain -, _, ., and alphanumeric characters. + Initialized project `baz-bop` at `[TEMP_DIR]/baz bop` "###); + let child = context.temp_dir.child("baz bop"); + let pyproject = fs_err::read_to_string(child.join("pyproject.toml"))?; + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + pyproject, @r###" + [project] + name = "baz-bop" + version = "0.1.0" + description = "Add your description here" + readme = "README.md" + requires-python = ">=3.12" + dependencies = [] + "### + ); + }); + Ok(()) } @@ -2749,6 +2764,7 @@ fn init_app_build_backend_maturin() -> Result<()> { init, @r###" from foo._core import hello_from_bin + def main() -> None: print(hello_from_bin()) "### @@ -2761,8 +2777,6 @@ fn init_app_build_backend_maturin() -> Result<()> { }, { assert_snapshot!( pyi_contents, @r###" - from __future__ import annotations - def hello_from_bin() -> str: ... "### ); @@ -2879,6 +2893,7 @@ fn init_app_build_backend_scikit() -> Result<()> { init, @r###" from foo._core import hello_from_bin + def main() -> None: print(hello_from_bin()) "### @@ -2891,8 +2906,6 @@ fn init_app_build_backend_scikit() -> Result<()> { }, { assert_snapshot!( pyi_contents, @r###" - from __future__ import annotations - def hello_from_bin() -> str: ... "### ); @@ -3002,6 +3015,7 @@ fn init_lib_build_backend_maturin() -> Result<()> { init, @r###" from foo._core import hello_from_bin + def hello() -> str: return hello_from_bin() "### @@ -3014,8 +3028,6 @@ fn init_lib_build_backend_maturin() -> Result<()> { }, { assert_snapshot!( pyi_contents, @r###" - from __future__ import annotations - def hello_from_bin() -> str: ... "### ); @@ -3129,6 +3141,7 @@ fn init_lib_build_backend_scikit() -> Result<()> { init, @r###" from foo._core import hello_from_bin + def hello() -> str: return hello_from_bin() "### @@ -3141,8 +3154,6 @@ fn init_lib_build_backend_scikit() -> Result<()> { }, { assert_snapshot!( pyi_contents, @r###" - from __future__ import annotations - def hello_from_bin() -> str: ... "### ); diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index 962eca2de435..6a67f8273de2 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -9307,7 +9307,7 @@ fn lock_find_links_local_wheel() -> Result<()> { ----- stderr ----- Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] Creating virtual environment at: .venv - Prepared 1 package in [TIME] + Prepared 2 packages in [TIME] Installed 2 packages in [TIME] + project==0.1.0 (from file://[TEMP_DIR]/workspace) + tqdm==1000.0.0 @@ -9518,7 +9518,7 @@ fn lock_find_links_http_wheel() -> Result<()> { ----- stdout ----- ----- stderr ----- - Prepared 1 package in [TIME] + Prepared 2 packages in [TIME] Installed 2 packages in [TIME] + packaging==23.2 + project==0.1.0 (from file://[TEMP_DIR]/) @@ -9744,7 +9744,7 @@ fn lock_local_index() -> Result<()> { ----- stdout ----- ----- stderr ----- - Prepared 1 package in [TIME] + Prepared 2 packages in [TIME] Installed 2 packages in [TIME] + project==0.1.0 (from file://[TEMP_DIR]/) + tqdm==1000.0.0 @@ -20592,7 +20592,7 @@ fn lock_no_build_dynamic_metadata() -> Result<()> { ----- stderr ----- × Failed to build `dummy @ file://[TEMP_DIR]/` - ╰─▶ Building source distributions for dummy is disabled + ╰─▶ Building source distributions for `dummy` is disabled "###); Ok(()) @@ -21360,6 +21360,254 @@ fn lock_missing_git_prefix() -> Result<()> { Ok(()) } +#[test] +fn lock_script() -> Result<()> { + let context = TestContext::new("3.12"); + + let script = context.temp_dir.child("script.py"); + script.write_str(indoc! { r#" + # /// script + # requires-python = ">=3.11" + # dependencies = [ + # "anyio", + # ] + # /// + + import anyio + "# + })?; + + uv_snapshot!(context.filters(), context.lock().arg("--script").arg("script.py"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 3 packages in [TIME] + "###); + + let lock = context.read("script.py.lock"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + lock, @r###" + version = 1 + requires-python = ">=3.11" + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [manifest] + requirements = [{ name = "anyio" }] + + [[package]] + name = "anyio" + version = "4.3.0" + source = { registry = "https://pypi.org/simple" } + dependencies = [ + { name = "idna" }, + { name = "sniffio" }, + ] + sdist = { url = "https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6", size = 159642 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8", size = 85584 }, + ] + + [[package]] + name = "idna" + version = "3.6" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", size = 175426 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f", size = 61567 }, + ] + + [[package]] + name = "sniffio" + version = "1.3.1" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, + ] + "### + ); + }); + + // Re-run with `--locked`. + uv_snapshot!(context.filters(), context.lock().arg("--script").arg("script.py").arg("--locked"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 3 packages in [TIME] + "###); + + // Modify the script metadata. + script.write_str(indoc! { r#" + # /// script + # requires-python = ">=3.11" + # dependencies = [ + # "anyio", + # "iniconfig", + # ] + # /// + + import anyio + "# + })?; + + // Re-run with `--locked`. + uv_snapshot!(context.filters(), context.lock().arg("--script").arg("script.py").arg("--locked"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Resolved 4 packages in [TIME] + error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + "###); + + Ok(()) +} + +#[test] +fn lock_script_path() -> Result<()> { + let context = TestContext::new("3.12"); + + let script = context.temp_dir.child("script.py"); + script.write_str(indoc! { r#" + # /// script + # requires-python = ">=3.11" + # dependencies = [ + # "anyio", + # "child", + # ] + # + # [tool.uv.sources] + # child = { path = "child" } + # /// + + import anyio + "# + })?; + + let child = context.temp_dir.child("child"); + fs_err::create_dir_all(&child)?; + + let pyproject_toml = child.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "child" + version = "0.1.0" + requires-python = ">=3.11" + dependencies = ["iniconfig"] + + [build-system] + requires = ["setuptools>=42"] + build-backend = "setuptools.build_meta" + "#, + )?; + + uv_snapshot!(context.filters(), context.lock().arg("--script").arg("script.py"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 5 packages in [TIME] + "###); + + let lock = context.read("script.py.lock"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + lock, @r###" + version = 1 + requires-python = ">=3.11" + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [manifest] + requirements = [ + { name = "anyio" }, + { name = "child", directory = "child" }, + ] + + [[package]] + name = "anyio" + version = "4.3.0" + source = { registry = "https://pypi.org/simple" } + dependencies = [ + { name = "idna" }, + { name = "sniffio" }, + ] + sdist = { url = "https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6", size = 159642 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8", size = 85584 }, + ] + + [[package]] + name = "child" + version = "0.1.0" + source = { directory = "child" } + dependencies = [ + { name = "iniconfig" }, + ] + + [package.metadata] + requires-dist = [{ name = "iniconfig" }] + + [[package]] + name = "idna" + version = "3.6" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", size = 175426 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f", size = 61567 }, + ] + + [[package]] + name = "iniconfig" + version = "2.0.0" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, + ] + + [[package]] + name = "sniffio" + version = "1.3.1" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, + ] + "### + ); + }); + + // Re-run with `--locked`. + uv_snapshot!(context.filters(), context.lock().arg("--script").arg("script.py").arg("--locked"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 5 packages in [TIME] + "###); + + Ok(()) +} + #[test] fn lock_pytorch_cpu() -> Result<()> { let context = TestContext::new("3.12"); @@ -21390,6 +21638,32 @@ fn lock_pytorch_cpu() -> Result<()> { { extra = "cu124" }, ], ] + constraint-dependencies = [ + "filelock<=3.16.1", + "fsspec<=2024.12.0", + "jinja2<=3.1.4", + "markupsafe<=3.0.2", + "mpmath<=1.3.0", + "networkx<=3.4.2", + "numpy<=2.2.0", + "nvidia-cublas-cu12<=12.4.5.8", + "nvidia-cuda-cupti-cu12<=12.4.127", + "nvidia-cuda-nvrtc-cu12<=12.4.127", + "nvidia-cuda-runtime-cu12<=12.4.127", + "nvidia-cudnn-cu12<=9.1.0.70", + "nvidia-cufft-cu12<=11.2.1.3", + "nvidia-curand-cu12<=10.3.5.147", + "nvidia-cusolver-cu12<=11.6.1.9", + "nvidia-cusparse-cu12<=12.3.1.170", + "nvidia-nccl-cu12<=2.21.5", + "nvidia-nvjitlink-cu12<=12.4.127", + "nvidia-nvtx-cu12<=12.4.127", + "pillow<=11.0.0", + "setuptools<=75.6.0", + "sympy<=1.13.1", + "triton<=3.1.0", + "typing-extensions<=4.12.2", + ] [tool.uv.sources] torch = [ @@ -21410,7 +21684,6 @@ fn lock_pytorch_cpu() -> Result<()> { name = "pytorch-cu124" url = "https://download.pytorch.org/whl/cu124" explicit = true - "#, )?; @@ -21420,7 +21693,7 @@ fn lock_pytorch_cpu() -> Result<()> { ----- stdout ----- ----- stderr ----- - Resolved 31 packages in [TIME] + Resolved 33 packages in [TIME] "###); let lock = context.read("uv.lock"); @@ -21433,14 +21706,44 @@ fn lock_pytorch_cpu() -> Result<()> { version = 1 requires-python = ">=3.12.[X]" resolution-markers = [ - "sys_platform != 'darwin'", - "sys_platform == 'darwin'", + "platform_machine != 'aarch64' or sys_platform != 'linux'", + "platform_machine == 'aarch64' and sys_platform == 'linux'", + "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')", + "(platform_machine == 'aarch64' and sys_platform == 'linux') or sys_platform == 'darwin'", ] conflicts = [[ { package = "project", extra = "cpu" }, { package = "project", extra = "cu124" }, ]] + [manifest] + constraints = [ + { name = "filelock", specifier = "<=3.16.1" }, + { name = "fsspec", specifier = "<=2024.12.0" }, + { name = "jinja2", specifier = "<=3.1.4" }, + { name = "markupsafe", specifier = "<=3.0.2" }, + { name = "mpmath", specifier = "<=1.3.0" }, + { name = "networkx", specifier = "<=3.4.2" }, + { name = "numpy", specifier = "<=2.2.0" }, + { name = "nvidia-cublas-cu12", specifier = "<=12.4.5.8" }, + { name = "nvidia-cuda-cupti-cu12", specifier = "<=12.4.127" }, + { name = "nvidia-cuda-nvrtc-cu12", specifier = "<=12.4.127" }, + { name = "nvidia-cuda-runtime-cu12", specifier = "<=12.4.127" }, + { name = "nvidia-cudnn-cu12", specifier = "<=9.1.0.70" }, + { name = "nvidia-cufft-cu12", specifier = "<=11.2.1.3" }, + { name = "nvidia-curand-cu12", specifier = "<=10.3.5.147" }, + { name = "nvidia-cusolver-cu12", specifier = "<=11.6.1.9" }, + { name = "nvidia-cusparse-cu12", specifier = "<=12.3.1.170" }, + { name = "nvidia-nccl-cu12", specifier = "<=2.21.5" }, + { name = "nvidia-nvjitlink-cu12", specifier = "<=12.4.127" }, + { name = "nvidia-nvtx-cu12", specifier = "<=12.4.127" }, + { name = "pillow", specifier = "<=11.0.0" }, + { name = "setuptools", specifier = "<=75.6.0" }, + { name = "sympy", specifier = "<=1.13.1" }, + { name = "triton", specifier = "<=3.1.0" }, + { name = "typing-extensions", specifier = "<=4.12.2" }, + ] + [[package]] name = "filelock" version = "3.16.1" @@ -21610,7 +21913,7 @@ fn lock_pytorch_cpu() -> Result<()> { version = "9.1.0.70" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas-cu12" }, + { name = "nvidia-cublas-cu12", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/9f/fd/713452cd72343f682b1c7b9321e23829f00b842ceaedcda96e742ea0b0b3/nvidia_cudnn_cu12-9.1.0.70-py3-none-manylinux2014_x86_64.whl", hash = "sha256:165764f44ef8c61fcdfdfdbe769d687e06374059fbb388b6c89ecb0e28793a6f", size = 664752741 }, @@ -21622,7 +21925,7 @@ fn lock_pytorch_cpu() -> Result<()> { version = "11.2.1.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink-cu12" }, + { name = "nvidia-nvjitlink-cu12", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/7a/8a/0e728f749baca3fbeffad762738276e5df60851958be7783af121a7221e7/nvidia_cufft_cu12-11.2.1.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:5dad8008fc7f92f5ddfa2101430917ce2ffacd86824914c82e28990ad7f00399", size = 211422548 }, @@ -21645,9 +21948,9 @@ fn lock_pytorch_cpu() -> Result<()> { version = "11.6.1.9" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas-cu12" }, - { name = "nvidia-cusparse-cu12" }, - { name = "nvidia-nvjitlink-cu12" }, + { name = "nvidia-cublas-cu12", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "nvidia-cusparse-cu12", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "nvidia-nvjitlink-cu12", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/46/6b/a5c33cf16af09166845345275c34ad2190944bcc6026797a39f8e0a282e0/nvidia_cusolver_cu12-11.6.1.9-py3-none-manylinux2014_aarch64.whl", hash = "sha256:d338f155f174f90724bbde3758b7ac375a70ce8e706d70b018dd3375545fc84e", size = 127634111 }, @@ -21660,7 +21963,7 @@ fn lock_pytorch_cpu() -> Result<()> { version = "12.3.1.170" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink-cu12" }, + { name = "nvidia-nvjitlink-cu12", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/96/a9/c0d2f83a53d40a4a41be14cea6a0bf9e668ffcf8b004bd65633f433050c0/nvidia_cusparse_cu12-12.3.1.170-py3-none-manylinux2014_aarch64.whl", hash = "sha256:9d32f62896231ebe0480efd8a7f702e143c98cfaa0e8a76df3386c1ba2b54df3", size = 207381987 }, @@ -21745,14 +22048,16 @@ fn lock_pytorch_cpu() -> Result<()> { [package.optional-dependencies] cpu = [ - { name = "torch", version = "2.5.1", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'darwin'" }, - { name = "torch", version = "2.5.1+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform != 'darwin'" }, - { name = "torchvision", version = "0.20.1", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'darwin'" }, - { name = "torchvision", version = "0.20.1+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform != 'darwin'" }, + { name = "torch", version = "2.5.1", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or sys_platform == 'darwin'" }, + { name = "torch", version = "2.5.1+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "torchvision", version = "0.20.1", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or sys_platform == 'darwin'" }, + { name = "torchvision", version = "0.20.1+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, ] cu124 = [ - { name = "torch", version = "2.5.1+cu124", source = { registry = "https://download.pytorch.org/whl/cu124" } }, - { name = "torchvision", version = "0.20.1+cu124", source = { registry = "https://download.pytorch.org/whl/cu124" } }, + { name = "torch", version = "2.5.1", source = { registry = "https://download.pytorch.org/whl/cu124" }, marker = "platform_machine == 'aarch64' and sys_platform == 'linux'" }, + { name = "torch", version = "2.5.1+cu124", source = { registry = "https://download.pytorch.org/whl/cu124" }, marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "torchvision", version = "0.20.1", source = { registry = "https://download.pytorch.org/whl/cu124" }, marker = "platform_machine == 'aarch64' and sys_platform == 'linux'" }, + { name = "torchvision", version = "0.20.1+cu124", source = { registry = "https://download.pytorch.org/whl/cu124" }, marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] [package.metadata] @@ -21791,37 +22096,57 @@ fn lock_pytorch_cpu() -> Result<()> { version = "2.5.1" source = { registry = "https://download.pytorch.org/whl/cpu" } resolution-markers = [ - "sys_platform == 'darwin'", + "(platform_machine == 'aarch64' and sys_platform == 'linux') or sys_platform == 'darwin'", ] dependencies = [ - { name = "filelock", marker = "sys_platform == 'darwin'" }, - { name = "fsspec", marker = "sys_platform == 'darwin'" }, - { name = "jinja2", marker = "sys_platform == 'darwin'" }, - { name = "networkx", marker = "sys_platform == 'darwin'" }, - { name = "setuptools", marker = "sys_platform == 'darwin'" }, - { name = "sympy", marker = "sys_platform == 'darwin'" }, - { name = "typing-extensions", marker = "sys_platform == 'darwin'" }, + { name = "filelock", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or sys_platform == 'darwin'" }, + { name = "fsspec", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or sys_platform == 'darwin'" }, + { name = "jinja2", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or sys_platform == 'darwin'" }, + { name = "networkx", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or sys_platform == 'darwin'" }, + { name = "setuptools", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or sys_platform == 'darwin'" }, + { name = "sympy", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or sys_platform == 'darwin'" }, + { name = "typing-extensions", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or sys_platform == 'darwin'" }, ] wheels = [ { url = "https://download.pytorch.org/whl/cpu/torch-2.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36d1be99281b6f602d9639bd0af3ee0006e7aab16f6718d86f709d395b6f262c" }, { url = "https://download.pytorch.org/whl/cpu/torch-2.5.1-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:8c712df61101964eb11910a846514011f0b6f5920c55dbf567bff8a34163d5b1" }, ] + [[package]] + name = "torch" + version = "2.5.1" + source = { registry = "https://download.pytorch.org/whl/cu124" } + resolution-markers = [ + "platform_machine == 'aarch64' and sys_platform == 'linux'", + ] + dependencies = [ + { name = "filelock", marker = "platform_machine == 'aarch64' and sys_platform == 'linux'" }, + { name = "fsspec", marker = "platform_machine == 'aarch64' and sys_platform == 'linux'" }, + { name = "jinja2", marker = "platform_machine == 'aarch64' and sys_platform == 'linux'" }, + { name = "networkx", marker = "platform_machine == 'aarch64' and sys_platform == 'linux'" }, + { name = "setuptools", marker = "platform_machine == 'aarch64' and sys_platform == 'linux'" }, + { name = "sympy", marker = "platform_machine == 'aarch64' and sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "platform_machine == 'aarch64' and sys_platform == 'linux'" }, + ] + wheels = [ + { url = "https://download.pytorch.org/whl/cu124/torch-2.5.1-cp312-cp312-linux_aarch64.whl", hash = "sha256:302041d457ee169fd925b53da283c13365c6de75c6bb3e84130774b10e2fbb39" }, + ] + [[package]] name = "torch" version = "2.5.1+cpu" source = { registry = "https://download.pytorch.org/whl/cpu" } resolution-markers = [ - "sys_platform != 'darwin'", + "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')", ] dependencies = [ - { name = "filelock", marker = "sys_platform != 'darwin'" }, - { name = "fsspec", marker = "sys_platform != 'darwin'" }, - { name = "jinja2", marker = "sys_platform != 'darwin'" }, - { name = "networkx", marker = "sys_platform != 'darwin'" }, - { name = "setuptools", marker = "sys_platform != 'darwin'" }, - { name = "sympy", marker = "sys_platform != 'darwin'" }, - { name = "typing-extensions", marker = "sys_platform != 'darwin'" }, + { name = "filelock", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "fsspec", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "jinja2", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "networkx", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "setuptools", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "sympy", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "typing-extensions", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, ] wheels = [ { url = "https://download.pytorch.org/whl/cpu/torch-2.5.1%2Bcpu-cp312-cp312-linux_x86_64.whl", hash = "sha256:4856f9d6925121d13c2df07aa7580b767f449dfe71ae5acde9c27535d5da4840" }, @@ -21833,11 +22158,14 @@ fn lock_pytorch_cpu() -> Result<()> { name = "torch" version = "2.5.1+cu124" source = { registry = "https://download.pytorch.org/whl/cu124" } + resolution-markers = [ + "platform_machine != 'aarch64' or sys_platform != 'linux'", + ] dependencies = [ - { name = "filelock" }, - { name = "fsspec" }, - { name = "jinja2" }, - { name = "networkx" }, + { name = "filelock", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "fsspec", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "jinja2", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "networkx", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, @@ -21850,10 +22178,10 @@ fn lock_pytorch_cpu() -> Result<()> { { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "nvidia-nvjitlink-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "setuptools" }, - { name = "sympy" }, + { name = "setuptools", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "sympy", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, { name = "triton", marker = "python_full_version < '3.13' and platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "typing-extensions" }, + { name = "typing-extensions", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] wheels = [ { url = "https://download.pytorch.org/whl/cu124/torch-2.5.1%2Bcu124-cp312-cp312-linux_x86_64.whl", hash = "sha256:bf6484bfe5bc4f92a4a1a1bf553041505e19a911f717065330eb061afe0e14d7" }, @@ -21866,29 +22194,45 @@ fn lock_pytorch_cpu() -> Result<()> { version = "0.20.1" source = { registry = "https://download.pytorch.org/whl/cpu" } resolution-markers = [ - "sys_platform == 'darwin'", + "(platform_machine == 'aarch64' and sys_platform == 'linux') or sys_platform == 'darwin'", ] dependencies = [ - { name = "numpy", marker = "sys_platform == 'darwin'" }, - { name = "pillow", marker = "sys_platform == 'darwin'" }, - { name = "torch", version = "2.5.1", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'darwin'" }, + { name = "numpy", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or sys_platform == 'darwin'" }, + { name = "pillow", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or sys_platform == 'darwin'" }, + { name = "torch", version = "2.5.1", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or sys_platform == 'darwin'" }, ] wheels = [ { url = "https://download.pytorch.org/whl/cpu/torchvision-0.20.1-cp312-cp312-linux_aarch64.whl", hash = "sha256:9f853ba4497ac4691815ad41b523ee23cf5ba4f87b1ce869d704052e233ca8b7" }, { url = "https://download.pytorch.org/whl/cpu/torchvision-0.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1a31256ff945d64f006bb306813a7c95a531fe16bfb2535c837dd4c104533d7a" }, ] + [[package]] + name = "torchvision" + version = "0.20.1" + source = { registry = "https://download.pytorch.org/whl/cu124" } + resolution-markers = [ + "platform_machine == 'aarch64' and sys_platform == 'linux'", + ] + dependencies = [ + { name = "numpy", marker = "platform_machine == 'aarch64' and sys_platform == 'linux'" }, + { name = "pillow", marker = "platform_machine == 'aarch64' and sys_platform == 'linux'" }, + { name = "torch", version = "2.5.1", source = { registry = "https://download.pytorch.org/whl/cu124" }, marker = "platform_machine == 'aarch64' and sys_platform == 'linux'" }, + ] + wheels = [ + { url = "https://download.pytorch.org/whl/cu124/torchvision-0.20.1-cp312-cp312-linux_aarch64.whl", hash = "sha256:3e3289e53d0cb5d1b7f55b3f5912f46a08293c6791585ba2fc32c12cded9f9af" }, + ] + [[package]] name = "torchvision" version = "0.20.1+cpu" source = { registry = "https://download.pytorch.org/whl/cpu" } resolution-markers = [ - "sys_platform != 'darwin'", + "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')", ] dependencies = [ - { name = "numpy", marker = "sys_platform != 'darwin'" }, - { name = "pillow", marker = "sys_platform != 'darwin'" }, - { name = "torch", version = "2.5.1+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform != 'darwin'" }, + { name = "numpy", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "pillow", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "torch", version = "2.5.1+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, ] wheels = [ { url = "https://download.pytorch.org/whl/cpu/torchvision-0.20.1%2Bcpu-cp312-cp312-linux_x86_64.whl", hash = "sha256:5f46c7ac7f00a065cb40bfb1e1bfc4ba16a35f5d46b3fe70cca6b3cea7f822f7" }, @@ -21899,10 +22243,13 @@ fn lock_pytorch_cpu() -> Result<()> { name = "torchvision" version = "0.20.1+cu124" source = { registry = "https://download.pytorch.org/whl/cu124" } + resolution-markers = [ + "platform_machine != 'aarch64' or sys_platform != 'linux'", + ] dependencies = [ - { name = "numpy" }, - { name = "pillow" }, - { name = "torch", version = "2.5.1+cu124", source = { registry = "https://download.pytorch.org/whl/cu124" } }, + { name = "numpy", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "pillow", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "torch", version = "2.5.1+cu124", source = { registry = "https://download.pytorch.org/whl/cu124" }, marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] wheels = [ { url = "https://download.pytorch.org/whl/cu124/torchvision-0.20.1%2Bcu124-cp312-cp312-linux_x86_64.whl", hash = "sha256:d1053ec5054549e7dac2613b151bffe323f3c924939d296df4d7d34925aaf3ad" }, @@ -21914,7 +22261,7 @@ fn lock_pytorch_cpu() -> Result<()> { version = "3.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "filelock" }, + { name = "filelock", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/78/eb/65f5ba83c2a123f6498a3097746607e5b2f16add29e36765305e4ac7fdd8/triton-3.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8182f42fd8080a7d39d666814fa36c5e30cc00ea7eeeb1a2983dbb4c99a0fdc", size = 209551444 }, diff --git a/crates/uv/tests/it/lock_conflict.rs b/crates/uv/tests/it/lock_conflict.rs index a056f7b366bb..334150536728 100644 --- a/crates/uv/tests/it/lock_conflict.rs +++ b/crates/uv/tests/it/lock_conflict.rs @@ -50,7 +50,7 @@ fn extra_basic() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because project[extra2] depends on sortedcontainers==2.4.0 and project[extra1] depends on sortedcontainers==2.3.0, we can conclude that project[extra1] and project[extra2] are incompatible. + ╰─▶ Because project[extra1] depends on sortedcontainers==2.3.0 and project[extra2] depends on sortedcontainers==2.4.0, we can conclude that project[extra1] and project[extra2] are incompatible. And because your project requires project[extra1] and project[extra2], we can conclude that your project's requirements are unsatisfiable. "###); @@ -250,7 +250,7 @@ fn extra_basic_three_extras() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because project[project3] depends on sortedcontainers==2.4.0 and project[extra2] depends on sortedcontainers==2.3.0, we can conclude that project[extra2] and project[project3] are incompatible. + ╰─▶ Because project[extra2] depends on sortedcontainers==2.3.0 and project[project3] depends on sortedcontainers==2.4.0, we can conclude that project[extra2] and project[project3] are incompatible. And because your project requires project[extra2] and project[project3], we can conclude that your project's requirements are unsatisfiable. "###); @@ -538,7 +538,7 @@ fn extra_multiple_not_conflicting2() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because project[project4] depends on sortedcontainers==2.4.0 and project[project3] depends on sortedcontainers==2.3.0, we can conclude that project[project3] and project[project4] are incompatible. + ╰─▶ Because project[project3] depends on sortedcontainers==2.3.0 and project[project4] depends on sortedcontainers==2.4.0, we can conclude that project[project3] and project[project4] are incompatible. And because your project requires project[project3] and project[project4], we can conclude that your project's requirements are unsatisfiable. "###); @@ -582,7 +582,7 @@ fn extra_multiple_not_conflicting2() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because project[project3] depends on sortedcontainers==2.3.0 and project[extra2] depends on sortedcontainers==2.4.0, we can conclude that project[extra2] and project[project3] are incompatible. + ╰─▶ Because project[extra2] depends on sortedcontainers==2.4.0 and project[project3] depends on sortedcontainers==2.3.0, we can conclude that project[extra2] and project[project3] are incompatible. And because your project requires project[extra2] and project[project3], we can conclude that your project's requirements are unsatisfiable. "###); @@ -715,7 +715,7 @@ fn extra_multiple_independent() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because project[project4] depends on anyio==4.2.0 and project[project3] depends on anyio==4.1.0, we can conclude that project[project3] and project[project4] are incompatible. + ╰─▶ Because project[project3] depends on anyio==4.1.0 and project[project4] depends on anyio==4.2.0, we can conclude that project[project3] and project[project4] are incompatible. And because your project requires project[project3] and project[project4], we can conclude that your project's requirements are unsatisfiable. "###); @@ -755,7 +755,7 @@ fn extra_multiple_independent() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because project[extra2] depends on sortedcontainers==2.4.0 and project[extra1] depends on sortedcontainers==2.3.0, we can conclude that project[extra1] and project[extra2] are incompatible. + ╰─▶ Because project[extra1] depends on sortedcontainers==2.3.0 and project[extra2] depends on sortedcontainers==2.4.0, we can conclude that project[extra1] and project[extra2] are incompatible. And because your project requires project[extra1] and project[extra2], we can conclude that your project's requirements are unsatisfiable. "###); @@ -1047,7 +1047,7 @@ fn extra_config_change_ignore_lockfile() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because project[extra2] depends on sortedcontainers==2.4.0 and project[extra1] depends on sortedcontainers==2.3.0, we can conclude that project[extra1] and project[extra2] are incompatible. + ╰─▶ Because project[extra1] depends on sortedcontainers==2.3.0 and project[extra2] depends on sortedcontainers==2.4.0, we can conclude that project[extra1] and project[extra2] are incompatible. And because your project requires project[extra1] and project[extra2], we can conclude that your project's requirements are unsatisfiable. "###); @@ -1289,9 +1289,9 @@ fn extra_nested_across_workspace() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because dummy[extra2] depends on proxy1[extra2] and only proxy1[extra2]==0.1.0 is available, we can conclude that dummy[extra2] depends on proxy1[extra2]==0.1.0. - And because proxy1[extra2]==0.1.0 depends on anyio==4.2.0 and proxy1[extra1]==0.1.0 depends on anyio==4.1.0, we can conclude that proxy1[extra1]==0.1.0 and dummy[extra2] are incompatible. - And because only proxy1[extra1]==0.1.0 is available and dummysub[extra1] depends on proxy1[extra1], we can conclude that dummysub[extra1] and dummy[extra2] are incompatible. + ╰─▶ Because proxy1[extra1]==0.1.0 depends on anyio==4.1.0 and proxy1[extra2]==0.1.0 depends on anyio==4.2.0, we can conclude that proxy1[extra1]==0.1.0 and proxy1[extra2]==0.1.0 are incompatible. + And because only proxy1[extra1]==0.1.0 is available and dummysub[extra1] depends on proxy1[extra1], we can conclude that dummysub[extra1] and proxy1[extra2]==0.1.0 are incompatible. + And because only proxy1[extra2]==0.1.0 is available and dummy[extra2] depends on proxy1[extra2], we can conclude that dummy[extra2] and dummysub[extra1] are incompatible. And because your workspace requires dummy[extra2] and dummysub[extra1], we can conclude that your workspace's requirements are unsatisfiable. "###); @@ -1415,7 +1415,7 @@ fn group_basic() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because project:group2 depends on sortedcontainers==2.4.0 and project:group1 depends on sortedcontainers==2.3.0, we can conclude that project:group1 and project:group2 are incompatible. + ╰─▶ Because project:group1 depends on sortedcontainers==2.3.0 and project:group2 depends on sortedcontainers==2.4.0, we can conclude that project:group1 and project:group2 are incompatible. And because your project requires project:group1 and project:group2, we can conclude that your project's requirements are unsatisfiable. "###); @@ -1793,7 +1793,7 @@ fn mixed() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because project:group1 depends on sortedcontainers==2.3.0 and project[extra1] depends on sortedcontainers==2.4.0, we can conclude that project[extra1] and project:group1 are incompatible. + ╰─▶ Because project[extra1] depends on sortedcontainers==2.4.0 and project:group1 depends on sortedcontainers==2.3.0, we can conclude that project:group1 and project[extra1] are incompatible. And because your project requires project[extra1] and project:group1, we can conclude that your project's requirements are unsatisfiable. "###); diff --git a/crates/uv/tests/it/lock_scenarios.rs b/crates/uv/tests/it/lock_scenarios.rs index febac6b07a09..a645f4f0f9fd 100644 --- a/crates/uv/tests/it/lock_scenarios.rs +++ b/crates/uv/tests/it/lock_scenarios.rs @@ -1,7 +1,7 @@ //! DO NOT EDIT //! //! Generated with `./scripts/sync_scenarios.sh` -//! Scenarios from +//! Scenarios from //! #![cfg(all(feature = "python", feature = "pypi"))] #![allow(clippy::needless_raw_string_hashes)] @@ -588,7 +588,7 @@ fn conflict_in_fork() -> Result<()> { × No solution found when resolving dependencies for split (sys_platform == 'darwin'): ╰─▶ Because only package-b==1.0.0 is available and package-b==1.0.0 depends on package-d==1, we can conclude that all versions of package-b depend on package-d==1. And because package-c==1.0.0 depends on package-d==2 and only package-c==1.0.0 is available, we can conclude that all versions of package-b and all versions of package-c are incompatible. - And because package-a{sys_platform == 'darwin'}==1.0.0 depends on package-b and package-c, we can conclude that package-a{sys_platform == 'darwin'}==1.0.0 cannot be used. + And because package-a==1.0.0 depends on package-b and package-c, we can conclude that package-a==1.0.0 cannot be used. And because only the following versions of package-a{sys_platform == 'darwin'} are available: package-a{sys_platform == 'darwin'}==1.0.0 package-a{sys_platform == 'darwin'}>2 @@ -1320,7 +1320,7 @@ fn fork_marker_disjoint() -> Result<()> { ----- stdout ----- ----- stderr ----- - × No solution found when resolving dependencies for split (sys_platform == 'linux'): + × No solution found when resolving dependencies: ╰─▶ Because your project depends on package-a{sys_platform == 'linux'}>=2 and package-a{sys_platform == 'linux'}<2, we can conclude that your project's requirements are unsatisfiable. "### ); @@ -2940,7 +2940,7 @@ fn fork_non_local_fork_marker_direct() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because package-a{sys_platform == 'linux'}==1.0.0 depends on package-c<2.0.0 and package-b{sys_platform == 'darwin'}==1.0.0 depends on package-c>=2.0.0, we can conclude that package-a{sys_platform == 'linux'}==1.0.0 and package-b{sys_platform == 'darwin'}==1.0.0 are incompatible. + ╰─▶ Because package-b==1.0.0 depends on package-c>=2.0.0 and package-a==1.0.0 depends on package-c<2.0.0, we can conclude that package-a==1.0.0 and package-b==1.0.0 are incompatible. And because your project depends on package-a{sys_platform == 'linux'}==1.0.0 and package-b{sys_platform == 'darwin'}==1.0.0, we can conclude that your project's requirements are unsatisfiable. "### ); @@ -3015,8 +3015,10 @@ fn fork_non_local_fork_marker_transitive() -> Result<()> { ╰─▶ Because package-a==1.0.0 depends on package-c{sys_platform == 'linux'}<2.0.0 and only the following versions of package-c{sys_platform == 'linux'} are available: package-c{sys_platform == 'linux'}==1.0.0 package-c{sys_platform == 'linux'}>2.0.0 - we can conclude that package-a==1.0.0 depends on package-c{sys_platform == 'linux'}==1.0.0. - And because only package-c{sys_platform == 'darwin'}<=2.0.0 is available and package-b==1.0.0 depends on package-c{sys_platform == 'darwin'}>=2.0.0, we can conclude that package-a==1.0.0 and package-b==1.0.0 are incompatible. + we can conclude that package-a==1.0.0 depends on package-c{sys_platform == 'linux'}==1.0.0. (1) + + Because only package-c{sys_platform == 'darwin'}<=2.0.0 is available and package-b==1.0.0 depends on package-c{sys_platform == 'darwin'}>=2.0.0, we can conclude that package-b==1.0.0 depends on package-c==2.0.0. + And because we know from (1) that package-a==1.0.0 depends on package-c{sys_platform == 'linux'}==1.0.0, we can conclude that package-a==1.0.0 and package-b==1.0.0 are incompatible. And because your project depends on package-a==1.0.0 and package-b==1.0.0, we can conclude that your project's requirements are unsatisfiable. "### ); diff --git a/crates/uv/tests/it/pip_compile.rs b/crates/uv/tests/it/pip_compile.rs index 1a6de326ad07..7adf4a635f0e 100644 --- a/crates/uv/tests/it/pip_compile.rs +++ b/crates/uv/tests/it/pip_compile.rs @@ -6859,10 +6859,10 @@ dependencies = [ // Write to a requirements file. let requirements_in = context.temp_dir.child("requirements.in"); - requirements_in.write_str(&indoc::formatdoc! {r#" + requirements_in.write_str(&indoc::formatdoc! {r" -e {} -e {} - "#, + ", editable_dir1.path().display(), editable_dir2.path().display() })?; @@ -7494,9 +7494,9 @@ fn universal_platform_fork() -> Result<()> { # via torch sympy==1.13.1 # via torch - torch==2.5.1 ; sys_platform == 'darwin' + torch==2.5.1 ; (platform_machine == 'aarch64' and sys_platform == 'linux') or sys_platform == 'darwin' # via -r requirements.in - torch==2.5.1+cpu ; sys_platform != 'darwin' + torch==2.5.1+cpu ; (platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux') # via -r requirements.in typing-extensions==4.9.0 # via torch @@ -7629,9 +7629,9 @@ fn universal_transitive_disjoint_locals() -> Result<()> { # -r requirements.in # torchvision # triton - torchvision==0.15.1 ; sys_platform == 'darwin' or sys_platform == 'win32' + torchvision==0.15.1 ; platform_machine != 'x86_64' or sys_platform == 'darwin' # via -r requirements.in - torchvision==0.15.1+rocm5.4.2 ; sys_platform != 'darwin' and sys_platform != 'win32' + torchvision==0.15.1+rocm5.4.2 ; platform_machine == 'x86_64' and sys_platform != 'darwin' # via -r requirements.in triton==2.0.0 ; platform_machine == 'x86_64' and sys_platform == 'linux' # via torch @@ -7924,11 +7924,11 @@ fn universal_disjoint_base_or_local_requirement() -> Result<()> { # via torch sympy==1.12 # via torch - torch==2.0.0 ; python_full_version < '3.11' and sys_platform == 'darwin' + torch==2.0.0 ; (python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform == 'darwin') # via # -r requirements.in # example - torch==2.0.0+cpu ; python_full_version >= '3.13' or (python_full_version < '3.11' and sys_platform != 'darwin') + torch==2.0.0+cpu ; python_full_version >= '3.13' or (python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux') # via # -r requirements.in # example @@ -7963,15 +7963,15 @@ fn universal_nested_overlapping_local_requirement() -> Result<()> { name = "example" version = "0.0.0" dependencies = [ - "torch==2.0.0+cu118 ; platform_machine == 'x86_64' and os_name == 'Linux'" + "torch==2.0.0+cu118 ; implementation_name == 'cpython' and os_name == 'Linux'" ] requires-python = ">=3.11" "#})?; let requirements_in = context.temp_dir.child("requirements.in"); requirements_in.write_str(indoc! {" - torch==2.0.0 ; platform_machine == 'x86_64' - torch==2.3.0 ; platform_machine != 'x86_64' + torch==2.0.0 ; implementation_name == 'cpython' + torch==2.3.0 ; implementation_name != 'cpython' . "})?; @@ -7985,7 +7985,7 @@ fn universal_nested_overlapping_local_requirement() -> Result<()> { ----- stdout ----- # This file was autogenerated by uv via the following command: # uv pip compile --cache-dir [CACHE_DIR] requirements.in --universal - cmake==3.28.4 ; platform_machine == 'x86_64' and sys_platform == 'linux' + cmake==3.28.4 ; implementation_name == 'cpython' and platform_machine == 'x86_64' and sys_platform == 'linux' # via triton . # via -r requirements.in @@ -7994,38 +7994,38 @@ fn universal_nested_overlapping_local_requirement() -> Result<()> { # pytorch-triton-rocm # torch # triton - fsspec==2024.3.1 ; platform_machine != 'x86_64' + fsspec==2024.3.1 ; implementation_name != 'cpython' # via torch - intel-openmp==2021.4.0 ; platform_machine != 'x86_64' and sys_platform == 'win32' + intel-openmp==2021.4.0 ; implementation_name != 'cpython' and sys_platform == 'win32' # via mkl jinja2==3.1.3 # via torch - lit==18.1.2 ; platform_machine == 'x86_64' and sys_platform == 'linux' + lit==18.1.2 ; implementation_name == 'cpython' and platform_machine == 'x86_64' and sys_platform == 'linux' # via triton markupsafe==2.1.5 # via jinja2 - mkl==2021.4.0 ; platform_machine != 'x86_64' and sys_platform == 'win32' + mkl==2021.4.0 ; implementation_name != 'cpython' and sys_platform == 'win32' # via torch mpmath==1.3.0 # via sympy networkx==3.2.1 # via torch - pytorch-triton-rocm==2.3.0 ; platform_machine != 'x86_64' and sys_platform != 'darwin' and sys_platform != 'win32' + pytorch-triton-rocm==2.3.0 ; (implementation_name != 'cpython' and platform_machine != 'aarch64' and sys_platform == 'linux') or (implementation_name != 'cpython' and sys_platform != 'darwin' and sys_platform != 'linux') # via torch sympy==1.12 # via torch - tbb==2021.11.0 ; platform_machine != 'x86_64' and sys_platform == 'win32' + tbb==2021.11.0 ; implementation_name != 'cpython' and sys_platform == 'win32' # via mkl - torch==2.0.0+cu118 ; platform_machine == 'x86_64' + torch==2.0.0+cu118 ; implementation_name == 'cpython' # via # -r requirements.in # example # triton - torch==2.3.0 ; (platform_machine != 'x86_64' and sys_platform == 'darwin') or (platform_machine != 'x86_64' and sys_platform == 'win32') + torch==2.3.0 ; (implementation_name != 'cpython' and platform_machine == 'aarch64' and sys_platform == 'linux') or (implementation_name != 'cpython' and sys_platform == 'darwin') # via -r requirements.in - torch==2.3.0+rocm6.0 ; platform_machine != 'x86_64' and sys_platform != 'darwin' and sys_platform != 'win32' + torch==2.3.0+rocm6.0 ; (implementation_name != 'cpython' and platform_machine != 'aarch64' and sys_platform == 'linux') or (implementation_name != 'cpython' and sys_platform != 'darwin' and sys_platform != 'linux') # via -r requirements.in - triton==2.0.0 ; platform_machine == 'x86_64' and sys_platform == 'linux' + triton==2.0.0 ; implementation_name == 'cpython' and platform_machine == 'x86_64' and sys_platform == 'linux' # via torch typing-extensions==4.10.0 # via torch @@ -8070,7 +8070,6 @@ fn universal_nested_overlapping_local_requirement() -> Result<()> { # via -r requirements.in filelock==3.13.1 # via - # pytorch-triton-rocm # torch # triton fsspec==2024.3.1 ; platform_machine != 'x86_64' @@ -8089,8 +8088,6 @@ fn universal_nested_overlapping_local_requirement() -> Result<()> { # via sympy networkx==3.2.1 # via torch - pytorch-triton-rocm==2.3.0 ; platform_machine != 'x86_64' and sys_platform != 'darwin' and sys_platform != 'win32' - # via torch sympy==1.12 # via torch tbb==2021.11.0 ; platform_machine != 'x86_64' and sys_platform == 'win32' @@ -8100,9 +8097,7 @@ fn universal_nested_overlapping_local_requirement() -> Result<()> { # -r requirements.in # example # triton - torch==2.3.0 ; (platform_machine != 'x86_64' and sys_platform == 'darwin') or (platform_machine != 'x86_64' and sys_platform == 'win32') - # via -r requirements.in - torch==2.3.0+rocm6.0 ; platform_machine != 'x86_64' and sys_platform != 'darwin' and sys_platform != 'win32' + torch==2.3.0 ; platform_machine != 'x86_64' # via -r requirements.in triton==2.0.0 ; platform_machine == 'x86_64' and sys_platform == 'linux' # via torch @@ -8110,7 +8105,7 @@ fn universal_nested_overlapping_local_requirement() -> Result<()> { # via torch ----- stderr ----- - Resolved 19 packages in [TIME] + Resolved 17 packages in [TIME] "### ); @@ -8129,8 +8124,8 @@ fn universal_nested_disjoint_local_requirement() -> Result<()> { name = "example" version = "0.0.0" dependencies = [ - "torch==2.0.0+cu118 ; platform_machine == 'x86_64'", - "torch==2.0.0+cpu ; platform_machine != 'x86_64'" + "torch==2.0.0+cu118 ; implementation_name == 'cpython'", + "torch==2.0.0+cpu ; implementation_name != 'cpython'" ] requires-python = ">=3.11" "#})?; @@ -8154,7 +8149,7 @@ fn universal_nested_disjoint_local_requirement() -> Result<()> { ----- stdout ----- # This file was autogenerated by uv via the following command: # uv pip compile --cache-dir [CACHE_DIR] requirements.in --universal - cmake==3.28.4 ; os_name == 'Linux' and platform_machine == 'x86_64' and sys_platform == 'linux' + cmake==3.28.4 ; implementation_name == 'cpython' and os_name == 'Linux' and platform_machine == 'x86_64' and sys_platform == 'linux' # via triton . ; os_name == 'Linux' # via -r requirements.in @@ -8169,7 +8164,7 @@ fn universal_nested_disjoint_local_requirement() -> Result<()> { # via mkl jinja2==3.1.3 # via torch - lit==18.1.2 ; os_name == 'Linux' and platform_machine == 'x86_64' and sys_platform == 'linux' + lit==18.1.2 ; implementation_name == 'cpython' and os_name == 'Linux' and platform_machine == 'x86_64' and sys_platform == 'linux' # via triton markupsafe==2.1.5 # via jinja2 @@ -8179,26 +8174,26 @@ fn universal_nested_disjoint_local_requirement() -> Result<()> { # via sympy networkx==3.2.1 # via torch - pytorch-triton-rocm==2.3.0 ; os_name != 'Linux' and sys_platform != 'darwin' and sys_platform != 'win32' + pytorch-triton-rocm==2.3.0 ; (os_name != 'Linux' and platform_machine != 'aarch64' and sys_platform == 'linux') or (os_name != 'Linux' and sys_platform != 'darwin' and sys_platform != 'linux') # via torch sympy==1.12 # via torch tbb==2021.11.0 ; os_name != 'Linux' and sys_platform == 'win32' # via mkl - torch==2.0.0+cpu ; os_name == 'Linux' and platform_machine != 'x86_64' + torch==2.0.0+cpu ; implementation_name != 'cpython' and os_name == 'Linux' # via # -r requirements.in # example - torch==2.0.0+cu118 ; os_name == 'Linux' and platform_machine == 'x86_64' + torch==2.0.0+cu118 ; implementation_name == 'cpython' and os_name == 'Linux' # via # -r requirements.in # example # triton - torch==2.3.0 ; (os_name != 'Linux' and sys_platform == 'darwin') or (os_name != 'Linux' and sys_platform == 'win32') + torch==2.3.0 ; (os_name != 'Linux' and platform_machine == 'aarch64' and sys_platform == 'linux') or (os_name != 'Linux' and sys_platform == 'darwin') # via -r requirements.in - torch==2.3.0+rocm6.0 ; os_name != 'Linux' and sys_platform != 'darwin' and sys_platform != 'win32' + torch==2.3.0+rocm6.0 ; (os_name != 'Linux' and platform_machine != 'aarch64' and sys_platform == 'linux') or (os_name != 'Linux' and sys_platform != 'darwin' and sys_platform != 'linux') # via -r requirements.in - triton==2.0.0 ; os_name == 'Linux' and platform_machine == 'x86_64' and sys_platform == 'linux' + triton==2.0.0 ; implementation_name == 'cpython' and os_name == 'Linux' and platform_machine == 'x86_64' and sys_platform == 'linux' # via torch typing-extensions==4.10.0 # via torch @@ -8993,8 +8988,6 @@ fn universal_marker_propagation() -> Result<()> { # via torchvision pytorch-triton-rocm==2.0.2 ; platform_machine == 'x86_64' and sys_platform != 'darwin' and sys_platform != 'win32' # via torch - pytorch-triton-rocm==2.2.0 ; platform_machine != 'x86_64' and sys_platform != 'darwin' and sys_platform != 'win32' - # via torch requests==2.31.0 # via torchvision sympy==1.12 @@ -9008,11 +9001,7 @@ fn universal_marker_propagation() -> Result<()> { # -r requirements.in # pytorch-triton-rocm # torchvision - torch==2.2.0 ; (platform_machine != 'x86_64' and sys_platform == 'darwin') or (platform_machine != 'x86_64' and sys_platform == 'win32') - # via - # -r requirements.in - # torchvision - torch==2.2.0+rocm5.7 ; platform_machine != 'x86_64' and sys_platform != 'darwin' and sys_platform != 'win32' + torch==2.2.0 ; platform_machine != 'x86_64' # via # -r requirements.in # torchvision @@ -9020,9 +9009,7 @@ fn universal_marker_propagation() -> Result<()> { # via -r requirements.in torchvision==0.15.1+rocm5.4.2 ; platform_machine == 'x86_64' and sys_platform != 'darwin' and sys_platform != 'win32' # via -r requirements.in - torchvision==0.17.0 ; (platform_machine != 'x86_64' and sys_platform == 'darwin') or (platform_machine != 'x86_64' and sys_platform == 'win32') - # via -r requirements.in - torchvision==0.17.0+rocm5.7 ; platform_machine != 'x86_64' and sys_platform != 'darwin' and sys_platform != 'win32' + torchvision==0.17.0 ; platform_machine != 'x86_64' # via -r requirements.in typing-extensions==4.10.0 # via torch @@ -9031,7 +9018,7 @@ fn universal_marker_propagation() -> Result<()> { ----- stderr ----- warning: The requested Python version 3.8 is not available; 3.12.[X] will be used to build dependencies instead. - Resolved 28 packages in [TIME] + Resolved 25 packages in [TIME] "### ); @@ -13283,7 +13270,9 @@ exceptiongroup==1.0.0rc8 # uv pip compile --cache-dir [CACHE_DIR] requirements.in -c constraints.txt --universal -p 3.10 alembic==1.8.1 # via -r requirements.in - astroid==2.13.5 + astroid==2.13.5 ; python_full_version >= '3.11' + # via pylint + astroid==3.1.0 ; python_full_version < '3.11' # via pylint asttokens==2.4.1 # via stack-data @@ -13311,7 +13300,7 @@ exceptiongroup==1.0.0rc8 # via pylint jedi==0.19.1 # via ipython - lazy-object-proxy==1.10.0 + lazy-object-proxy==1.10.0 ; python_full_version >= '3.11' # via astroid mako==1.3.2 # via alembic @@ -13335,7 +13324,9 @@ exceptiongroup==1.0.0rc8 # via stack-data pygments==2.17.2 # via ipython - pylint==2.15.8 + pylint==2.15.8 ; python_full_version >= '3.11' + # via -r requirements.in + pylint==3.1.0 ; python_full_version < '3.11' # via -r requirements.in six==1.16.0 # via asttokens @@ -13357,11 +13348,11 @@ exceptiongroup==1.0.0rc8 # sqlalchemy wcwidth==0.2.13 # via prompt-toolkit - wrapt==1.16.0 + wrapt==1.16.0 ; python_full_version >= '3.11' # via astroid ----- stderr ----- - Resolved 34 packages in [TIME] + Resolved 36 packages in [TIME] "###); Ok(()) @@ -13635,9 +13626,9 @@ fn unsupported_requires_python_dynamic_metadata() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies for split (python_full_version >= '3.10'): - ╰─▶ Because source-distribution{python_full_version >= '3.10'}==0.0.3 requires Python >=3.10 and you require source-distribution{python_full_version >= '3.10'}==0.0.3, we can conclude that your requirements are unsatisfiable. + ╰─▶ Because source-distribution==0.0.3 requires Python >=3.10 and you require source-distribution{python_full_version >= '3.10'}==0.0.3, we can conclude that your requirements are unsatisfiable. - hint: The source distribution for `source-distribution{python_full_version >= '3.10'}` (v0.0.3) does not include static metadata. Generating metadata for this package requires Python >=3.10, but Python 3.8.[X] is installed. + hint: The source distribution for `source-distribution` (v0.0.3) does not include static metadata. Generating metadata for this package requires Python >=3.10, but Python 3.8.[X] is installed. "###); Ok(()) @@ -13953,25 +13944,8 @@ fn invalid_platform() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because only the following versions of open3d are available: - open3d==0.8.0.0 - open3d==0.9.0.0 - open3d==0.10.0.0 - open3d==0.10.0.1 - open3d==0.11.0 - open3d==0.11.1 - open3d==0.11.2 - open3d==0.12.0 - open3d==0.13.0 - open3d==0.14.1 - open3d==0.15.1 - open3d==0.15.2 - open3d==0.16.0 - open3d==0.16.1 - open3d==0.17.0 - open3d==0.18.0 - and open3d<=0.15.2 has no wheels with a matching Python ABI tag, we can conclude that open3d<=0.15.2 cannot be used. - And because open3d>=0.16.0 has no wheels with a matching platform tag and you require open3d, we can conclude that your requirements are unsatisfiable. + ╰─▶ Because only open3d<=0.18.0 is available and open3d<=0.15.2 has no wheels with a matching Python ABI tag, we can conclude that open3d<=0.15.2 cannot be used. + And because open3d>=0.16.0,<=0.18.0 has no wheels with a matching platform tag and you require open3d, we can conclude that your requirements are unsatisfiable. "###); Ok(()) diff --git a/crates/uv/tests/it/pip_compile_scenarios.rs b/crates/uv/tests/it/pip_compile_scenarios.rs index 722d3f0e001c..a49792c8782d 100644 --- a/crates/uv/tests/it/pip_compile_scenarios.rs +++ b/crates/uv/tests/it/pip_compile_scenarios.rs @@ -1,7 +1,7 @@ //! DO NOT EDIT //! //! Generated with `./scripts/sync_scenarios.sh` -//! Scenarios from +//! Scenarios from //! #![cfg(all(feature = "python", feature = "pypi", unix))] diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index 1a8df7a7a68c..14e7dee5008b 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -218,8 +218,8 @@ fn invalid_pyproject_toml_option_unknown_field() -> Result<()> { let mut filters = context.filters(); filters.push(( - "expected one of `native-tls`, `offline`, .*", - "expected one of `native-tls`, `offline`, [...]", + "expected one of `required-version`, `native-tls`, .*", + "expected one of `required-version`, `native-tls`, [...]", )); uv_snapshot!(filters, context.pip_install() @@ -235,7 +235,7 @@ fn invalid_pyproject_toml_option_unknown_field() -> Result<()> { | 2 | unknown = "field" | ^^^^^^^ - unknown field `unknown`, expected one of `native-tls`, `offline`, [...] + unknown field `unknown`, expected one of `required-version`, `native-tls`, [...] Resolved in [TIME] Audited in [TIME] @@ -486,6 +486,39 @@ fn install_requirements_txt() -> Result<()> { Ok(()) } +/// Warn (but don't fail) when unsupported flags are set in the `requirements.txt`. +#[test] +fn install_unsupported_flag() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str(indoc! {r" + --pre + --prefer-binary :all: + iniconfig + "})?; + + uv_snapshot!(context.pip_install() + .arg("-r") + .arg("requirements.txt") + .arg("--strict"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: Ignoring unsupported option in `requirements.txt`: `--pre` (hint: pass `--pre` on the command line instead) + warning: Ignoring unsupported option in `requirements.txt`: `--prefer-binary` + Resolved 1 package in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + iniconfig==2.0.0 + "### + ); + + Ok(()) +} + /// Install a requirements file with pins that conflict /// /// This is likely to occur in the real world when compiled on one platform then installed on another. @@ -2617,6 +2650,93 @@ fn no_deps_editable() { context.assert_command("import aiohttp").failure(); } +/// Avoid downgrading already-installed packages when `--upgrade` is provided. +#[test] +fn install_no_downgrade() -> Result<()> { + let context = TestContext::new("3.12"); + + // Create a local package named `idna`. + let idna = context.temp_dir.child("idna"); + idna.child("pyproject.toml").write_str(indoc! {r#" + [project] + name = "idna" + version = "1000" + requires-python = ">=3.12" + dependencies = [] + + [build-system] + requires = ["setuptools>=42"] + build-backend = "setuptools.build_meta" + "#})?; + + // Install the local `idna`. + uv_snapshot!(context.filters(), context.pip_install() + .arg("./idna"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + idna==1000 (from file://[TEMP_DIR]/idna) + "### + ); + + // Install `anyio`, which depends on `idna`. + uv_snapshot!(context.filters(), context.pip_install() + .arg("anyio"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 3 packages in [TIME] + Prepared 2 packages in [TIME] + Installed 2 packages in [TIME] + + anyio==4.3.0 + + sniffio==1.3.1 + "### + ); + + // Install `anyio` with `--upgrade`, which should retain the local `idna`. + uv_snapshot!(context.filters(), context.pip_install() + .arg("-U") + .arg("anyio"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 3 packages in [TIME] + Audited 3 packages in [TIME] + "### + ); + + // Install `anyio` with `--reinstall`, which should downgrade `idna`. + uv_snapshot!(context.filters(), context.pip_install() + .arg("--reinstall") + .arg("anyio"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 3 packages in [TIME] + Prepared 3 packages in [TIME] + Uninstalled 3 packages in [TIME] + Installed 3 packages in [TIME] + ~ anyio==4.3.0 + - idna==1000 (from file://[TEMP_DIR]/idna) + + idna==3.6 + ~ sniffio==1.3.1 + "### + ); + + Ok(()) +} + /// Upgrade a package. #[test] fn install_upgrade() { @@ -5311,14 +5431,13 @@ fn already_installed_local_path_dependent() { .arg("--no-index") .arg("--find-links") .arg(build_vendor_links_url()), @r###" - success: false - exit_code: 1 + success: true + exit_code: 0 ----- stdout ----- ----- stderr ----- - × No solution found when resolving dependencies: - ╰─▶ Because first-local was not found in the provided package locations and second-local==0.1.0 depends on first-local, we can conclude that second-local==0.1.0 cannot be used. - And because only second-local==0.1.0 is available and you require second-local, we can conclude that your requirements are unsatisfiable. + Resolved 2 packages in [TIME] + Audited 2 packages in [TIME] "### ); @@ -5477,8 +5596,8 @@ fn already_installed_local_version_of_remote_package() { ----- stdout ----- ----- stderr ----- - Resolved 3 packages in [TIME] - Audited 3 packages in [TIME] + Resolved 1 package in [TIME] + Audited 1 package in [TIME] "### ); @@ -6221,6 +6340,148 @@ fn require_hashes_override() -> Result<()> { Ok(()) } +/// Provide valid hashes for all dependencies with `--require-hashes` with accompanying markers. +/// Critically, one package (`requests`) depends on another (`urllib3`). +#[test] +fn require_hashes_marker() -> Result<()> { + static EXCLUDE_NEWER: &str = "2025-01-01T00:00:00Z"; + + let context = TestContext::new("3.12"); + + // Write to a requirements file. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str(indoc::indoc! {r" + certifi==2024.12.14 ; python_version >= '3.8' \ + --hash=sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56 \ + --hash=sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db + charset-normalizer==3.4.1 ; python_version >= '3.8' \ + --hash=sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537 \ + --hash=sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa \ + --hash=sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a \ + --hash=sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294 \ + --hash=sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b \ + --hash=sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd \ + --hash=sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601 \ + --hash=sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd \ + --hash=sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4 \ + --hash=sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d \ + --hash=sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2 \ + --hash=sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313 \ + --hash=sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd \ + --hash=sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa \ + --hash=sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8 \ + --hash=sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1 \ + --hash=sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2 \ + --hash=sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496 \ + --hash=sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d \ + --hash=sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b \ + --hash=sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e \ + --hash=sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a \ + --hash=sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4 \ + --hash=sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca \ + --hash=sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78 \ + --hash=sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408 \ + --hash=sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5 \ + --hash=sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3 \ + --hash=sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f \ + --hash=sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a \ + --hash=sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765 \ + --hash=sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6 \ + --hash=sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146 \ + --hash=sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6 \ + --hash=sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9 \ + --hash=sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd \ + --hash=sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c \ + --hash=sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f \ + --hash=sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545 \ + --hash=sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176 \ + --hash=sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770 \ + --hash=sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824 \ + --hash=sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f \ + --hash=sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf \ + --hash=sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487 \ + --hash=sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d \ + --hash=sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd \ + --hash=sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b \ + --hash=sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534 \ + --hash=sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f \ + --hash=sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b \ + --hash=sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9 \ + --hash=sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd \ + --hash=sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125 \ + --hash=sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9 \ + --hash=sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de \ + --hash=sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11 \ + --hash=sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d \ + --hash=sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35 \ + --hash=sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f \ + --hash=sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda \ + --hash=sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7 \ + --hash=sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a \ + --hash=sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971 \ + --hash=sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8 \ + --hash=sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41 \ + --hash=sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d \ + --hash=sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f \ + --hash=sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757 \ + --hash=sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a \ + --hash=sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886 \ + --hash=sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77 \ + --hash=sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76 \ + --hash=sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247 \ + --hash=sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85 \ + --hash=sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb \ + --hash=sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7 \ + --hash=sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e \ + --hash=sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6 \ + --hash=sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037 \ + --hash=sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1 \ + --hash=sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e \ + --hash=sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807 \ + --hash=sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407 \ + --hash=sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c \ + --hash=sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12 \ + --hash=sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3 \ + --hash=sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089 \ + --hash=sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd \ + --hash=sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e \ + --hash=sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00 \ + --hash=sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616 + idna==3.10 ; python_version >= '3.8' \ + --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \ + --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 + requests==2.32.3 ; python_version >= '3.8' \ + --hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \ + --hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6 + urllib3==2.2.3 ; python_version >= '3.8' \ + --hash=sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac \ + --hash=sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9 + "})?; + + uv_snapshot!(context.pip_install() + .env(EnvVars::UV_EXCLUDE_NEWER, EXCLUDE_NEWER) + .arg("-r") + .arg("requirements.txt") + .arg("--require-hashes"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 5 packages in [TIME] + Prepared 5 packages in [TIME] + Installed 5 packages in [TIME] + + certifi==2024.12.14 + + charset-normalizer==3.4.1 + + idna==3.10 + + requests==2.32.3 + + urllib3==2.2.3 + "### + ); + + Ok(()) +} + /// Provide valid hashes for all dependencies with `--require-hashes`. #[test] fn verify_hashes() -> Result<()> { @@ -6816,10 +7077,10 @@ fn local_index_requirements_txt_absolute() -> Result<()> { "#, Url::from_directory_path(context.workspace_root.join("scripts/links/")).unwrap().as_str()})?; let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.write_str(&indoc::formatdoc! {r#" + requirements_txt.write_str(&indoc::formatdoc! {r" --index-url {} tqdm - "#, Url::from_directory_path(root).unwrap().as_str()})?; + ", Url::from_directory_path(root).unwrap().as_str()})?; uv_snapshot!(context.filters(), context.pip_install() .env_remove(EnvVars::UV_EXCLUDE_NEWER) @@ -7724,3 +7985,189 @@ fn missing_subdirectory_url() -> Result<()> { Ok(()) } + +#[test] +fn static_metadata_pyproject_toml() -> Result<()> { + let context = TestContext::new("3.12"); + + context.temp_dir.child("pyproject.toml").write_str( + r#" + [project] + name = "example" + version = "0.0.0" + dependencies = [ + "anyio==3.7.0" + ] + + [[tool.uv.dependency-metadata]] + name = "anyio" + version = "3.7.0" + requires-dist = ["typing-extensions"] + "#, + )?; + + uv_snapshot!(context.filters(), context.pip_install() + .arg("-r") + .arg("pyproject.toml"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + Prepared 2 packages in [TIME] + Installed 2 packages in [TIME] + + anyio==3.7.0 + + typing-extensions==4.10.0 + "### + ); + + Ok(()) +} + +#[test] +fn static_metadata_source_tree() -> Result<()> { + let context = TestContext::new("3.12"); + + context.temp_dir.child("pyproject.toml").write_str( + r#" + [project] + name = "example" + version = "0.0.0" + dependencies = [ + "anyio==3.7.0" + ] + + [[tool.uv.dependency-metadata]] + name = "anyio" + version = "3.7.0" + requires-dist = ["typing-extensions"] + "#, + )?; + + uv_snapshot!(context.filters(), context.pip_install() + .arg("-e") + .arg("."), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 3 packages in [TIME] + Prepared 3 packages in [TIME] + Installed 3 packages in [TIME] + + anyio==3.7.0 + + example==0.0.0 (from file://[TEMP_DIR]/) + + typing-extensions==4.10.0 + "### + ); + + Ok(()) +} + +/// Regression test for: +#[test] +fn static_metadata_already_installed() -> Result<()> { + let context = TestContext::new("3.12"); + + context.temp_dir.child("pyproject.toml").write_str( + r#" + [project] + name = "example" + version = "0.0.0" + dependencies = [ + "anyio==3.7.0" + ] + + [[tool.uv.dependency-metadata]] + name = "anyio" + version = "3.7.0" + requires-dist = ["typing-extensions"] + "#, + )?; + + uv_snapshot!(context.filters(), context.pip_install() + .arg("-r") + .arg("pyproject.toml"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + Prepared 2 packages in [TIME] + Installed 2 packages in [TIME] + + anyio==3.7.0 + + typing-extensions==4.10.0 + "### + ); + + uv_snapshot!(context.filters(), context.pip_install() + .arg("-e") + .arg("."), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 3 packages in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + example==0.0.0 (from file://[TEMP_DIR]/) + "### + ); + + Ok(()) +} + +/// `circular-one` depends on `circular-two` as a build dependency, but `circular-two` depends on +/// `circular-one` was a runtime dependency. +#[test] +fn cyclic_build_dependency() { + static EXCLUDE_NEWER: &str = "2025-01-02T00:00:00Z"; + + let context = TestContext::new("3.13"); + + // Installing with `--no-binary circular-one` should fail, since we'll end up in a recursive + // build. + uv_snapshot!(context.filters(), context.pip_install() + .env(EnvVars::UV_EXCLUDE_NEWER, EXCLUDE_NEWER) + .arg("circular-one") + .arg("--extra-index-url") + .arg("https://test.pypi.org/simple") + .arg("--index-strategy") + .arg("unsafe-best-match") + .arg("--no-binary") + .arg("circular-one"), @r###" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + × Failed to download and build `circular-one==0.2.0` + ├─▶ Failed to install requirements from `build-system.requires` + ╰─▶ Cyclic build dependency detected for `circular-one` + "### + ); + + // Installing without `--no-binary circular-one` should succeed, since we can use the wheel. + uv_snapshot!(context.filters(), context.pip_install() + .env(EnvVars::UV_EXCLUDE_NEWER, EXCLUDE_NEWER) + .arg("circular-one") + .arg("--extra-index-url") + .arg("https://test.pypi.org/simple") + .arg("--index-strategy") + .arg("unsafe-best-match"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + circular-one==0.2.0 + "### + ); +} diff --git a/crates/uv/tests/it/pip_install_scenarios.rs b/crates/uv/tests/it/pip_install_scenarios.rs index cd8882c870e7..aa16c7941d85 100644 --- a/crates/uv/tests/it/pip_install_scenarios.rs +++ b/crates/uv/tests/it/pip_install_scenarios.rs @@ -1,7 +1,7 @@ //! DO NOT EDIT //! //! Generated with `./scripts/sync_scenarios.sh` -//! Scenarios from +//! Scenarios from //! #![cfg(all(feature = "python", feature = "pypi", unix))] @@ -531,17 +531,17 @@ fn excluded_only_compatible_version() { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because only the following versions of package-a are available: + ╰─▶ Because package-a==1.0.0 depends on package-b==1.0.0 and only the following versions of package-a are available: package-a==1.0.0 package-a==2.0.0 package-a==3.0.0 - and package-a==1.0.0 depends on package-b==1.0.0, we can conclude that package-a<2.0.0 depends on package-b==1.0.0. + we can conclude that package-a<2.0.0 depends on package-b==1.0.0. And because package-a==3.0.0 depends on package-b==3.0.0, we can conclude that all of: package-a<2.0.0 package-a>2.0.0 depend on one of: - package-b==1.0.0 - package-b==3.0.0 + package-b<=1.0.0 + package-b>=3.0.0 And because you require one of: package-a<2.0.0 @@ -836,8 +836,8 @@ fn extra_incompatible_with_extra() { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because only package-a[extra-c]==1.0.0 is available and package-a[extra-c]==1.0.0 depends on package-b==2.0.0, we can conclude that all versions of package-a[extra-c] depend on package-b==2.0.0. - And because package-a[extra-b]==1.0.0 depends on package-b==1.0.0 and only package-a[extra-b]==1.0.0 is available, we can conclude that all versions of package-a[extra-b] and all versions of package-a[extra-c] are incompatible. + ╰─▶ Because package-a[extra-b]==1.0.0 depends on package-b==1.0.0 and package-a[extra-c]==1.0.0 depends on package-b==2.0.0, we can conclude that package-a[extra-b]==1.0.0 and package-a[extra-c]==1.0.0 are incompatible. + And because only package-a[extra-c]==1.0.0 is available and only package-a[extra-b]==1.0.0 is available, we can conclude that all versions of package-a[extra-b] and all versions of package-a[extra-c] are incompatible. And because you require package-a[extra-b] and package-a[extra-c], we can conclude that your requirements are unsatisfiable. "###); @@ -4079,6 +4079,7 @@ fn no_sdist_no_wheels_with_matching_abi() { filters.push((r"no-sdist-no-wheels-with-matching-abi-", "package-")); uv_snapshot!(filters, command(&context) + .arg("--python-platform=x86_64-manylinux2014") .arg("no-sdist-no-wheels-with-matching-abi-a") , @r###" success: false @@ -4119,6 +4120,7 @@ fn no_sdist_no_wheels_with_matching_platform() { filters.push((r"no-sdist-no-wheels-with-matching-platform-", "package-")); uv_snapshot!(filters, command(&context) + .arg("--python-platform=x86_64-manylinux2014") .arg("no-sdist-no-wheels-with-matching-platform-a") , @r###" success: false @@ -4159,6 +4161,7 @@ fn no_sdist_no_wheels_with_matching_python() { filters.push((r"no-sdist-no-wheels-with-matching-python-", "package-")); uv_snapshot!(filters, command(&context) + .arg("--python-platform=x86_64-manylinux2014") .arg("no-sdist-no-wheels-with-matching-python-a") , @r###" success: false diff --git a/crates/uv/tests/it/run.rs b/crates/uv/tests/it/run.rs index b330ba9cac62..d915d58522b7 100644 --- a/crates/uv/tests/it/run.rs +++ b/crates/uv/tests/it/run.rs @@ -324,6 +324,9 @@ fn run_pep723_script() -> Result<()> { Resolved 1 package in [TIME] "###); + // But neither invocation should create a lockfile. + assert!(!context.temp_dir.child("main.py.lock").exists()); + // Otherwise, the script requirements should _not_ be available, but the project requirements // should. let test_non_script = context.temp_dir.child("main.py"); @@ -774,6 +777,190 @@ fn run_pep723_script_overrides() -> Result<()> { Ok(()) } +/// Run a PEP 723-compatible script with a lockfile. +#[test] +fn run_pep723_script_lock() -> Result<()> { + let context = TestContext::new("3.12"); + + let test_script = context.temp_dir.child("main.py"); + test_script.write_str(indoc! { r#" + # /// script + # requires-python = ">=3.11" + # dependencies = [ + # "iniconfig", + # ] + # /// + + import iniconfig + + print("Hello, world!") + "# + })?; + + // Explicitly lock the script. + uv_snapshot!(context.filters(), context.lock().arg("--script").arg("main.py"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + "###); + + let lock = context.read("main.py.lock"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + lock, @r###" + version = 1 + requires-python = ">=3.11" + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [manifest] + requirements = [{ name = "iniconfig" }] + + [[package]] + name = "iniconfig" + version = "2.0.0" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, + ] + "### + ); + }); + + uv_snapshot!(context.filters(), context.run().arg("main.py"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Hello, world! + + ----- stderr ----- + Reading inline script metadata from `main.py` + Resolved 1 package in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + iniconfig==2.0.0 + "###); + + // Modify the metadata. + test_script.write_str(indoc! { r#" + # /// script + # requires-python = ">=3.11" + # dependencies = [ + # "anyio", + # ] + # /// + + import anyio + + print("Hello, world!") + "# + })?; + + // Re-running the script with `--locked` should error. + uv_snapshot!(context.filters(), context.run().arg("--locked").arg("main.py"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Reading inline script metadata from `main.py` + Resolved 3 packages in [TIME] + error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + "###); + + // Re-running the script with `--frozen` should also error, but at runtime. + uv_snapshot!(context.filters(), context.run().arg("--frozen").arg("main.py"), @r###" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + Reading inline script metadata from `main.py` + warning: `--frozen` is a no-op for Python scripts with inline metadata, which always run in isolation + Traceback (most recent call last): + File "[TEMP_DIR]/main.py", line 8, in + import anyio + ModuleNotFoundError: No module named 'anyio' + "###); + + // Re-running the script should update the lockfile. + uv_snapshot!(context.filters(), context.run().arg("main.py"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Hello, world! + + ----- stderr ----- + Reading inline script metadata from `main.py` + Resolved 3 packages in [TIME] + Prepared 3 packages in [TIME] + Installed 3 packages in [TIME] + + anyio==4.3.0 + + idna==3.6 + + sniffio==1.3.1 + "###); + + let lock = context.read("main.py.lock"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + lock, @r###" + version = 1 + requires-python = ">=3.11" + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [manifest] + requirements = [{ name = "anyio" }] + + [[package]] + name = "anyio" + version = "4.3.0" + source = { registry = "https://pypi.org/simple" } + dependencies = [ + { name = "idna" }, + { name = "sniffio" }, + ] + sdist = { url = "https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6", size = 159642 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8", size = 85584 }, + ] + + [[package]] + name = "idna" + version = "3.6" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", size = 175426 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f", size = 61567 }, + ] + + [[package]] + name = "sniffio" + version = "1.3.1" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, + ] + "### + ); + }); + + Ok(()) +} + /// With `managed = false`, we should avoid installing the project itself. #[test] fn run_managed_false() -> Result<()> { @@ -1937,19 +2124,51 @@ fn run_requirements_txt() -> Result<()> { + sniffio==1.3.1 "###); - // But reject `-` as a requirements file. + // Allow `-` for stdin. uv_snapshot!(context.filters(), context.run() .arg("--with-requirements") .arg("-") .arg("--with") .arg("iniconfig") - .arg("main.py"), @r###" + .arg("main.py") + .stdin(std::fs::File::open(&requirements_txt)?), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 6 packages in [TIME] + Audited 4 packages in [TIME] + Resolved 2 packages in [TIME] + "###); + + // But not in combination with reading the script from stdin + uv_snapshot!(context.filters(), context.run() + .arg("--with-requirements") + .arg("-") + // The script to run + .arg("-") + .stdin(std::fs::File::open(&requirements_txt)?), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Cannot read both requirements file and script from stdin + "###); + + uv_snapshot!(context.filters(), context.run() + .arg("--with-requirements") + .arg("-") + .arg("--script") + .arg("-") + .stdin(std::fs::File::open(&requirements_txt)?), @r###" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- - error: Reading requirements from stdin is not supported in `uv run` + error: Cannot read both requirements file and script from stdin "###); Ok(()) diff --git a/crates/uv/tests/it/show_settings.rs b/crates/uv/tests/it/show_settings.rs index fb8ea039f9ef..3cf5d5ddc4ea 100644 --- a/crates/uv/tests/it/show_settings.rs +++ b/crates/uv/tests/it/show_settings.rs @@ -63,6 +63,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> { exit_code: 0 ----- stdout ----- GlobalSettings { + required_version: None, quiet: false, verbose: 0, color: Auto, @@ -215,6 +216,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> { exit_code: 0 ----- stdout ----- GlobalSettings { + required_version: None, quiet: false, verbose: 0, color: Auto, @@ -368,6 +370,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> { exit_code: 0 ----- stdout ----- GlobalSettings { + required_version: None, quiet: false, verbose: 0, color: Auto, @@ -553,6 +556,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { exit_code: 0 ----- stdout ----- GlobalSettings { + required_version: None, quiet: false, verbose: 0, color: Auto, @@ -707,6 +711,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { exit_code: 0 ----- stdout ----- GlobalSettings { + required_version: None, quiet: false, verbose: 0, color: Auto, @@ -840,6 +845,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { exit_code: 0 ----- stdout ----- GlobalSettings { + required_version: None, quiet: false, verbose: 0, color: Auto, @@ -1017,6 +1023,7 @@ fn resolve_index_url() -> anyhow::Result<()> { exit_code: 0 ----- stdout ----- GlobalSettings { + required_version: None, quiet: false, verbose: 0, color: Auto, @@ -1200,6 +1207,7 @@ fn resolve_index_url() -> anyhow::Result<()> { exit_code: 0 ----- stdout ----- GlobalSettings { + required_version: None, quiet: false, verbose: 0, color: Auto, @@ -1437,6 +1445,7 @@ fn resolve_find_links() -> anyhow::Result<()> { exit_code: 0 ----- stdout ----- GlobalSettings { + required_version: None, quiet: false, verbose: 0, color: Auto, @@ -1613,6 +1622,7 @@ fn resolve_top_level() -> anyhow::Result<()> { exit_code: 0 ----- stdout ----- GlobalSettings { + required_version: None, quiet: false, verbose: 0, color: Auto, @@ -1752,6 +1762,7 @@ fn resolve_top_level() -> anyhow::Result<()> { exit_code: 0 ----- stdout ----- GlobalSettings { + required_version: None, quiet: false, verbose: 0, color: Auto, @@ -1933,6 +1944,7 @@ fn resolve_top_level() -> anyhow::Result<()> { exit_code: 0 ----- stdout ----- GlobalSettings { + required_version: None, quiet: false, verbose: 0, color: Auto, @@ -2138,6 +2150,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { exit_code: 0 ----- stdout ----- GlobalSettings { + required_version: None, quiet: false, verbose: 0, color: Auto, @@ -2267,6 +2280,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { exit_code: 0 ----- stdout ----- GlobalSettings { + required_version: None, quiet: false, verbose: 0, color: Auto, @@ -2396,6 +2410,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { exit_code: 0 ----- stdout ----- GlobalSettings { + required_version: None, quiet: false, verbose: 0, color: Auto, @@ -2527,6 +2542,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { exit_code: 0 ----- stdout ----- GlobalSettings { + required_version: None, quiet: false, verbose: 0, color: Auto, @@ -2677,6 +2693,7 @@ fn resolve_tool() -> anyhow::Result<()> { exit_code: 0 ----- stdout ----- GlobalSettings { + required_version: None, quiet: false, verbose: 0, color: Auto, @@ -2835,6 +2852,7 @@ fn resolve_poetry_toml() -> anyhow::Result<()> { exit_code: 0 ----- stdout ----- GlobalSettings { + required_version: None, quiet: false, verbose: 0, color: Auto, @@ -2992,6 +3010,7 @@ fn resolve_both() -> anyhow::Result<()> { exit_code: 0 ----- stdout ----- GlobalSettings { + required_version: None, quiet: false, verbose: 0, color: Auto, @@ -3267,6 +3286,7 @@ fn resolve_config_file() -> anyhow::Result<()> { exit_code: 0 ----- stdout ----- GlobalSettings { + required_version: None, quiet: false, verbose: 0, color: Auto, @@ -3438,7 +3458,7 @@ fn resolve_config_file() -> anyhow::Result<()> { | 1 | [project] | ^^^^^^^ - unknown field `project`, expected one of `native-tls`, `offline`, `no-cache`, `cache-dir`, `preview`, `python-preference`, `python-downloads`, `concurrent-downloads`, `concurrent-builds`, `concurrent-installs`, `index`, `index-url`, `extra-index-url`, `no-index`, `find-links`, `index-strategy`, `keyring-provider`, `allow-insecure-host`, `resolution`, `prerelease`, `fork-strategy`, `dependency-metadata`, `config-settings`, `no-build-isolation`, `no-build-isolation-package`, `exclude-newer`, `link-mode`, `compile-bytecode`, `no-sources`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `python-install-mirror`, `pypy-install-mirror`, `publish-url`, `trusted-publishing`, `check-url`, `pip`, `cache-keys`, `override-dependencies`, `constraint-dependencies`, `environments`, `conflicts`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dev-dependencies`, `build-backend` + unknown field `project`, expected one of `required-version`, `native-tls`, `offline`, `no-cache`, `cache-dir`, `preview`, `python-preference`, `python-downloads`, `concurrent-downloads`, `concurrent-builds`, `concurrent-installs`, `index`, `index-url`, `extra-index-url`, `no-index`, `find-links`, `index-strategy`, `keyring-provider`, `allow-insecure-host`, `resolution`, `prerelease`, `fork-strategy`, `dependency-metadata`, `config-settings`, `no-build-isolation`, `no-build-isolation-package`, `exclude-newer`, `link-mode`, `compile-bytecode`, `no-sources`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `python-install-mirror`, `pypy-install-mirror`, `publish-url`, `trusted-publishing`, `check-url`, `pip`, `cache-keys`, `override-dependencies`, `constraint-dependencies`, `environments`, `conflicts`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dev-dependencies`, `build-backend` "### ); @@ -3520,6 +3540,7 @@ fn resolve_skip_empty() -> anyhow::Result<()> { exit_code: 0 ----- stdout ----- GlobalSettings { + required_version: None, quiet: false, verbose: 0, color: Auto, @@ -3652,6 +3673,7 @@ fn resolve_skip_empty() -> anyhow::Result<()> { exit_code: 0 ----- stdout ----- GlobalSettings { + required_version: None, quiet: false, verbose: 0, color: Auto, @@ -3792,6 +3814,7 @@ fn allow_insecure_host() -> anyhow::Result<()> { exit_code: 0 ----- stdout ----- GlobalSettings { + required_version: None, quiet: false, verbose: 0, color: Auto, @@ -3946,6 +3969,7 @@ fn index_priority() -> anyhow::Result<()> { exit_code: 0 ----- stdout ----- GlobalSettings { + required_version: None, quiet: false, verbose: 0, color: Auto, @@ -4129,6 +4153,7 @@ fn index_priority() -> anyhow::Result<()> { exit_code: 0 ----- stdout ----- GlobalSettings { + required_version: None, quiet: false, verbose: 0, color: Auto, @@ -4318,6 +4343,7 @@ fn index_priority() -> anyhow::Result<()> { exit_code: 0 ----- stdout ----- GlobalSettings { + required_version: None, quiet: false, verbose: 0, color: Auto, @@ -4502,6 +4528,7 @@ fn index_priority() -> anyhow::Result<()> { exit_code: 0 ----- stdout ----- GlobalSettings { + required_version: None, quiet: false, verbose: 0, color: Auto, @@ -4693,6 +4720,7 @@ fn index_priority() -> anyhow::Result<()> { exit_code: 0 ----- stdout ----- GlobalSettings { + required_version: None, quiet: false, verbose: 0, color: Auto, @@ -4877,6 +4905,7 @@ fn index_priority() -> anyhow::Result<()> { exit_code: 0 ----- stdout ----- GlobalSettings { + required_version: None, quiet: false, verbose: 0, color: Auto, @@ -5074,6 +5103,7 @@ fn verify_hashes() -> anyhow::Result<()> { exit_code: 0 ----- stdout ----- GlobalSettings { + required_version: None, quiet: false, verbose: 0, color: Auto, @@ -5197,6 +5227,7 @@ fn verify_hashes() -> anyhow::Result<()> { exit_code: 0 ----- stdout ----- GlobalSettings { + required_version: None, quiet: false, verbose: 0, color: Auto, @@ -5318,6 +5349,7 @@ fn verify_hashes() -> anyhow::Result<()> { exit_code: 0 ----- stdout ----- GlobalSettings { + required_version: None, quiet: false, verbose: 0, color: Auto, @@ -5441,6 +5473,7 @@ fn verify_hashes() -> anyhow::Result<()> { exit_code: 0 ----- stdout ----- GlobalSettings { + required_version: None, quiet: false, verbose: 0, color: Auto, @@ -5562,6 +5595,7 @@ fn verify_hashes() -> anyhow::Result<()> { exit_code: 0 ----- stdout ----- GlobalSettings { + required_version: None, quiet: false, verbose: 0, color: Auto, @@ -5684,6 +5718,7 @@ fn verify_hashes() -> anyhow::Result<()> { exit_code: 0 ----- stdout ----- GlobalSettings { + required_version: None, quiet: false, verbose: 0, color: Auto, diff --git a/crates/uv/tests/it/snapshots/it__ecosystem__transformers-lock-file.snap b/crates/uv/tests/it/snapshots/it__ecosystem__transformers-lock-file.snap index 67d4e4fe2bf2..2ed54f7ce7e7 100644 --- a/crates/uv/tests/it/snapshots/it__ecosystem__transformers-lock-file.snap +++ b/crates/uv/tests/it/snapshots/it__ecosystem__transformers-lock-file.snap @@ -770,69 +770,26 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/da/ce/43f77dc8e7bbad02a9f88d07bf794eaf68359df756a28bb9f2f78e255bb1/dash_table-5.0.0-py3-none-any.whl", hash = "sha256:19036fa352bb1c11baf38068ec62d172f0515f73ca3276c79dee49b95ddc16c9", size = 3912 }, ] -[[package]] -name = "datasets" -version = "2.14.4" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.13' and sys_platform == 'darwin'", - "python_full_version == '3.12.*' and sys_platform == 'darwin'", - "python_full_version == '3.12.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "(python_full_version == '3.12.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and sys_platform != 'linux')", - "python_full_version == '3.11.*' and sys_platform == 'darwin'", - "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "(python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux')", - "python_full_version == '3.10.*' and sys_platform == 'darwin'", - "python_full_version == '3.10.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "(python_full_version == '3.10.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.10.*' and sys_platform != 'darwin' and sys_platform != 'linux')", - "python_full_version < '3.10' and platform_machine == 'arm64' and sys_platform == 'darwin'", - "python_full_version < '3.10' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "(python_full_version < '3.10' and platform_machine != 'arm64' and sys_platform == 'darwin') or (python_full_version < '3.10' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.10' and sys_platform != 'darwin' and sys_platform != 'linux')", -] -dependencies = [ - { name = "aiohttp", marker = "python_full_version < '3.13' or sys_platform == 'darwin'" }, - { name = "dill", marker = "python_full_version < '3.13' or sys_platform == 'darwin'" }, - { name = "fsspec", version = "2024.6.1", source = { registry = "https://pypi.org/simple" }, extra = ["http"], marker = "python_full_version < '3.13' or sys_platform == 'darwin'" }, - { name = "huggingface-hub", marker = "python_full_version < '3.13' or sys_platform == 'darwin'" }, - { name = "multiprocess", marker = "python_full_version < '3.13' or sys_platform == 'darwin'" }, - { name = "numpy", marker = "python_full_version < '3.13' or sys_platform == 'darwin'" }, - { name = "packaging", marker = "python_full_version < '3.13' or sys_platform == 'darwin'" }, - { name = "pandas", marker = "python_full_version < '3.13' or sys_platform == 'darwin'" }, - { name = "pyarrow", marker = "python_full_version < '3.13' or sys_platform == 'darwin'" }, - { name = "pyyaml", marker = "python_full_version < '3.13' or sys_platform == 'darwin'" }, - { name = "requests", marker = "python_full_version < '3.13' or sys_platform == 'darwin'" }, - { name = "tqdm", marker = "python_full_version < '3.13' or sys_platform == 'darwin'" }, - { name = "xxhash", marker = "python_full_version < '3.13' or sys_platform == 'darwin'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1d/69/8cc725b5d38968fd118e4ce56a483b16e75b7793854c1a392ec4a34eeb31/datasets-2.14.4.tar.gz", hash = "sha256:ef29c2b5841de488cd343cfc26ab979bff77efa4d2285af51f1ad7db5c46a83b", size = 2178719 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/66/f8/38298237d18d4b6a8ee5dfe390e97bed5adb8e01ec6f9680c0ddf3066728/datasets-2.14.4-py3-none-any.whl", hash = "sha256:29336bd316a7d827ccd4da2236596279b20ca2ac78f64c04c9483da7cbc2459b", size = 519335 }, -] - [[package]] name = "datasets" version = "2.20.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.13' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "(python_full_version >= '3.13' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.13' and sys_platform != 'darwin' and sys_platform != 'linux')", -] dependencies = [ - { name = "aiohttp", marker = "python_full_version >= '3.13' and sys_platform != 'darwin'" }, - { name = "dill", marker = "python_full_version >= '3.13' and sys_platform != 'darwin'" }, - { name = "filelock", marker = "python_full_version >= '3.13' and sys_platform != 'darwin'" }, - { name = "fsspec", version = "2024.5.0", source = { registry = "https://pypi.org/simple" }, extra = ["http"], marker = "python_full_version >= '3.13' and sys_platform != 'darwin'" }, - { name = "huggingface-hub", marker = "python_full_version >= '3.13' and sys_platform != 'darwin'" }, - { name = "multiprocess", marker = "python_full_version >= '3.13' and sys_platform != 'darwin'" }, - { name = "numpy", marker = "python_full_version >= '3.13' and sys_platform != 'darwin'" }, - { name = "packaging", marker = "python_full_version >= '3.13' and sys_platform != 'darwin'" }, - { name = "pandas", marker = "python_full_version >= '3.13' and sys_platform != 'darwin'" }, - { name = "pyarrow", marker = "python_full_version >= '3.13' and sys_platform != 'darwin'" }, - { name = "pyarrow-hotfix", marker = "python_full_version >= '3.13' and sys_platform != 'darwin'" }, - { name = "pyyaml", marker = "python_full_version >= '3.13' and sys_platform != 'darwin'" }, - { name = "requests", marker = "python_full_version >= '3.13' and sys_platform != 'darwin'" }, - { name = "tqdm", marker = "python_full_version >= '3.13' and sys_platform != 'darwin'" }, - { name = "xxhash", marker = "python_full_version >= '3.13' and sys_platform != 'darwin'" }, + { name = "aiohttp" }, + { name = "dill" }, + { name = "filelock" }, + { name = "fsspec", extra = ["http"] }, + { name = "huggingface-hub" }, + { name = "multiprocess" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pyarrow" }, + { name = "pyarrow-hotfix" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "xxhash" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d5/59/b94bfb5f6225c4c931cd516390b3f006e232a036a48337f72889c6c9ab27/datasets-2.20.0.tar.gz", hash = "sha256:3c4dbcd27e0f642b9d41d20ff2efa721a5e04b32b2ca4009e0fc9139e324553f", size = 2225757 } wheels = [ @@ -945,7 +902,7 @@ wheels = [ [package.optional-dependencies] epath = [ - { name = "fsspec", version = "2024.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "fsspec", marker = "python_full_version < '3.10'" }, { name = "importlib-resources", marker = "python_full_version < '3.10'" }, { name = "typing-extensions", marker = "python_full_version < '3.10'" }, { name = "zipp", marker = "python_full_version < '3.10'" }, @@ -970,7 +927,7 @@ wheels = [ [package.optional-dependencies] epath = [ - { name = "fsspec", version = "2024.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "fsspec", marker = "python_full_version == '3.10.*'" }, { name = "importlib-resources", marker = "python_full_version == '3.10.*'" }, { name = "typing-extensions", marker = "python_full_version == '3.10.*'" }, { name = "zipp", marker = "python_full_version == '3.10.*'" }, @@ -1001,8 +958,7 @@ wheels = [ [package.optional-dependencies] epath = [ - { name = "fsspec", version = "2024.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13' and sys_platform != 'darwin'" }, - { name = "fsspec", version = "2024.6.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and python_full_version < '3.13') or (python_full_version >= '3.11' and sys_platform == 'darwin')" }, + { name = "fsspec", marker = "python_full_version >= '3.11'" }, { name = "importlib-resources", marker = "python_full_version >= '3.11'" }, { name = "typing-extensions", marker = "python_full_version >= '3.11'" }, { name = "zipp", marker = "python_full_version >= '3.11'" }, @@ -1016,11 +972,9 @@ name = "evaluate" version = "0.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "datasets", version = "2.14.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' or sys_platform == 'darwin'" }, - { name = "datasets", version = "2.20.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13' and sys_platform != 'darwin'" }, + { name = "datasets" }, { name = "dill" }, - { name = "fsspec", version = "2024.5.0", source = { registry = "https://pypi.org/simple" }, extra = ["http"], marker = "python_full_version >= '3.13' and sys_platform != 'darwin'" }, - { name = "fsspec", version = "2024.6.1", source = { registry = "https://pypi.org/simple" }, extra = ["http"], marker = "python_full_version < '3.13' or sys_platform == 'darwin'" }, + { name = "fsspec", extra = ["http"] }, { name = "huggingface-hub" }, { name = "multiprocess" }, { name = "numpy" }, @@ -1246,10 +1200,6 @@ wheels = [ name = "fsspec" version = "2024.5.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.13' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "(python_full_version >= '3.13' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.13' and sys_platform != 'darwin' and sys_platform != 'linux')", -] sdist = { url = "https://files.pythonhosted.org/packages/71/28/cbf337fddd6f22686b7c2639b80e006accd904db152fe333fd98f4cd8d1e/fsspec-2024.5.0.tar.gz", hash = "sha256:1d021b0b0f933e3b3029ed808eb400c08ba101ca2de4b3483fbc9ca23fcee94a", size = 400066 } wheels = [ { url = "https://files.pythonhosted.org/packages/ba/a3/16e9fe32187e9c8bc7f9b7bcd9728529faa725231a0c96f2f98714ff2fc5/fsspec-2024.5.0-py3-none-any.whl", hash = "sha256:e0fdbc446d67e182f49a70b82cf7889028a63588fde6b222521f10937b2b670c", size = 316106 }, @@ -1257,36 +1207,7 @@ wheels = [ [package.optional-dependencies] http = [ - { name = "aiohttp", marker = "python_full_version >= '3.13' and sys_platform != 'darwin'" }, -] - -[[package]] -name = "fsspec" -version = "2024.6.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.13' and sys_platform == 'darwin'", - "python_full_version == '3.12.*' and sys_platform == 'darwin'", - "python_full_version == '3.12.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "(python_full_version == '3.12.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and sys_platform != 'linux')", - "python_full_version == '3.11.*' and sys_platform == 'darwin'", - "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "(python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux')", - "python_full_version == '3.10.*' and sys_platform == 'darwin'", - "python_full_version == '3.10.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "(python_full_version == '3.10.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.10.*' and sys_platform != 'darwin' and sys_platform != 'linux')", - "python_full_version < '3.10' and platform_machine == 'arm64' and sys_platform == 'darwin'", - "python_full_version < '3.10' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "(python_full_version < '3.10' and platform_machine != 'arm64' and sys_platform == 'darwin') or (python_full_version < '3.10' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.10' and sys_platform != 'darwin' and sys_platform != 'linux')", -] -sdist = { url = "https://files.pythonhosted.org/packages/90/b6/eba5024a9889fcfff396db543a34bef0ab9d002278f163129f9f01005960/fsspec-2024.6.1.tar.gz", hash = "sha256:fad7d7e209dd4c1208e3bbfda706620e0da5142bebbd9c384afb95b07e798e49", size = 284584 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/44/73bea497ac69bafde2ee4269292fa3b41f1198f4bb7bbaaabde30ad29d4a/fsspec-2024.6.1-py3-none-any.whl", hash = "sha256:3cb443f8bcd2efb31295a5b9fdb02aee81d8452c80d28f97a6d0959e6cee101e", size = 177561 }, -] - -[package.optional-dependencies] -http = [ - { name = "aiohttp", marker = "python_full_version < '3.13' or sys_platform == 'darwin'" }, + { name = "aiohttp" }, ] [[package]] @@ -1565,8 +1486,7 @@ version = "0.24.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, - { name = "fsspec", version = "2024.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13' and sys_platform != 'darwin'" }, - { name = "fsspec", version = "2024.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' or sys_platform == 'darwin'" }, + { name = "fsspec" }, { name = "packaging" }, { name = "pyyaml" }, { name = "requests" }, @@ -3081,20 +3001,18 @@ wheels = [ [[package]] name = "protobuf" -version = "3.20.3" +version = "4.25.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/55/5b/e3d951e34f8356e5feecacd12a8e3b258a1da6d9a03ad1770f28925f29bc/protobuf-3.20.3.tar.gz", hash = "sha256:2e3427429c9cffebf259491be0af70189607f365c2f41c7c3764af6f337105f2", size = 216768 } +sdist = { url = "https://files.pythonhosted.org/packages/e8/ab/cb61a4b87b2e7e6c312dce33602bd5884797fd054e0e53205f1c27cf0f66/protobuf-4.25.4.tar.gz", hash = "sha256:0dc4a62cc4052a036ee2204d26fe4d835c62827c855c8a03f29fe6da146b380d", size = 380283 } wheels = [ - { url = "https://files.pythonhosted.org/packages/28/55/b80e8567ec327c060fa39b242392e25690c8899c489ecd7bb65b46b7bb55/protobuf-3.20.3-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:f4bd856d702e5b0d96a00ec6b307b0f51c1982c2bf9c0052cf9019e9a544ba99", size = 918427 }, - { url = "https://files.pythonhosted.org/packages/31/be/80a9c6f16dfa4d41be3edbe655349778ae30882407fa8275eb46b4d34854/protobuf-3.20.3-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9aae4406ea63d825636cc11ffb34ad3379335803216ee3a856787bcf5ccc751e", size = 1051042 }, - { url = "https://files.pythonhosted.org/packages/db/96/948d3fcc1fa816e7ae1d27af59b9d8c5c5e582f3994fd14394f31da95b99/protobuf-3.20.3-cp310-cp310-win32.whl", hash = "sha256:28545383d61f55b57cf4df63eebd9827754fd2dc25f80c5253f9184235db242c", size = 780167 }, - { url = "https://files.pythonhosted.org/packages/6f/5e/fc6feb366b0a9f28e0a2de3b062667c521cd9517d4ff55077b8f351ba2f3/protobuf-3.20.3-cp310-cp310-win_amd64.whl", hash = "sha256:67a3598f0a2dcbc58d02dd1928544e7d88f764b47d4a286202913f0b2801c2e7", size = 904029 }, - { url = "https://files.pythonhosted.org/packages/00/e7/d23c439c55c90ae2e52184363162f7079ca3e7d86205b411d4e9dc266f81/protobuf-3.20.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:398a9e0c3eaceb34ec1aee71894ca3299605fa8e761544934378bbc6c97de23b", size = 982826 }, - { url = "https://files.pythonhosted.org/packages/99/25/5825472ecd911f4ac2ac4e9ab039a48b6d03874e2add92fb633e080bf3eb/protobuf-3.20.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bf01b5720be110540be4286e791db73f84a2b721072a3711efff6c324cdf074b", size = 918423 }, - { url = "https://files.pythonhosted.org/packages/c7/df/ec3ecb8c940b36121c7b77c10acebf3d1c736498aa2f1fe3b6231ee44e76/protobuf-3.20.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:daa564862dd0d39c00f8086f88700fdbe8bc717e993a21e90711acfed02f2402", size = 1019250 }, - { url = "https://files.pythonhosted.org/packages/36/8b/433071fed0058322090a55021bdc8da76d16c7bc9823f5795797803dd6d0/protobuf-3.20.3-cp39-cp39-win32.whl", hash = "sha256:819559cafa1a373b7096a482b504ae8a857c89593cf3a25af743ac9ecbd23480", size = 780270 }, - { url = "https://files.pythonhosted.org/packages/11/a5/e52b731415ad6ef3d841e9e6e337a690249e800cc7c06f0749afab26348c/protobuf-3.20.3-cp39-cp39-win_amd64.whl", hash = "sha256:03038ac1cfbc41aa21f6afcbcd357281d7521b4157926f30ebecc8d4ea59dcb7", size = 904215 }, - { url = "https://files.pythonhosted.org/packages/8d/14/619e24a4c70df2901e1f4dbc50a6291eb63a759172558df326347dce1f0d/protobuf-3.20.3-py2.py3-none-any.whl", hash = "sha256:a7ca6d488aa8ff7f329d4c545b2dbad8ac31464f1d8b1c87ad1346717731e4db", size = 162128 }, + { url = "https://files.pythonhosted.org/packages/c8/43/27b48d9040763b78177d3083e16c70dba6e3c3ee2af64b659f6332c2b06e/protobuf-4.25.4-cp310-abi3-win32.whl", hash = "sha256:db9fd45183e1a67722cafa5c1da3e85c6492a5383f127c86c4c4aa4845867dc4", size = 392409 }, + { url = "https://files.pythonhosted.org/packages/0c/d4/589d673ada9c4c62d5f155218d7ff7ac796efb9c6af95b0bd29d438ae16e/protobuf-4.25.4-cp310-abi3-win_amd64.whl", hash = "sha256:ba3d8504116a921af46499471c63a85260c1a5fc23333154a427a310e015d26d", size = 413398 }, + { url = "https://files.pythonhosted.org/packages/34/ca/bf85ffe3dd16f1f2aaa6c006da8118800209af3da160ae4d4f47500eabd9/protobuf-4.25.4-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:eecd41bfc0e4b1bd3fa7909ed93dd14dd5567b98c941d6c1ad08fdcab3d6884b", size = 394160 }, + { url = "https://files.pythonhosted.org/packages/68/1d/e8961af9a8e534d66672318d6b70ea8e3391a6b13e16a29b039e4a99c214/protobuf-4.25.4-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:4c8a70fdcb995dcf6c8966cfa3a29101916f7225e9afe3ced4395359955d3835", size = 293700 }, + { url = "https://files.pythonhosted.org/packages/ca/6c/cc7ab2fb3a4a7f07f211d8a7bbb76bba633eb09b148296dbd4281e217f95/protobuf-4.25.4-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:3319e073562e2515c6ddc643eb92ce20809f5d8f10fead3332f71c63be6a7040", size = 294612 }, + { url = "https://files.pythonhosted.org/packages/a4/b5/f7e2460dec8347d67e6108bef6ad3291c76e38c898a1087e2c836c02951e/protobuf-4.25.4-cp39-cp39-win32.whl", hash = "sha256:90bf6fd378494eb698805bbbe7afe6c5d12c8e17fca817a646cd6a1818c696ca", size = 392490 }, + { url = "https://files.pythonhosted.org/packages/c7/0b/15bd1a224e5e5744a0dcccf11bcd5dc1405877be38e477b1359d7c2c3737/protobuf-4.25.4-cp39-cp39-win_amd64.whl", hash = "sha256:ac79a48d6b99dfed2729ccccee547b34a1d3d63289c71cef056653a846a2240f", size = 413357 }, + { url = "https://files.pythonhosted.org/packages/b5/95/0ba7f66934a0a798006f06fc3d74816da2b7a2bcfd9b98c53d26f684c89e/protobuf-4.25.4-py3-none-any.whl", hash = "sha256:bfbebc1c8e4793cfd58589acfb8a1026be0003e852b9da7db5a4285bde996978", size = 156464 }, ] [[package]] @@ -3572,8 +3490,7 @@ wheels = [ [package.optional-dependencies] tune = [ - { name = "fsspec", version = "2024.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13' and sys_platform != 'darwin'" }, - { name = "fsspec", version = "2024.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' or sys_platform == 'darwin'" }, + { name = "fsspec" }, { name = "pandas" }, { name = "pyarrow" }, { name = "requests" }, @@ -4781,18 +4698,17 @@ wheels = [ [[package]] name = "tf2onnx" -version = "1.16.1" +version = "1.8.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "flatbuffers" }, { name = "numpy" }, { name = "onnx" }, - { name = "protobuf" }, { name = "requests" }, { name = "six" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/48/826db3d02645d84e7ee5d5ce8407f771057d40fe224d9c3e89536674ccef/tf2onnx-1.16.1-py3-none-any.whl", hash = "sha256:90fb5f62575896d47884d27dc313cfebff36b8783e1094335ad00824ce923a8a", size = 455820 }, + { url = "https://files.pythonhosted.org/packages/db/32/33ce509a79c207a39cf04bfa3ec3353da15d1e6553a6ad912f117cc29130/tf2onnx-1.8.4-py3-none-any.whl", hash = "sha256:1ebabb96c914da76e23222b6107a8b248a024bf259d77f027e6690099512d457", size = 345298 }, ] [[package]] @@ -4933,8 +4849,7 @@ version = "2.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, - { name = "fsspec", version = "2024.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13' and sys_platform != 'darwin'" }, - { name = "fsspec", version = "2024.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' or sys_platform == 'darwin'" }, + { name = "fsspec" }, { name = "jinja2" }, { name = "networkx", version = "3.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "networkx", version = "3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, @@ -5074,8 +4989,7 @@ accelerate = [ ] agents = [ { name = "accelerate" }, - { name = "datasets", version = "2.14.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' or sys_platform == 'darwin'" }, - { name = "datasets", version = "2.20.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13' and sys_platform != 'darwin'" }, + { name = "datasets" }, { name = "diffusers" }, { name = "opencv-python" }, { name = "pillow" }, @@ -5129,8 +5043,7 @@ deepspeed-testing = [ { name = "accelerate" }, { name = "beautifulsoup4" }, { name = "cookiecutter" }, - { name = "datasets", version = "2.14.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' or sys_platform == 'darwin'" }, - { name = "datasets", version = "2.20.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13' and sys_platform != 'darwin'" }, + { name = "datasets" }, { name = "deepspeed" }, { name = "dill" }, { name = "evaluate" }, @@ -5161,8 +5074,7 @@ dev-dependencies = [ { name = "beautifulsoup4" }, { name = "codecarbon" }, { name = "cookiecutter" }, - { name = "datasets", version = "2.14.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' or sys_platform == 'darwin'" }, - { name = "datasets", version = "2.20.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13' and sys_platform != 'darwin'" }, + { name = "datasets" }, { name = "decord" }, { name = "dill" }, { name = "evaluate" }, @@ -5300,8 +5212,7 @@ optuna = [ { name = "optuna" }, ] quality = [ - { name = "datasets", version = "2.14.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' or sys_platform == 'darwin'" }, - { name = "datasets", version = "2.20.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13' and sys_platform != 'darwin'" }, + { name = "datasets" }, { name = "gitpython" }, { name = "hf-doc-builder" }, { name = "isort" }, @@ -5312,8 +5223,7 @@ ray = [ { name = "ray", extra = ["tune"] }, ] retrieval = [ - { name = "datasets", version = "2.14.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' or sys_platform == 'darwin'" }, - { name = "datasets", version = "2.20.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13' and sys_platform != 'darwin'" }, + { name = "datasets" }, { name = "faiss-cpu" }, ] sagemaker = [ diff --git a/crates/uv/tests/it/snapshots/it__ecosystem__transformers-uv-lock-output.snap b/crates/uv/tests/it/snapshots/it__ecosystem__transformers-uv-lock-output.snap index f1a57fbd8613..a3408eda5ba2 100644 --- a/crates/uv/tests/it/snapshots/it__ecosystem__transformers-uv-lock-output.snap +++ b/crates/uv/tests/it/snapshots/it__ecosystem__transformers-uv-lock-output.snap @@ -7,4 +7,4 @@ exit_code: 0 ----- stdout ----- ----- stderr ----- -Resolved 288 packages in [TIME] +Resolved 286 packages in [TIME] diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index 9c362d78b096..e4a663362d9b 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -5870,6 +5870,7 @@ fn sync_build_tag() -> Result<()> { ----- stdout ----- ----- stderr ----- + Prepared 1 package in [TIME] Installed 1 package in [TIME] + build-tag==1.0.0 "###); diff --git a/crates/uv/tests/it/tool_list.rs b/crates/uv/tests/it/tool_list.rs index 71e17f87c78a..d0b0d6b9389f 100644 --- a/crates/uv/tests/it/tool_list.rs +++ b/crates/uv/tests/it/tool_list.rs @@ -156,7 +156,7 @@ fn tool_list_bad_environment() -> Result<()> { - ruff ----- stderr ----- - Invalid environment at `tools/black`: missing Python executable at `tools/black/[BIN]/python` + warning: Invalid environment at `tools/black`: missing Python executable at `tools/black/[BIN]/python` (run `uv tool install black --reinstall` to reinstall) "### ); diff --git a/crates/uv/tests/it/tree.rs b/crates/uv/tests/it/tree.rs index 879fe3315569..7e71fc4e2b1e 100644 --- a/crates/uv/tests/it/tree.rs +++ b/crates/uv/tests/it/tree.rs @@ -1,7 +1,8 @@ use anyhow::Result; use assert_cmd::assert::OutputAssertExt; use assert_fs::prelude::*; -use indoc::formatdoc; +use indoc::{formatdoc, indoc}; +use insta::assert_snapshot; use url::Url; use crate::common::{uv_snapshot, TestContext}; @@ -1172,3 +1173,400 @@ fn non_project_member() -> Result<()> { Ok(()) } + +#[test] +fn script() -> Result<()> { + let context = TestContext::new("3.12"); + + let script = context.temp_dir.child("script.py"); + script.write_str(indoc! {r#" + # /// script + # requires-python = ">=3.11" + # dependencies = [ + # "requests<3", + # "rich", + # ] + # /// + + import requests + from rich.pretty import pprint + + resp = requests.get("https://peps.python.org/api/peps.json") + data = resp.json() + pprint([(k, v["title"]) for k, v in data.items()][:10]) + "#})?; + + uv_snapshot!(context.filters(), context.tree().arg("--script").arg(script.path()), @r###" + success: true + exit_code: 0 + ----- stdout ----- + rich v13.7.1 + ├── markdown-it-py v3.0.0 + │ └── mdurl v0.1.2 + └── pygments v2.17.2 + requests v2.31.0 + ├── certifi v2024.2.2 + ├── charset-normalizer v3.3.2 + ├── idna v3.6 + └── urllib3 v2.2.1 + + ----- stderr ----- + Resolved 9 packages in [TIME] + "###); + + // If the lockfile didn't exist already, it shouldn't be persisted to disk. + assert!(!context.temp_dir.child("uv.lock").exists()); + + // Explicitly lock the script. + uv_snapshot!(context.filters(), context.lock().arg("--script").arg(script.path()), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 9 packages in [TIME] + "###); + + let lock = context.read("script.py.lock"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + lock, @r###" + version = 1 + requires-python = ">=3.11" + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [manifest] + requirements = [ + { name = "requests", specifier = "<3" }, + { name = "rich" }, + ] + + [[package]] + name = "certifi" + version = "2024.2.2" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/71/da/e94e26401b62acd6d91df2b52954aceb7f561743aa5ccc32152886c76c96/certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f", size = 164886 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/06/a07f096c664aeb9f01624f858c3add0a4e913d6c96257acb4fce61e7de14/certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1", size = 163774 }, + ] + + [[package]] + name = "charset-normalizer" + version = "3.3.2" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/63/09/c1bc53dab74b1816a00d8d030de5bf98f724c52c1635e07681d312f20be8/charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", size = 104809 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/68/77/02839016f6fbbf808e8b38601df6e0e66c17bbab76dff4613f7511413597/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", size = 191647 }, + { url = "https://files.pythonhosted.org/packages/3e/33/21a875a61057165e92227466e54ee076b73af1e21fe1b31f1e292251aa1e/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", size = 121434 }, + { url = "https://files.pythonhosted.org/packages/dd/51/68b61b90b24ca35495956b718f35a9756ef7d3dd4b3c1508056fa98d1a1b/charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", size = 118979 }, + { url = "https://files.pythonhosted.org/packages/e4/a6/7ee57823d46331ddc37dd00749c95b0edec2c79b15fc0d6e6efb532e89ac/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", size = 136582 }, + { url = "https://files.pythonhosted.org/packages/74/f1/0d9fe69ac441467b737ba7f48c68241487df2f4522dd7246d9426e7c690e/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", size = 146645 }, + { url = "https://files.pythonhosted.org/packages/05/31/e1f51c76db7be1d4aef220d29fbfa5dbb4a99165d9833dcbf166753b6dc0/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", size = 139398 }, + { url = "https://files.pythonhosted.org/packages/40/26/f35951c45070edc957ba40a5b1db3cf60a9dbb1b350c2d5bef03e01e61de/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", size = 140273 }, + { url = "https://files.pythonhosted.org/packages/07/07/7e554f2bbce3295e191f7e653ff15d55309a9ca40d0362fcdab36f01063c/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", size = 142577 }, + { url = "https://files.pythonhosted.org/packages/d8/b5/eb705c313100defa57da79277d9207dc8d8e45931035862fa64b625bfead/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", size = 137747 }, + { url = "https://files.pythonhosted.org/packages/19/28/573147271fd041d351b438a5665be8223f1dd92f273713cb882ddafe214c/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", size = 143375 }, + { url = "https://files.pythonhosted.org/packages/cf/7c/f3b682fa053cc21373c9a839e6beba7705857075686a05c72e0f8c4980ca/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", size = 148474 }, + { url = "https://files.pythonhosted.org/packages/1e/49/7ab74d4ac537ece3bc3334ee08645e231f39f7d6df6347b29a74b0537103/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", size = 140232 }, + { url = "https://files.pythonhosted.org/packages/2d/dc/9dacba68c9ac0ae781d40e1a0c0058e26302ea0660e574ddf6797a0347f7/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", size = 140859 }, + { url = "https://files.pythonhosted.org/packages/6c/c2/4a583f800c0708dd22096298e49f887b49d9746d0e78bfc1d7e29816614c/charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", size = 92509 }, + { url = "https://files.pythonhosted.org/packages/57/ec/80c8d48ac8b1741d5b963797b7c0c869335619e13d4744ca2f67fc11c6fc/charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", size = 99870 }, + { url = "https://files.pythonhosted.org/packages/d1/b2/fcedc8255ec42afee97f9e6f0145c734bbe104aac28300214593eb326f1d/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", size = 192892 }, + { url = "https://files.pythonhosted.org/packages/2e/7d/2259318c202f3d17f3fe6438149b3b9e706d1070fe3fcbb28049730bb25c/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", size = 122213 }, + { url = "https://files.pythonhosted.org/packages/3a/52/9f9d17c3b54dc238de384c4cb5a2ef0e27985b42a0e5cc8e8a31d918d48d/charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", size = 119404 }, + { url = "https://files.pythonhosted.org/packages/99/b0/9c365f6d79a9f0f3c379ddb40a256a67aa69c59609608fe7feb6235896e1/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", size = 137275 }, + { url = "https://files.pythonhosted.org/packages/91/33/749df346e93d7a30cdcb90cbfdd41a06026317bfbfb62cd68307c1a3c543/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", size = 147518 }, + { url = "https://files.pythonhosted.org/packages/72/1a/641d5c9f59e6af4c7b53da463d07600a695b9824e20849cb6eea8a627761/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", size = 140182 }, + { url = "https://files.pythonhosted.org/packages/ee/fb/14d30eb4956408ee3ae09ad34299131fb383c47df355ddb428a7331cfa1e/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", size = 141869 }, + { url = "https://files.pythonhosted.org/packages/df/3e/a06b18788ca2eb6695c9b22325b6fde7dde0f1d1838b1792a0076f58fe9d/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", size = 144042 }, + { url = "https://files.pythonhosted.org/packages/45/59/3d27019d3b447a88fe7e7d004a1e04be220227760264cc41b405e863891b/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", size = 138275 }, + { url = "https://files.pythonhosted.org/packages/7b/ef/5eb105530b4da8ae37d506ccfa25057961b7b63d581def6f99165ea89c7e/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", size = 144819 }, + { url = "https://files.pythonhosted.org/packages/a2/51/e5023f937d7f307c948ed3e5c29c4b7a3e42ed2ee0b8cdf8f3a706089bf0/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", size = 149415 }, + { url = "https://files.pythonhosted.org/packages/24/9d/2e3ef673dfd5be0154b20363c5cdcc5606f35666544381bee15af3778239/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", size = 141212 }, + { url = "https://files.pythonhosted.org/packages/5b/ae/ce2c12fcac59cb3860b2e2d76dc405253a4475436b1861d95fe75bdea520/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", size = 142167 }, + { url = "https://files.pythonhosted.org/packages/ed/3a/a448bf035dce5da359daf9ae8a16b8a39623cc395a2ffb1620aa1bce62b0/charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", size = 93041 }, + { url = "https://files.pythonhosted.org/packages/b6/7c/8debebb4f90174074b827c63242c23851bdf00a532489fba57fef3416e40/charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", size = 100397 }, + { url = "https://files.pythonhosted.org/packages/28/76/e6222113b83e3622caa4bb41032d0b1bf785250607392e1b778aca0b8a7d/charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", size = 48543 }, + ] + + [[package]] + name = "idna" + version = "3.6" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", size = 175426 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f", size = 61567 }, + ] + + [[package]] + name = "markdown-it-py" + version = "3.0.0" + source = { registry = "https://pypi.org/simple" } + dependencies = [ + { name = "mdurl" }, + ] + sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, + ] + + [[package]] + name = "mdurl" + version = "0.1.2" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, + ] + + [[package]] + name = "pygments" + version = "2.17.2" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/55/59/8bccf4157baf25e4aa5a0bb7fa3ba8600907de105ebc22b0c78cfbf6f565/pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367", size = 4827772 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/97/9c/372fef8377a6e340b1704768d20daaded98bf13282b5327beb2e2fe2c7ef/pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c", size = 1179756 }, + ] + + [[package]] + name = "requests" + version = "2.31.0" + source = { registry = "https://pypi.org/simple" } + dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, + ] + sdist = { url = "https://files.pythonhosted.org/packages/9d/be/10918a2eac4ae9f02f6cfe6414b7a155ccd8f7f9d4380d62fd5b955065c3/requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1", size = 110794 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/70/8e/0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44/requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", size = 62574 }, + ] + + [[package]] + name = "rich" + version = "13.7.1" + source = { registry = "https://pypi.org/simple" } + dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, + ] + sdist = { url = "https://files.pythonhosted.org/packages/b3/01/c954e134dc440ab5f96952fe52b4fdc64225530320a910473c1fe270d9aa/rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432", size = 221248 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/87/67/a37f6214d0e9fe57f6ae54b2956d550ca8365857f42a1ce0392bb21d9410/rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222", size = 240681 }, + ] + + [[package]] + name = "urllib3" + version = "2.2.1" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/7a/50/7fd50a27caa0652cd4caf224aa87741ea41d3265ad13f010886167cfcc79/urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19", size = 291020 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/73/a68704750a7679d0b6d3ad7aa8d4da8e14e151ae82e6fee774e6e0d05ec8/urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d", size = 121067 }, + ] + "### + ); + }); + + // Update the dependencies. + script.write_str(indoc! {r#" + # /// script + # requires-python = ">=3.11" + # dependencies = [ + # "iniconfig", + # "requests<3", + # "rich", + # ] + # /// + + import requests + from rich.pretty import pprint + + resp = requests.get("https://peps.python.org/api/peps.json") + data = resp.json() + pprint([(k, v["title"]) for k, v in data.items()][:10]) + "#})?; + + // `uv tree` should update the lockfile. + uv_snapshot!(context.filters(), context.tree().arg("--script").arg(script.path()), @r###" + success: true + exit_code: 0 + ----- stdout ----- + rich v13.7.1 + ├── markdown-it-py v3.0.0 + │ └── mdurl v0.1.2 + └── pygments v2.17.2 + requests v2.31.0 + ├── certifi v2024.2.2 + ├── charset-normalizer v3.3.2 + ├── idna v3.6 + └── urllib3 v2.2.1 + iniconfig v2.0.0 + + ----- stderr ----- + Resolved 10 packages in [TIME] + "###); + + let lock = context.read("script.py.lock"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + lock, @r###" + version = 1 + requires-python = ">=3.11" + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [manifest] + requirements = [ + { name = "iniconfig" }, + { name = "requests", specifier = "<3" }, + { name = "rich" }, + ] + + [[package]] + name = "certifi" + version = "2024.2.2" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/71/da/e94e26401b62acd6d91df2b52954aceb7f561743aa5ccc32152886c76c96/certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f", size = 164886 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/06/a07f096c664aeb9f01624f858c3add0a4e913d6c96257acb4fce61e7de14/certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1", size = 163774 }, + ] + + [[package]] + name = "charset-normalizer" + version = "3.3.2" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/63/09/c1bc53dab74b1816a00d8d030de5bf98f724c52c1635e07681d312f20be8/charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", size = 104809 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/68/77/02839016f6fbbf808e8b38601df6e0e66c17bbab76dff4613f7511413597/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", size = 191647 }, + { url = "https://files.pythonhosted.org/packages/3e/33/21a875a61057165e92227466e54ee076b73af1e21fe1b31f1e292251aa1e/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", size = 121434 }, + { url = "https://files.pythonhosted.org/packages/dd/51/68b61b90b24ca35495956b718f35a9756ef7d3dd4b3c1508056fa98d1a1b/charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", size = 118979 }, + { url = "https://files.pythonhosted.org/packages/e4/a6/7ee57823d46331ddc37dd00749c95b0edec2c79b15fc0d6e6efb532e89ac/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", size = 136582 }, + { url = "https://files.pythonhosted.org/packages/74/f1/0d9fe69ac441467b737ba7f48c68241487df2f4522dd7246d9426e7c690e/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", size = 146645 }, + { url = "https://files.pythonhosted.org/packages/05/31/e1f51c76db7be1d4aef220d29fbfa5dbb4a99165d9833dcbf166753b6dc0/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", size = 139398 }, + { url = "https://files.pythonhosted.org/packages/40/26/f35951c45070edc957ba40a5b1db3cf60a9dbb1b350c2d5bef03e01e61de/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", size = 140273 }, + { url = "https://files.pythonhosted.org/packages/07/07/7e554f2bbce3295e191f7e653ff15d55309a9ca40d0362fcdab36f01063c/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", size = 142577 }, + { url = "https://files.pythonhosted.org/packages/d8/b5/eb705c313100defa57da79277d9207dc8d8e45931035862fa64b625bfead/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", size = 137747 }, + { url = "https://files.pythonhosted.org/packages/19/28/573147271fd041d351b438a5665be8223f1dd92f273713cb882ddafe214c/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", size = 143375 }, + { url = "https://files.pythonhosted.org/packages/cf/7c/f3b682fa053cc21373c9a839e6beba7705857075686a05c72e0f8c4980ca/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", size = 148474 }, + { url = "https://files.pythonhosted.org/packages/1e/49/7ab74d4ac537ece3bc3334ee08645e231f39f7d6df6347b29a74b0537103/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", size = 140232 }, + { url = "https://files.pythonhosted.org/packages/2d/dc/9dacba68c9ac0ae781d40e1a0c0058e26302ea0660e574ddf6797a0347f7/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", size = 140859 }, + { url = "https://files.pythonhosted.org/packages/6c/c2/4a583f800c0708dd22096298e49f887b49d9746d0e78bfc1d7e29816614c/charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", size = 92509 }, + { url = "https://files.pythonhosted.org/packages/57/ec/80c8d48ac8b1741d5b963797b7c0c869335619e13d4744ca2f67fc11c6fc/charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", size = 99870 }, + { url = "https://files.pythonhosted.org/packages/d1/b2/fcedc8255ec42afee97f9e6f0145c734bbe104aac28300214593eb326f1d/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", size = 192892 }, + { url = "https://files.pythonhosted.org/packages/2e/7d/2259318c202f3d17f3fe6438149b3b9e706d1070fe3fcbb28049730bb25c/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", size = 122213 }, + { url = "https://files.pythonhosted.org/packages/3a/52/9f9d17c3b54dc238de384c4cb5a2ef0e27985b42a0e5cc8e8a31d918d48d/charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", size = 119404 }, + { url = "https://files.pythonhosted.org/packages/99/b0/9c365f6d79a9f0f3c379ddb40a256a67aa69c59609608fe7feb6235896e1/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", size = 137275 }, + { url = "https://files.pythonhosted.org/packages/91/33/749df346e93d7a30cdcb90cbfdd41a06026317bfbfb62cd68307c1a3c543/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", size = 147518 }, + { url = "https://files.pythonhosted.org/packages/72/1a/641d5c9f59e6af4c7b53da463d07600a695b9824e20849cb6eea8a627761/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", size = 140182 }, + { url = "https://files.pythonhosted.org/packages/ee/fb/14d30eb4956408ee3ae09ad34299131fb383c47df355ddb428a7331cfa1e/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", size = 141869 }, + { url = "https://files.pythonhosted.org/packages/df/3e/a06b18788ca2eb6695c9b22325b6fde7dde0f1d1838b1792a0076f58fe9d/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", size = 144042 }, + { url = "https://files.pythonhosted.org/packages/45/59/3d27019d3b447a88fe7e7d004a1e04be220227760264cc41b405e863891b/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", size = 138275 }, + { url = "https://files.pythonhosted.org/packages/7b/ef/5eb105530b4da8ae37d506ccfa25057961b7b63d581def6f99165ea89c7e/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", size = 144819 }, + { url = "https://files.pythonhosted.org/packages/a2/51/e5023f937d7f307c948ed3e5c29c4b7a3e42ed2ee0b8cdf8f3a706089bf0/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", size = 149415 }, + { url = "https://files.pythonhosted.org/packages/24/9d/2e3ef673dfd5be0154b20363c5cdcc5606f35666544381bee15af3778239/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", size = 141212 }, + { url = "https://files.pythonhosted.org/packages/5b/ae/ce2c12fcac59cb3860b2e2d76dc405253a4475436b1861d95fe75bdea520/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", size = 142167 }, + { url = "https://files.pythonhosted.org/packages/ed/3a/a448bf035dce5da359daf9ae8a16b8a39623cc395a2ffb1620aa1bce62b0/charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", size = 93041 }, + { url = "https://files.pythonhosted.org/packages/b6/7c/8debebb4f90174074b827c63242c23851bdf00a532489fba57fef3416e40/charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", size = 100397 }, + { url = "https://files.pythonhosted.org/packages/28/76/e6222113b83e3622caa4bb41032d0b1bf785250607392e1b778aca0b8a7d/charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", size = 48543 }, + ] + + [[package]] + name = "idna" + version = "3.6" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", size = 175426 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f", size = 61567 }, + ] + + [[package]] + name = "iniconfig" + version = "2.0.0" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, + ] + + [[package]] + name = "markdown-it-py" + version = "3.0.0" + source = { registry = "https://pypi.org/simple" } + dependencies = [ + { name = "mdurl" }, + ] + sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, + ] + + [[package]] + name = "mdurl" + version = "0.1.2" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, + ] + + [[package]] + name = "pygments" + version = "2.17.2" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/55/59/8bccf4157baf25e4aa5a0bb7fa3ba8600907de105ebc22b0c78cfbf6f565/pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367", size = 4827772 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/97/9c/372fef8377a6e340b1704768d20daaded98bf13282b5327beb2e2fe2c7ef/pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c", size = 1179756 }, + ] + + [[package]] + name = "requests" + version = "2.31.0" + source = { registry = "https://pypi.org/simple" } + dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, + ] + sdist = { url = "https://files.pythonhosted.org/packages/9d/be/10918a2eac4ae9f02f6cfe6414b7a155ccd8f7f9d4380d62fd5b955065c3/requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1", size = 110794 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/70/8e/0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44/requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", size = 62574 }, + ] + + [[package]] + name = "rich" + version = "13.7.1" + source = { registry = "https://pypi.org/simple" } + dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, + ] + sdist = { url = "https://files.pythonhosted.org/packages/b3/01/c954e134dc440ab5f96952fe52b4fdc64225530320a910473c1fe270d9aa/rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432", size = 221248 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/87/67/a37f6214d0e9fe57f6ae54b2956d550ca8365857f42a1ce0392bb21d9410/rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222", size = 240681 }, + ] + + [[package]] + name = "urllib3" + version = "2.2.1" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/7a/50/7fd50a27caa0652cd4caf224aa87741ea41d3265ad13f010886167cfcc79/urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19", size = 291020 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/73/a68704750a7679d0b6d3ad7aa8d4da8e14e151ae82e6fee774e6e0d05ec8/urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d", size = 121067 }, + ] + "### + ); + }); + + Ok(()) +} diff --git a/docs/concepts/projects/config.md b/docs/concepts/projects/config.md index 3fa7c088890c..6e1f84632a3e 100644 --- a/docs/concepts/projects/config.md +++ b/docs/concepts/projects/config.md @@ -119,6 +119,18 @@ with the default build system. installable. Similarly, if you add a dependency on a local package or install it with `uv pip`, uv will always attempt to build and install it. +### Build system options + +Build systems are used to power the following features: + +- Including or excluding files from distributions +- Editable install behavior +- Dynamic project metadata +- Compilation of native code +- Vendoring shared libraries + +To configure these features, refer to the documentation of your chosen build system. + ## Project packaging As discussed in [build systems](#build-systems), a Python project must be built to be installed. diff --git a/docs/concepts/projects/init.md b/docs/concepts/projects/init.md index 30c4ee4bad2a..a237af7f57e1 100644 --- a/docs/concepts/projects/init.md +++ b/docs/concepts/projects/init.md @@ -279,6 +279,7 @@ And the Python module imports it: ```python title="src/example_ext/__init__.py" from example_ext._core import hello_from_bin + def main() -> None: print(hello_from_bin()) ``` diff --git a/docs/concepts/python-versions.md b/docs/concepts/python-versions.md index 85dbb25c6b43..70da18fa89b5 100644 --- a/docs/concepts/python-versions.md +++ b/docs/concepts/python-versions.md @@ -53,7 +53,7 @@ This behavior can be The `.python-version` file can be used to create a default Python version request. uv searches for a `.python-version` file in the working directory and each of its parents. Any of the request formats -described above can be used, though use of a version number is recommended for interopability with +described above can be used, though use of a version number is recommended for interoperability with other tools. A `.python-version` file can be created in the current directory with the diff --git a/docs/concepts/tools.md b/docs/concepts/tools.md index c80f8d1589b2..4d3603bc1711 100644 --- a/docs/concepts/tools.md +++ b/docs/concepts/tools.md @@ -160,7 +160,7 @@ Tool upgrades will respect the version constraints provided when installing the `uv tool install black >=23,<24` followed by `uv tool upgrade black` will upgrade Black to the latest version in the range `>=23,<24`. -To instead replace the version constraints, re-install the tool with `uv tool install`: +To instead replace the version constraints, reinstall the tool with `uv tool install`: ```console $ uv tool install black>=24 diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 3e44291555a6..1eec38086336 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.5.13/install.sh | sh + $ curl -LsSf https://astral.sh/uv/0.5.18/install.sh | sh ``` === "Windows" @@ -41,7 +41,7 @@ uv provides a standalone installer to download and install uv: Request a specific version by including it in the URL: ```console - $ powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/0.5.13/install.ps1 | iex" + $ powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/0.5.18/install.ps1 | iex" ``` !!! tip @@ -105,14 +105,22 @@ uv is available in the core Homebrew packages. $ brew install uv ``` -### Winget +### WinGet -uv is available via [winget](https://winstall.app/apps/astral-sh.uv). +uv is available via [WinGet](https://winstall.app/apps/astral-sh.uv). ```console $ winget install --id=astral-sh.uv -e ``` +### Scoop + +uv is available via [Scoop](https://scoop.sh/#/apps?q=uv). + +```console +$ scoop install main/uv +``` + ### Docker uv provides a Docker image at diff --git a/docs/guides/integration/alternative-indexes.md b/docs/guides/integration/alternative-indexes.md index 9e8142891056..b686ed5a9b67 100644 --- a/docs/guides/integration/alternative-indexes.md +++ b/docs/guides/integration/alternative-indexes.md @@ -1,3 +1,10 @@ +--- +title: Using alternative package indexes +description: + A guide to using alternative package indexes with uv, including Azure Artifacts, Google Artifact + Registry, AWS CodeArtifact, and more. +--- + # Using alternative package indexes While uv uses the official Python Package Index (PyPI) by default, it also supports alternative diff --git a/docs/guides/integration/aws-lambda.md b/docs/guides/integration/aws-lambda.md new file mode 100644 index 000000000000..16cdab8e9691 --- /dev/null +++ b/docs/guides/integration/aws-lambda.md @@ -0,0 +1,520 @@ +--- +title: Using uv with AWS Lambda +description: + A complete guide to using uv with AWS Lambda to manage Python dependencies and deploy serverless + functions via Docker containers or zip archives. +--- + +# Using uv with AWS Lambda + +[AWS Lambda](https://aws.amazon.com/lambda/) is a serverless computing service that lets you run +code without provisioning or managing servers. + +You can use uv with AWS Lambda to manage your Python dependencies, build your deployment package, +and deploy your Lambda functions. + +!!! tip + + Check out the [`uv-aws-lambda-example`](https://github.com/astral-sh/uv-aws-lambda-example) project for + an example of best practices when using uv to deploy an application to AWS Lambda. + +## Getting started + +To start, assume we have a minimal FastAPI application with the following structure: + +```plaintext +project +├── pyproject.toml +└── app + ├── __init__.py + └── main.py +``` + +Where the `pyproject.toml` contains: + +```toml title="pyproject.toml" +[project] +name = "uv-aws-lambda-example" +version = "0.1.0" +requires-python = ">=3.13" +dependencies = [ + # FastAPI is a modern web framework for building APIs with Python. + "fastapi", + # Mangum is a library that adapts ASGI applications to AWS Lambda and API Gateway. + "mangum", +] + +[dependency-groups] +dev = [ + # In development mode, include the FastAPI development server. + "fastapi[standard]>=0.115", +] +``` + +And the `main.py` file contains: + +```python title="app/main.py" +import logging + +from fastapi import FastAPI +from mangum import Mangum + +logger = logging.getLogger() +logger.setLevel(logging.INFO) + +app = FastAPI() +handler = Mangum(app) + + +@app.get("/") +async def root() -> str: + return "Hello, world!" +``` + +We can run this application locally with: + +```console +$ uv run fastapi dev +``` + +From there, opening http://127.0.0.1:8000/ in a web browser will display "Hello, world!" + +## Deploying a Docker image + +To deploy to AWS Lambda, we need to build a container image that includes the application code and +dependencies in a single output directory. + +We'll follow the principles outlined in the [Docker guide](./docker.md) (in particular, a +multi-stage build) to ensure that the final image is as small and cache-friendly as possible. + +In the first stage, we'll populate a single directory with all application code and dependencies. In +the second stage, we'll copy this directory over to the final image, omitting the build tools and +other unnecessary files. + +```dockerfile title="Dockerfile" +FROM ghcr.io/astral-sh/uv:0.5.18 AS uv + +# First, bundle the dependencies into the task root. +FROM public.ecr.aws/lambda/python:3.13 AS builder + +# Enable bytecode compilation, to improve cold-start performance. +ENV UV_COMPILE_BYTECODE=1 + +# Disable installer metadata, to create a deterministic layer. +ENV UV_NO_INSTALLER_METADATA=1 + +# Enable copy mode to support bind mount caching. +ENV UV_LINK_MODE=copy + +# Bundle the dependencies into the Lambda task root via `uv pip install --target`. +# +# Omit any local packages (`--no-emit-workspace`) and development dependencies (`--no-dev`). +# This ensures that the Docker layer cache is only invalidated when the `pyproject.toml` or `uv.lock` +# files change, but remains robust to changes in the application code. +RUN --mount=from=uv,source=/uv,target=/bin/uv \ + --mount=type=cache,target=/root/.cache/uv \ + --mount=type=bind,source=uv.lock,target=uv.lock \ + --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ + uv export --frozen --no-emit-workspace --no-dev --no-editable -o requirements.txt && \ + uv pip install -r requirements.txt --target "${LAMBDA_TASK_ROOT}" + +FROM public.ecr.aws/lambda/python:3.13 + +# Copy the runtime dependencies from the builder stage. +COPY --from=builder ${LAMBDA_TASK_ROOT} ${LAMBDA_TASK_ROOT} + +# Copy the application code. +COPY ./app ${LAMBDA_TASK_ROOT}/app + +# Set the AWS Lambda handler. +CMD ["app.main.handler"] +``` + +!!! tip + + To deploy to ARM-based AWS Lambda runtimes, replace `public.ecr.aws/lambda/python:3.13` with `public.ecr.aws/lambda/python:3.13-arm64`. + +We can build the image with, e.g.: + +```console +$ uv lock +$ docker build -t fastapi-app . +``` + +The core benefits of this Dockerfile structure are as follows: + +1. **Minimal image size.** By using a multi-stage build, we can ensure that the final image only + includes the application code and dependencies. For example, the uv binary itself is not included + in the final image. +2. **Maximal cache reuse.** By installing application dependencies separately from the application + code, we can ensure that the Docker layer cache is only invalidated when the dependencies change. + +Concretely, rebuilding the image after modifying the application source code can reuse the cached +layers, resulting in millisecond builds: + +```console + => [internal] load build definition from Dockerfile 0.0s + => => transferring dockerfile: 1.31kB 0.0s + => [internal] load metadata for public.ecr.aws/lambda/python:3.13 0.3s + => [internal] load metadata for ghcr.io/astral-sh/uv:latest 0.3s + => [internal] load .dockerignore 0.0s + => => transferring context: 106B 0.0s + => [uv 1/1] FROM ghcr.io/astral-sh/uv:latest@sha256:ea61e006cfec0e8d81fae901ad703e09d2c6cf1aa58abcb6507d124b50286f 0.0s + => [builder 1/2] FROM public.ecr.aws/lambda/python:3.13@sha256:f5b51b377b80bd303fe8055084e2763336ea8920d12955b23ef 0.0s + => [internal] load build context 0.0s + => => transferring context: 185B 0.0s + => CACHED [builder 2/2] RUN --mount=from=uv,source=/uv,target=/bin/uv --mount=type=cache,target=/root/.cache/u 0.0s + => CACHED [stage-2 2/3] COPY --from=builder /var/task /var/task 0.0s + => CACHED [stage-2 3/3] COPY ./app /var/task 0.0s + => exporting to image 0.0s + => => exporting layers 0.0s + => => writing image sha256:6f8f9ef715a7cda466b677a9df4046ebbb90c8e88595242ade3b4771f547652d 0.0 +``` + +After building, we can push the image to +[Elastic Container Registry (ECR)](https://aws.amazon.com/ecr/) with, e.g.: + +```console +$ aws ecr get-login-password --region region | docker login --username AWS --password-stdin aws_account_id.dkr.ecr.region.amazonaws.com +$ docker tag fastapi-app:latest aws_account_id.dkr.ecr.region.amazonaws.com/fastapi-app:latest +$ docker push aws_account_id.dkr.ecr.region.amazonaws.com/fastapi-app:latest +``` + +Finally, we can deploy the image to AWS Lambda using the AWS Management Console or the AWS CLI, +e.g.: + +```console +$ aws lambda create-function \ + --function-name myFunction \ + --package-type Image \ + --code ImageUri=aws_account_id.dkr.ecr.region.amazonaws.com/fastapi-app:latest \ + --role arn:aws:iam::111122223333:role/my-lambda-role +``` + +Where the +[execution role](https://docs.aws.amazon.com/lambda/latest/dg/lambda-intro-execution-role.html#permissions-executionrole-api) +is created via: + +```console +$ aws iam create-role \ + --role-name my-lambda-role \ + --assume-role-policy-document '{"Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Principal": {"Service": "lambda.amazonaws.com"}, "Action": "sts:AssumeRole"}]}' +``` + +Or, update an existing function with: + +```console +$ aws lambda update-function-code \ + --function-name myFunction \ + --image-uri aws_account_id.dkr.ecr.region.amazonaws.com/fastapi-app:latest \ + --publish +``` + +For details, see the +[AWS Lambda documentation](https://docs.aws.amazon.com/lambda/latest/dg/python-image.html). + +### Workspace support + +If a project includes local dependencies (e.g., via +[Workspaces](../../concepts/projects/workspaces.md), those too must be included in the deployment +package. + +We'll start by extending the above example to include a dependency on a locally-developed library +named `library`. + +First, we'll create the library itself: + +```console +$ uv init --lib library +$ uv add ./library +``` + +Running `uv init` within the `project` directory will automatically convert `project` to a workspace +and add `library` as a workspace member: + +```toml title="pyproject.toml" +[project] +name = "uv-aws-lambda-example" +version = "0.1.0" +requires-python = ">=3.13" +dependencies = [ + # FastAPI is a modern web framework for building APIs with Python. + "fastapi", + # A local library. + "library", + # Mangum is a library that adapts ASGI applications to AWS Lambda and API Gateway. + "mangum", +] + +[dependency-groups] +dev = [ + # In development mode, include the FastAPI development server. + "fastapi[standard]", +] + +[tool.uv.workspace] +members = ["library"] + +[tool.uv.sources] +lib = { workspace = true } +``` + +By default, `uv init --lib` will create a package that exports a `hello` function. We'll modify the +application source code to call that function: + +```python title="app/main.py" +import logging + +from fastapi import FastAPI +from mangum import Mangum + +from library import hello + +logger = logging.getLogger() +logger.setLevel(logging.INFO) + +app = FastAPI() +handler = Mangum(app) + + +@app.get("/") +async def root() -> str: + return hello() +``` + +We can run the modified application locally with: + +```console +$ uv run fastapi dev +``` + +And confirm that opening http://127.0.0.1:8000/ in a web browser displays, "Hello from library!" +(instead of "Hello, World!") + +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.5.18 AS uv + +# First, bundle the dependencies into the task root. +FROM public.ecr.aws/lambda/python:3.13 AS builder + +# Enable bytecode compilation, to improve cold-start performance. +ENV UV_COMPILE_BYTECODE=1 + +# Disable installer metadata, to create a deterministic layer. +ENV UV_NO_INSTALLER_METADATA=1 + +# Enable copy mode to support bind mount caching. +ENV UV_LINK_MODE=copy + +# Bundle the dependencies into the Lambda task root via `uv pip install --target`. +# +# Omit any local packages (`--no-emit-workspace`) and development dependencies (`--no-dev`). +# This ensures that the Docker layer cache is only invalidated when the `pyproject.toml` or `uv.lock` +# files change, but remains robust to changes in the application code. +RUN --mount=from=uv,source=/uv,target=/bin/uv \ + --mount=type=cache,target=/root/.cache/uv \ + --mount=type=bind,source=uv.lock,target=uv.lock \ + --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ + uv export --frozen --no-emit-workspace --no-dev --no-editable -o requirements.txt && \ + uv pip install -r requirements.txt --target "${LAMBDA_TASK_ROOT}" + +# If you have a workspace, copy it over and install it too. +# +# By omitting `--no-emit-workspace`, `library` will be copied into the task root. Using a separate +# `RUN` command ensures that all third-party dependencies are cached separately and remain +# robust to changes in the workspace. +RUN --mount=from=uv,source=/uv,target=/bin/uv \ + --mount=type=cache,target=/root/.cache/uv \ + --mount=type=bind,source=uv.lock,target=uv.lock \ + --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ + --mount=type=bind,source=library,target=library \ + uv export --frozen --no-dev --no-editable -o requirements.txt && \ + uv pip install -r requirements.txt --target "${LAMBDA_TASK_ROOT}" + +FROM public.ecr.aws/lambda/python:3.13 + +# Copy the runtime dependencies from the builder stage. +COPY --from=builder ${LAMBDA_TASK_ROOT} ${LAMBDA_TASK_ROOT} + +# Copy the application code. +COPY ./app ${LAMBDA_TASK_ROOT}/app + +# Set the AWS Lambda handler. +CMD ["app.main.handler"] +``` + +!!! tip + + To deploy to ARM-based AWS Lambda runtimes, replace `public.ecr.aws/lambda/python:3.13` with `public.ecr.aws/lambda/python:3.13-arm64`. + +From there, we can build and deploy the updated image as before. + +## Deploying a zip archive + +AWS Lambda also supports deployment via zip archives. For simple applications, zip archives can be a +more straightforward and efficient deployment method than Docker images; however, zip archives are +limited to +[250 MB](https://docs.aws.amazon.com/lambda/latest/dg/python-package.html#python-package-create-update) +in size. + +Returning to the FastAPI example, we can bundle the application dependencies into a local directory +for AWS Lambda via: + +```console +$ uv export --frozen --no-dev --no-editable -o requirements.txt +$ uv pip install \ + --no-installer-metadata \ + --no-compile-bytecode \ + --python-platform x86_64-manylinux2014 \ + --python 3.13 \ + --target packages \ + -r requirements.txt +``` + +!!! tip + + To deploy to ARM-based AWS Lambda runtimes, replace `x86_64-manylinux2014` with `aarch64-manylinux2014`. + +Following the +[AWS Lambda documentation](https://docs.aws.amazon.com/lambda/latest/dg/python-package.html), we can +then bundle these dependencies into a zip as follows: + +```console +$ cd packages +$ zip -r ../package.zip . +$ cd .. +``` + +Finally, we can add the application code to the zip archive: + +```console +$ zip -r package.zip app +``` + +We can then deploy the zip archive to AWS Lambda via the AWS Management Console or the AWS CLI, +e.g.: + +```console +$ aws lambda create-function \ + --function-name myFunction \ + --runtime python3.13 \ + --zip-file fileb://package.zip \ + --handler app.main.handler \ + --role arn:aws:iam::111122223333:role/service-role/my-lambda-role +``` + +Where the +[execution role](https://docs.aws.amazon.com/lambda/latest/dg/lambda-intro-execution-role.html#permissions-executionrole-api) +is created via: + +```console +$ aws iam create-role \ + --role-name my-lambda-role \ + --assume-role-policy-document '{"Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Principal": {"Service": "lambda.amazonaws.com"}, "Action": "sts:AssumeRole"}]}' +``` + +Or, update an existing function with: + +```console +$ aws lambda update-function-code \ + --function-name myFunction \ + --zip-file fileb://package.zip +``` + +!!! note + + By default, the AWS Management Console assumes a Lambda entrypoint of `lambda_function.lambda_handler`. + If your application uses a different entrypoint, you'll need to modify it in the AWS Management Console. + For example, the above FastAPI application uses `app.main.handler`. + +### Using a Lambda layer + +AWS Lambda also supports the deployment of multiple composed +[Lambda layers](https://docs.aws.amazon.com/lambda/latest/dg/python-layers.html) when working with +zip archives. These layers are conceptually similar to layers in a Docker image, allowing you to +separate application code from dependencies. + +In particular, we can create a lambda layer for application dependencies and attach it to the Lambda +function, separate from the application code itself. This setup can improve cold-start performance +for application updates, as the dependencies layer can be reused across deployments. + +To create a Lambda layer, we'll follow similar steps, but create two separate zip archives: one for +the application code and one for the application dependencies. + +First, we'll create the dependency layer. Lambda layers are expected to follow a slightly different +structure, so we'll use `--prefix` rather than `--target`: + +```console +$ uv export --frozen --no-dev --no-editable -o requirements.txt +$ uv pip install \ + --no-installer-metadata \ + --no-compile-bytecode \ + --python-platform x86_64-manylinux2014 \ + --python 3.13 \ + --prefix packages \ + -r requirements.txt +``` + +We'll then zip the dependencies in adherence with the expected layout for Lambda layers: + +```console +$ mkdir python +$ cp -r packages/lib python/ +$ zip -r layer_content.zip python +``` + +!!! tip + + To generate deterministic zip archives, consider passing the `-X` flag to `zip` to exclude + extended attributes and file system metadata. + +And publish the Lambda layer: + +```console +$ aws lambda publish-layer-version --layer-name dependencies-layer \ + --zip-file fileb://layer_content.zip \ + --compatible-runtimes python3.13 \ + --compatible-architectures "x86_64" +``` + +We can then create the Lambda function as in the previous example, omitting the dependencies: + +```console +$ # Zip the application code. +$ zip -r app.zip app + +$ # Create the Lambda function. +$ aws lambda create-function \ + --function-name myFunction \ + --runtime python3.13 \ + --zip-file fileb://app.zip \ + --handler app.main.handler \ + --role arn:aws:iam::111122223333:role/service-role/my-lambda-role +``` + +Finally, we can attach the dependencies layer to the Lambda function, using the ARN returned by the +`publish-layer-version` step: + +```console +$ aws lambda update-function-configuration --function-name myFunction \ + --cli-binary-format raw-in-base64-out \ + --layers "arn:aws:lambda:region:111122223333:layer:dependencies-layer:1" +``` + +When the application dependencies change, the layer can be updated independently of the application +by republishing the layer and updating the Lambda function configuration: + +```console +$ # Update the dependencies in the layer. +$ aws lambda publish-layer-version --layer-name dependencies-layer \ + --zip-file fileb://layer_content.zip \ + --compatible-runtimes python3.13 \ + --compatible-architectures "x86_64" + +$ # Update the Lambda function configuration. +$ aws lambda update-function-configuration --function-name myFunction \ + --cli-binary-format raw-in-base64-out \ + --layers "arn:aws:lambda:region:111122223333:layer:dependencies-layer:2" +``` diff --git a/docs/guides/integration/dependency-bots.md b/docs/guides/integration/dependency-bots.md index 63cff791052d..90a45f61e1eb 100644 --- a/docs/guides/integration/dependency-bots.md +++ b/docs/guides/integration/dependency-bots.md @@ -1,3 +1,8 @@ +--- +title: Using uv with dependency bots +description: A guide to using uv with dependency bots like Renovate and Dependabot. +--- + # Dependency bots It is considered best practice to regularly update dependencies, to avoid being exposed to diff --git a/docs/guides/integration/docker.md b/docs/guides/integration/docker.md index 55abe014c272..6d0ca0fb651d 100644 --- a/docs/guides/integration/docker.md +++ b/docs/guides/integration/docker.md @@ -1,3 +1,10 @@ +--- +title: Using uv in Docker +description: + A complete guide to using uv in Docker to manage Python dependencies while optimizing build times + and image size via multi-stage builds, intermediate layers, and more. +--- + # Using uv in Docker ## Getting started @@ -21,7 +28,7 @@ $ docker run ghcr.io/astral-sh/uv --help uv provides a distroless Docker image including the `uv` binary. The following tags are published: - `ghcr.io/astral-sh/uv:latest` -- `ghcr.io/astral-sh/uv:{major}.{minor}.{patch}`, e.g., `ghcr.io/astral-sh/uv:0.5.13` +- `ghcr.io/astral-sh/uv:{major}.{minor}.{patch}`, e.g., `ghcr.io/astral-sh/uv:0.5.18` - `ghcr.io/astral-sh/uv:{major}.{minor}`, e.g., `ghcr.io/astral-sh/uv:0.5` (the latest patch version) @@ -62,7 +69,7 @@ In addition, uv publishes the following images: As with the distroless image, each 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.5.13-alpine`. +`ghcr.io/astral-sh/uv:{major}.{minor}-{base}`, e.g., `ghcr.io/astral-sh/uv:0.5.18-alpine`. For more details, see the [GitHub Container](https://github.com/astral-sh/uv/pkgs/container/uv) page. @@ -100,13 +107,13 @@ 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.5.13 /uv /uvx /bin/ +COPY --from=ghcr.io/astral-sh/uv:0.5.18 /uv /uvx /bin/ ``` Or, with the installer: ```dockerfile -ADD https://astral.sh/uv/0.5.13/install.sh /uv-installer.sh +ADD https://astral.sh/uv/0.5.18/install.sh /uv-installer.sh ``` ### Installing a project diff --git a/docs/guides/integration/fastapi.md b/docs/guides/integration/fastapi.md index 654b2ed14265..6693f46d3c6b 100644 --- a/docs/guides/integration/fastapi.md +++ b/docs/guides/integration/fastapi.md @@ -1,3 +1,10 @@ +--- +title: Using uv with FastAPI +description: + A guide to using uv with FastAPI to manage Python dependencies, run applications, and deploy with + Docker. +--- + # Using uv with FastAPI [FastAPI](https://github.com/fastapi/fastapi) is a modern, high-performance Python web framework. diff --git a/docs/guides/integration/github.md b/docs/guides/integration/github.md index 7ed7b11d9526..1f3c4aa510d9 100644 --- a/docs/guides/integration/github.md +++ b/docs/guides/integration/github.md @@ -1,3 +1,10 @@ +--- +title: Using uv in GitHub Actions +description: + A guide to using uv in GitHub Actions, including installation, setting up Python, installing + dependencies, and more. +--- + # Using uv in GitHub Actions ## Installation @@ -40,7 +47,7 @@ jobs: uses: astral-sh/setup-uv@v5 with: # Install a specific version of uv. - version: "0.5.13" + version: "0.5.18" ``` ## Setting up Python @@ -182,9 +189,6 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v5 - - name: Set up Python - run: uv python install - - name: Install the project run: uv sync --all-extras --dev diff --git a/docs/guides/integration/gitlab.md b/docs/guides/integration/gitlab.md index 60bff070e9f5..ef84efbcf7c0 100644 --- a/docs/guides/integration/gitlab.md +++ b/docs/guides/integration/gitlab.md @@ -1,3 +1,9 @@ +--- +title: Using uv in GitLab CI/CD +description: A guide to using uv in GitLab CI/CD, including installation, setting up Python, + installing dependencies, and more. +--- + # Using uv in GitLab CI/CD ## Using the uv image diff --git a/docs/guides/integration/index.md b/docs/guides/integration/index.md index 3e00d7bf26c7..209ec358a95c 100644 --- a/docs/guides/integration/index.md +++ b/docs/guides/integration/index.md @@ -10,6 +10,7 @@ Learn how to integrate uv with other software: - [Using with alternative package indexes](./alternative-indexes.md) - [Installing PyTorch](./pytorch.md) - [Building a FastAPI application](./fastapi.md) +- [Using with AWS Lambda](./aws-lambda.md) Or, explore the [concept documentation](../../concepts/index.md) for comprehensive breakdown of each feature. diff --git a/docs/guides/integration/jupyter.md b/docs/guides/integration/jupyter.md index 358a090d49b6..a81890287811 100644 --- a/docs/guides/integration/jupyter.md +++ b/docs/guides/integration/jupyter.md @@ -1,3 +1,10 @@ +--- +title: Using uv with Jupyter +description: + A complete guide to using uv with Jupyter notebooks for interactive computing, data analysis, and + visualization, including kernel management and virtual environment integration. +--- + # Using uv with Jupyter The [Jupyter](https://jupyter.org/) notebook is a popular tool for interactive computing, data @@ -99,12 +106,23 @@ If you need to run Jupyter in a virtual environment that isn't associated with a [project](../../concepts/projects/index.md) (e.g., has no `pyproject.toml` or `uv.lock`), you can do so by adding Jupyter to the environment directly. For example: -```console -$ uv venv --seed -$ uv pip install pydantic -$ uv pip install jupyterlab -$ .venv/bin/jupyter lab -``` +=== "macOS and Linux" + + ```console + $ uv venv --seed + $ uv pip install pydantic + $ uv pip install jupyterlab + $ .venv/bin/jupyter lab + ``` + +=== "Windows" + + ```powershell + uv venv --seed + uv pip install pydantic + uv pip install jupyterlab + .venv\Scripts\jupyter lab + ``` From here, `import pydantic` will work within the notebook, and you can install additional packages via `!uv pip install`, or even `!pip install`. @@ -118,10 +136,13 @@ project, as in the following: ```console # Create a project. $ uv init project + # Move into the project directory. $ cd project + # Add ipykernel as a dev dependency. $ uv add --dev ipykernel + # Open the project in VS Code. $ code . ``` @@ -129,7 +150,7 @@ $ code . Once the project directory is open in VS Code, you can create a new Jupyter notebook by selecting "Create: New Jupyter Notebook" from the command palette. When prompted to select a kernel, choose "Python Environments" and select the virtual environment you created earlier (e.g., -`.venv/bin/python`). +`.venv/bin/python` on macOS and Linux, or `.venv\Scripts\python` on Windows). !!! note diff --git a/docs/guides/integration/pre-commit.md b/docs/guides/integration/pre-commit.md index c41be00df52c..8e7f623e7c39 100644 --- a/docs/guides/integration/pre-commit.md +++ b/docs/guides/integration/pre-commit.md @@ -1,3 +1,10 @@ +--- +title: Using uv with pre-commit +description: + A guide to using uv with pre-commit to automatically update lock files, export requirements, and + compile requirements files. +--- + # Using uv in pre-commit An official pre-commit hook is provided at @@ -29,7 +36,7 @@ To compile requirements via pre-commit, add the following to the `.pre-commit-co ```yaml title=".pre-commit-config.yaml" - repo: https://github.com/astral-sh/uv-pre-commit # uv version. - rev: 0.5.13 + rev: 0.5.18 hooks: # Compile requirements - id: pip-compile @@ -41,7 +48,7 @@ To compile alternative files, modify `args` and `files`: ```yaml title=".pre-commit-config.yaml" - repo: https://github.com/astral-sh/uv-pre-commit # uv version. - rev: 0.5.13 + rev: 0.5.18 hooks: # Compile requirements - id: pip-compile @@ -54,7 +61,7 @@ To run the hook over multiple files at the same time: ```yaml title=".pre-commit-config.yaml" - repo: https://github.com/astral-sh/uv-pre-commit # uv version. - rev: 0.5.13 + rev: 0.5.18 hooks: # Compile requirements - id: pip-compile diff --git a/docs/guides/integration/pytorch.md b/docs/guides/integration/pytorch.md index 8b71d8567e41..ea1104706261 100644 --- a/docs/guides/integration/pytorch.md +++ b/docs/guides/integration/pytorch.md @@ -1,3 +1,10 @@ +--- +title: Using uv with PyTorch +description: + A guide to using uv with PyTorch, including installing PyTorch, configuring per-platform and + per-accelerator builds, and more. +--- + # Using uv with PyTorch The [PyTorch](https://pytorch.org/) ecosystem is a popular choice for deep learning research and diff --git a/docs/guides/projects.md b/docs/guides/projects.md index 867a25238b44..a9d219162b55 100644 --- a/docs/guides/projects.md +++ b/docs/guides/projects.md @@ -177,12 +177,23 @@ $ uv run example.py Alternatively, you can use `uv sync` to manually update the environment then activate it before executing a command: -```console -$ uv sync -$ source .venv/bin/activate -$ flask run -p 3000 -$ python example.py -``` +=== "macOS and Linux" + + ```console + $ uv sync + $ source .venv/bin/activate + $ flask run -p 3000 + $ python example.py + ``` + +=== "Windows" + + ```powershell + uv sync + source .venv\Scripts\activate + flask run -p 3000 + python example.py + ``` !!! note diff --git a/docs/guides/scripts.md b/docs/guides/scripts.md index 1b337986049f..c6d10af63fde 100644 --- a/docs/guides/scripts.md +++ b/docs/guides/scripts.md @@ -74,7 +74,7 @@ install the current project before running the script. If your script does not d project, use the `--no-project` flag to skip this: ```console -$ # Note, it is important that the flag comes _before_ the script +$ # Note: the `--no-project` flag must be provided _before_ the script name. $ uv run --no-project example.py ``` @@ -210,11 +210,30 @@ print(Point) is not installed — see the documentation on [Python versions](../concepts/python-versions.md) for more details. +## Locking dependencies + +uv supports locking dependencies for PEP 723 scripts using the `uv.lock` file format. Unlike with +projects, scripts must be explicitly locked using `uv lock`: + +```console +$ uv lock --script example.py +``` + +Running `uv lock --script` will create a `.lock` file adjacent to the script (e.g., +`example.py.lock`). + +Once locked, subsequent operations like `uv run --script`, `uv add --script`, `uv export --script`, +and `uv tree --script` will reuse the locked dependencies, updating the lockfile if necessary. + +If no such lockfile is present, commands like `uv export --script` will still function as expected, +but will not create a lockfile. + ## Improving reproducibility -uv supports an `exclude-newer` field in the `tool.uv` section of inline script metadata to limit uv -to only considering distributions released before a specific date. This is useful for improving the -reproducibility of your script when run at a later point in time. +In addition to locking dependencies, uv supports an `exclude-newer` field in the `tool.uv` section +of inline script metadata to limit uv to only considering distributions released before a specific +date. This is useful for improving the reproducibility of your script when run at a later point in +time. The date must be specified as an [RFC 3339](https://www.rfc-editor.org/rfc/rfc3339.html) timestamp (e.g., `2006-12-02T02:07:43Z`). diff --git a/docs/guides/tools.md b/docs/guides/tools.md index 8300d1d5db33..e1419f0e60f2 100644 --- a/docs/guides/tools.md +++ b/docs/guides/tools.md @@ -235,4 +235,4 @@ $ uv tool upgrade --all To learn more about managing tools with uv, see the [Tools concept](../concepts/tools.md) page and the [command reference](../reference/cli.md#uv-tool). -Or, read on to learn how to to [work on projects](./projects.md). +Or, read on to learn how to [work on projects](./projects.md). diff --git a/docs/pip/packages.md b/docs/pip/packages.md index 3643f4c0cdfd..ab363105e081 100644 --- a/docs/pip/packages.md +++ b/docs/pip/packages.md @@ -62,7 +62,7 @@ for installation from a private repository. ## Editable packages -Editable packages do not need to be reinstalled for change to their source code to be active. +Editable packages do not need to be reinstalled for changes to their source code to be active. To install the current project as an editable package diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 602c77c9224f..ed6db02acf7b 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -104,9 +104,10 @@ uv run [OPTIONS] [COMMAND]

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

May also be set with the UV_CACHE_DIR environment variable.

-
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -572,9 +573,10 @@ uv init [OPTIONS] [PATH]

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

    May also be set with the UV_CACHE_DIR environment variable.

    -
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -766,9 +768,10 @@ uv add [OPTIONS] >

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

    May also be set with the UV_CACHE_DIR environment variable.

    -
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -1132,9 +1135,10 @@ uv remove [OPTIONS] ...

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

    May also be set with the UV_CACHE_DIR environment variable.

    -
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -1484,9 +1488,10 @@ uv sync [OPTIONS]

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

    May also be set with the UV_CACHE_DIR environment variable.

    -
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -1873,9 +1878,10 @@ uv lock [OPTIONS]

    Equivalent to --frozen.

    May also be set with the UV_FROZEN environment variable.

    -
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -2129,6 +2135,10 @@ uv lock [OPTIONS]
  • lowest-direct: Resolve the lowest compatible version of any direct dependencies, and the highest compatible version of any transitive dependencies
+
--script script

Lock the specified Python script, rather than the current project.

+ +

If provided, uv will lock the script (based on its inline metadata table, in adherence with PEP 723) to a .lock file adjacent to the script itself.

+
--upgrade, -U

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

--upgrade-package, -P upgrade-package

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

@@ -2189,9 +2199,10 @@ uv export [OPTIONS]

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

May also be set with the UV_CACHE_DIR environment variable.

-
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -2521,6 +2532,10 @@ uv export [OPTIONS]
  • lowest-direct: Resolve the lowest compatible version of any direct dependencies, and the highest compatible version of any transitive dependencies
+
--script script

Export the dependencies for the specified PEP 723 Python script, rather than the current project.

+ +

If provided, uv will resolve the dependencies based on its inline metadata table, in adherence with PEP 723.

+
--upgrade, -U

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

--upgrade-package, -P upgrade-package

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

@@ -2565,9 +2580,10 @@ uv tree [OPTIONS]

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

May also be set with the UV_CACHE_DIR environment variable.

-
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -2947,6 +2963,10 @@ uv tree [OPTIONS]
  • lowest-direct: Resolve the lowest compatible version of any direct dependencies, and the highest compatible version of any transitive dependencies
+
--script script

Show the dependency tree the specified PEP 723 Python script, rather than the current project.

+ +

If provided, uv will resolve the dependencies based on its inline metadata table, in adherence with PEP 723.

+
--universal

Show a platform-independent dependency tree.

Shows resolved package versions for all Python versions and platforms, rather than filtering to those that are relevant for the current environment.

@@ -3033,9 +3053,10 @@ uv tool run [OPTIONS] [COMMAND]

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

May also be set with the UV_CACHE_DIR environment variable.

-
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -3354,9 +3375,10 @@ uv tool install [OPTIONS]

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

    May also be set with the UV_CACHE_DIR environment variable.

    -
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -3687,9 +3709,10 @@ uv tool upgrade [OPTIONS] ...

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

    May also be set with the UV_CACHE_DIR environment variable.

    -
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -3976,9 +3999,10 @@ uv tool list [OPTIONS]

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

    May also be set with the UV_CACHE_DIR environment variable.

    -
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -4086,9 +4110,10 @@ uv tool uninstall [OPTIONS] ...

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

    May also be set with the UV_CACHE_DIR environment variable.

    -
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -4208,9 +4233,10 @@ uv tool update-shell [OPTIONS]

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

    May also be set with the UV_CACHE_DIR environment variable.

    -
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -4348,9 +4374,10 @@ uv tool dir [OPTIONS]

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

    May also be set with the UV_CACHE_DIR environment variable.

    -
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -4548,9 +4575,10 @@ uv python list [OPTIONS]

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

    May also be set with the UV_CACHE_DIR environment variable.

    -
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -4696,9 +4724,10 @@ uv python install [OPTIONS] [TARGETS]...

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

    May also be set with the UV_CACHE_DIR environment variable.

    -
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -4863,9 +4892,10 @@ uv python find [OPTIONS] [REQUEST]

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

    May also be set with the UV_CACHE_DIR environment variable.

    -
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -5008,9 +5038,10 @@ uv python pin [OPTIONS] [REQUEST]

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

    May also be set with the UV_CACHE_DIR environment variable.

    -
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -5156,9 +5187,10 @@ uv python dir [OPTIONS]

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

    May also be set with the UV_CACHE_DIR environment variable.

    -
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -5282,9 +5314,10 @@ uv python uninstall [OPTIONS] ...

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

    May also be set with the UV_CACHE_DIR environment variable.

    -
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -5465,9 +5498,10 @@ uv pip compile [OPTIONS] ...

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

    May also be set with the UV_CACHE_DIR environment variable.

    -
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -5934,9 +5968,10 @@ uv pip sync [OPTIONS] ...

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

    May also be set with the UV_CACHE_DIR environment variable.

    -
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -6331,9 +6366,10 @@ uv pip install [OPTIONS] |--editable To view the location of the cache directory, run uv cache dir.

    May also be set with the UV_CACHE_DIR environment variable.

    -
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -6801,9 +6837,10 @@ uv pip uninstall [OPTIONS] >

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

    May also be set with the UV_CACHE_DIR environment variable.

    -
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -6953,9 +6990,10 @@ uv pip freeze [OPTIONS]

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

    May also be set with the UV_CACHE_DIR environment variable.

    -
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -7087,9 +7125,10 @@ uv pip list [OPTIONS]

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

    May also be set with the UV_CACHE_DIR environment variable.

    -
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -7317,9 +7356,10 @@ uv pip show [OPTIONS] [PACKAGE]...

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

    May also be set with the UV_CACHE_DIR environment variable.

    -
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -7451,9 +7491,10 @@ uv pip tree [OPTIONS]

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

    May also be set with the UV_CACHE_DIR environment variable.

    -
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -7668,9 +7709,10 @@ uv pip check [OPTIONS]

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

    May also be set with the UV_CACHE_DIR environment variable.

    -
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -7822,9 +7864,10 @@ uv venv [OPTIONS] [PATH]

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

    May also be set with the UV_CACHE_DIR environment variable.

    -
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -8086,9 +8129,10 @@ uv build [OPTIONS] [SRC]

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

    May also be set with the UV_CACHE_DIR environment variable.

    -
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -8433,9 +8477,10 @@ uv publish [OPTIONS] [FILES]...

    The index must provide one of the supported hashes (SHA-256, SHA-384, or SHA-512).

    May also be set with the UV_PUBLISH_CHECK_URL environment variable.

    -
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -8629,9 +8674,10 @@ uv cache clean [OPTIONS] [PACKAGE]...

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

    May also be set with the UV_CACHE_DIR environment variable.

    -
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -8751,9 +8797,10 @@ uv cache prune [OPTIONS]

    In --ci mode, uv will prune any pre-built wheels from the cache, but retain any wheels that were built from source.

    -
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -8875,9 +8922,10 @@ uv cache dir [OPTIONS]

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

    May also be set with the UV_CACHE_DIR environment variable.

    -
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -9013,9 +9061,10 @@ uv self update [OPTIONS] [TARGET_VERSION]

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

    May also be set with the UV_CACHE_DIR environment variable.

    -
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -9132,9 +9181,10 @@ uv version [OPTIONS]

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

    May also be set with the UV_CACHE_DIR environment variable.

    -
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    @@ -9297,9 +9347,10 @@ uv help [OPTIONS] [COMMAND]...

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

    May also be set with the UV_CACHE_DIR environment variable.

    -
--color color-choice

Control colors in output

+
--color color-choice

Control the use of color in output.

+ +

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

-

[default: auto]

Possible values:

    diff --git a/docs/reference/settings.md b/docs/reference/settings.md index e0e2fc738dd3..f401eef33adc 100644 --- a/docs/reference/settings.md +++ b/docs/reference/settings.md @@ -1522,6 +1522,35 @@ Reinstall a specific package, regardless of whether it's already installed. Impl --- +### [`required-version`](#required-version) {: #required-version } + +Enforce a requirement on the version of uv. + +If the version of uv does not meet the requirement at runtime, uv will exit +with an error. + +Accepts a [PEP 440](https://peps.python.org/pep-0440/) specifier, like `==0.5.0` or `>=0.5.0`. + +**Default value**: `null` + +**Type**: `str` + +**Example usage**: + +=== "pyproject.toml" + + ```toml + [tool.uv] + required-version = ">=0.5.0" + ``` +=== "uv.toml" + + ```toml + required-version = ">=0.5.0" + ``` + +--- + ### [`resolution`](#resolution) {: #resolution } The strategy to use when selecting between the different compatible versions for a given @@ -2026,11 +2055,11 @@ be correct. #### [`exclude-newer`](#pip_exclude-newer) {: #pip_exclude-newer } -Limit candidate packages to those that were uploaded prior to the given date. +Limit candidate packages to those that were uploaded prior to a given point in time. -Accepts both [RFC 3339](https://www.rfc-editor.org/rfc/rfc3339.html) timestamps (e.g., -`2006-12-02T02:07:43Z`) and local dates in the same format (e.g., `2006-12-02`) in your -system's configured time zone. +Accepts 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. **Default value**: `None` @@ -2042,13 +2071,13 @@ system's configured time zone. ```toml [tool.uv.pip] - exclude-newer = "2006-12-02" + exclude-newer = "2006-12-02T02:07:43Z" ``` === "uv.toml" ```toml [pip] - exclude-newer = "2006-12-02" + exclude-newer = "2006-12-02T02:07:43Z" ``` --- diff --git a/mkdocs.template.yml b/mkdocs.template.yml index 0753abbdbdd8..ef1ca20d3402 100644 --- a/mkdocs.template.yml +++ b/mkdocs.template.yml @@ -112,6 +112,7 @@ nav: - FastAPI: guides/integration/fastapi.md - Alternative indexes: guides/integration/alternative-indexes.md - Dependency bots: guides/integration/dependency-bots.md + - AWS Lambda: guides/integration/aws-lambda.md - Concepts: - concepts/index.md - Projects: diff --git a/pyproject.toml b/pyproject.toml index 8dadfc5b90af..88b0e3802edf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "uv" -version = "0.5.13" +version = "0.5.18" description = "An extremely fast Python package and project manager, written in Rust." authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }] requires-python = ">=3.8" @@ -83,6 +83,7 @@ version_files = [ "docs/guides/integration/docker.md", "docs/guides/integration/pre-commit.md", "docs/guides/integration/github.md", + "docs/guides/integration/aws-lambda.md", ] [tool.mypy] diff --git a/python/uv/_build_backend.py b/python/uv/_build_backend.py index 88bd54c91286..127342d8eea8 100644 --- a/python/uv/_build_backend.py +++ b/python/uv/_build_backend.py @@ -16,15 +16,19 @@ them while IDEs and type checker can see through the quotes. """ +TYPE_CHECKING = False +if TYPE_CHECKING: + from typing import Any # noqa:I001 -def warn_config_settings(config_settings: "dict | None" = None): + +def warn_config_settings(config_settings: "dict[Any, Any] | None" = None) -> None: import sys if config_settings: print("Warning: Config settings are not supported", file=sys.stderr) -def call(args: "list[str]", config_settings: "dict | None" = None) -> str: +def call(args: "list[str]", config_settings: "dict[Any, Any] | None" = None) -> str: """Invoke a uv subprocess and return the filename from stdout.""" import shutil import subprocess @@ -49,7 +53,9 @@ def call(args: "list[str]", config_settings: "dict | None" = None) -> str: return stdout[-1].strip() -def build_sdist(sdist_directory: str, config_settings: "dict | None" = None): +def build_sdist( + sdist_directory: str, config_settings: "dict[Any, Any] | None" = None +) -> str: """PEP 517 hook `build_sdist`.""" args = ["build-backend", "build-sdist", sdist_directory] return call(args, config_settings) @@ -57,9 +63,9 @@ def build_sdist(sdist_directory: str, config_settings: "dict | None" = None): def build_wheel( wheel_directory: str, - config_settings: "dict | None" = None, + config_settings: "dict[Any, Any] | None" = None, metadata_directory: "str | None" = None, -): +) -> str: """PEP 517 hook `build_wheel`.""" args = ["build-backend", "build-wheel", wheel_directory] if metadata_directory: @@ -67,21 +73,25 @@ def build_wheel( return call(args, config_settings) -def get_requires_for_build_sdist(config_settings: "dict | None" = None): +def get_requires_for_build_sdist( + config_settings: "dict[Any, Any] | None" = None, +) -> "list[str]": """PEP 517 hook `get_requires_for_build_sdist`.""" warn_config_settings(config_settings) return [] -def get_requires_for_build_wheel(config_settings: "dict | None" = None): +def get_requires_for_build_wheel( + config_settings: "dict[Any, Any] | None" = None, +) -> "list[str]": """PEP 517 hook `get_requires_for_build_wheel`.""" warn_config_settings(config_settings) return [] def prepare_metadata_for_build_wheel( - metadata_directory: str, config_settings: "dict | None" = None -): + metadata_directory: str, config_settings: "dict[Any, Any] | None" = None +) -> str: """PEP 517 hook `prepare_metadata_for_build_wheel`.""" args = ["build-backend", "prepare-metadata-for-build-wheel", metadata_directory] return call(args, config_settings) @@ -89,9 +99,9 @@ def prepare_metadata_for_build_wheel( def build_editable( wheel_directory: str, - config_settings: "dict | None" = None, + config_settings: "dict[Any, Any] | None" = None, metadata_directory: "str | None" = None, -): +) -> str: """PEP 660 hook `build_editable`.""" args = ["build-backend", "build-editable", wheel_directory] if metadata_directory: @@ -99,15 +109,17 @@ def build_editable( return call(args, config_settings) -def get_requires_for_build_editable(config_settings: "dict | None" = None): +def get_requires_for_build_editable( + config_settings: "dict[Any, Any] | None" = None, +) -> "list[str]": """PEP 660 hook `get_requires_for_build_editable`.""" warn_config_settings(config_settings) return [] def prepare_metadata_for_build_editable( - metadata_directory: str, config_settings: "dict | None" = None -): + metadata_directory: str, config_settings: "dict[Any, Any] | None" = None +) -> str: """PEP 660 hook `prepare_metadata_for_build_editable`.""" args = ["build-backend", "prepare-metadata-for-build-editable", metadata_directory] return call(args, config_settings) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index a718dc2fc84c..9db33c0e40d1 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.83" +channel = "1.84" diff --git a/scripts/benchmark/src/benchmark/resolver.py b/scripts/benchmark/src/benchmark/resolver.py index 923a4c313722..54ae8471944a 100644 --- a/scripts/benchmark/src/benchmark/resolver.py +++ b/scripts/benchmark/src/benchmark/resolver.py @@ -443,9 +443,9 @@ def resolve_incremental( self.setup(requirements_file, cwd=cwd) poetry_lock = os.path.join(cwd, "poetry.lock") - assert not os.path.exists( - poetry_lock - ), f"Lockfile already exists at: {poetry_lock}" + assert not os.path.exists(poetry_lock), ( + f"Lockfile already exists at: {poetry_lock}" + ) # Run a resolution, to ensure that the lockfile exists. # TODO(charlie): Make this a `setup`. @@ -499,9 +499,9 @@ def resolve_noop(self, requirements_file: str, *, cwd: str) -> Command | None: self.setup(requirements_file, cwd=cwd) poetry_lock = os.path.join(cwd, "poetry.lock") - assert not os.path.exists( - poetry_lock - ), f"Lockfile already exists at: {poetry_lock}" + assert not os.path.exists(poetry_lock), ( + f"Lockfile already exists at: {poetry_lock}" + ) # Run a resolution, to ensure that the lockfile exists. # TODO(charlie): Make this a `setup`. @@ -536,9 +536,9 @@ def install_cold(self, requirements_file: str, *, cwd: str) -> Command | None: self.setup(requirements_file, cwd=cwd) poetry_lock = os.path.join(cwd, "poetry.lock") - assert not os.path.exists( - poetry_lock - ), f"Lockfile already exists at: {poetry_lock}" + assert not os.path.exists(poetry_lock), ( + f"Lockfile already exists at: {poetry_lock}" + ) # Run a resolution, to ensure that the lockfile exists. # TODO(charlie): Make this a `setup`. @@ -581,9 +581,9 @@ def install_warm(self, requirements_file: str, *, cwd: str) -> Command | None: self.setup(requirements_file, cwd=cwd) poetry_lock = os.path.join(cwd, "poetry.lock") - assert not os.path.exists( - poetry_lock - ), f"Lockfile already exists at: {poetry_lock}" + assert not os.path.exists(poetry_lock), ( + f"Lockfile already exists at: {poetry_lock}" + ) # Run a resolution, to ensure that the lockfile exists. subprocess.check_call( diff --git a/scripts/popular_packages/pypi_10k_most_dependents.ipynb b/scripts/popular_packages/pypi_10k_most_dependents.ipynb index 9dcf3c0d1b3c..6b9d401e68ca 100644 --- a/scripts/popular_packages/pypi_10k_most_dependents.ipynb +++ b/scripts/popular_packages/pypi_10k_most_dependents.ipynb @@ -37,7 +37,7 @@ " if i not in responses:\n", " # https://libraries.io/api#project-search\n", " sort = \"dependents_count\"\n", - " url = f\"https://libraries.io/api/search?platforms=Pypi&per_page=100&page={i+1}&sort{sort}&api_key={api_key}\"\n", + " url = f\"https://libraries.io/api/search?platforms=Pypi&per_page=100&page={i + 1}&sort{sort}&api_key={api_key}\"\n", " responses[i] = httpx.get(url, timeout=30.0).json()" ] }, diff --git a/scripts/publish/test_publish.py b/scripts/publish/test_publish.py index 55666274c1c7..f3f8e8d0fdb0 100644 --- a/scripts/publish/test_publish.py +++ b/scripts/publish/test_publish.py @@ -371,8 +371,8 @@ def publish_project(target: str, uv: Path, client: httpx.Client): ): raise RuntimeError( f"PyPI re-upload of the same files failed: " - f"{output.count("Uploading")} != {len(expected_filenames)}, " - f"{output.count("already exists")} != 0\n" + f"{output.count('Uploading')} != {len(expected_filenames)}, " + f"{output.count('already exists')} != 0\n" f"---\n{output}\n---" ) @@ -407,8 +407,8 @@ def publish_project(target: str, uv: Path, client: httpx.Client): ): raise RuntimeError( f"Re-upload with check URL failed: " - f"{output.count("Uploading")} != 0, " - f"{output.count("already exists")} != {len(expected_filenames)}\n" + f"{output.count('Uploading')} != 0, " + f"{output.count('already exists')} != {len(expected_filenames)}\n" f"---\n{output}\n---" ) diff --git a/scripts/requirements/airflow2-constraints.txt b/scripts/requirements/airflow2-constraints.txt new file mode 100644 index 000000000000..8fdda5d3d7c6 --- /dev/null +++ b/scripts/requirements/airflow2-constraints.txt @@ -0,0 +1,299 @@ +adal==1.2.7 +alembic==1.8.1 +amqp==5.1.1 +anyio==3.6.1 +apache-airflow==2.3.4 +apache-airflow-providers-amazon==5.0.0 +apache-airflow-providers-celery==3.0.0 +apache-airflow-providers-cncf-kubernetes==4.3.0 +apache-airflow-providers-common-sql==1.1.0 +apache-airflow-providers-docker==3.1.0 +apache-airflow-providers-elasticsearch==4.2.0 +apache-airflow-providers-ftp==3.1.0 +apache-airflow-providers-google==8.3.0 +apache-airflow-providers-grpc==3.0.0 +apache-airflow-providers-hashicorp==3.1.0 +apache-airflow-providers-http==4.0.0 +apache-airflow-providers-imap==3.0.0 +apache-airflow-providers-microsoft-azure==4.2.0 +apache-airflow-providers-mysql==3.2.0 +apache-airflow-providers-odbc==3.1.1 +apache-airflow-providers-postgres==5.2.0 +apache-airflow-providers-redis==3.0.0 +apache-airflow-providers-sendgrid==3.0.0 +apache-airflow-providers-sftp==4.0.0 +apache-airflow-providers-slack==5.1.0 +apache-airflow-providers-sqlite==3.2.0 +apache-airflow-providers-ssh==3.1.0 +apispec==3.3.2 +argcomplete==2.0.0 +asn1crypto==1.5.1 +attrs==22.1.0 +Authlib==0.15.5 +azure-batch==12.0.0 +azure-common==1.1.28 +azure-core==1.25.0 +azure-cosmos==4.3.0 +azure-datalake-store==0.0.52 +azure-identity==1.10.0 +azure-keyvault-secrets==4.5.1 +azure-kusto-data==0.0.45 +azure-mgmt-containerinstance==1.5.0 +azure-mgmt-core==1.3.2 +azure-mgmt-datafactory==1.1.0 +azure-mgmt-datalake-nspkg==3.0.1 +azure-mgmt-datalake-store==0.5.0 +azure-mgmt-nspkg==3.0.2 +azure-mgmt-resource==21.1.0 +azure-nspkg==3.0.2 +azure-servicebus==7.8.0 +azure-storage-blob==12.8.1 +azure-storage-common==2.1.0 +azure-storage-file==2.1.0 +Babel==2.10.3 +bcrypt==3.2.2 +beautifulsoup4==4.11.1 +billiard==3.6.4.0 +blinker==1.5 +boto3==1.24.56 +botocore==1.27.56 +cachelib==0.9.0 +cachetools==4.2.2 +cattrs==22.1.0 +celery==5.2.7 +certifi==2022.6.15 +cffi==1.15.1 +charset-normalizer==2.0.12 +click==8.1.3 +click-didyoumean==0.3.0 +click-plugins==1.1.1 +click-repl==0.2.0 +clickclick==20.10.2 +cloudpickle==2.1.0 +colorama==0.4.5 +colorlog==4.8.0 +commonmark==0.9.1 +connexion==2.14.0 +cron_descriptor==1.2.31 +croniter==1.3.5 +cryptography==36.0.2 +dask==2022.8.0 +db-dtypes==1.0.3 +decorator==5.1.1 +Deprecated==1.2.13 +dill==0.3.1.1 +distlib==0.3.5 +distributed==2022.8.0 +dnspython==2.2.1 +docker==6.1.3 +docutils==0.19 +elasticsearch==7.13.4 +elasticsearch-dbapi==0.2.9 +elasticsearch-dsl==7.4.0 +email-validator==1.2.1 +eventlet==0.33.1 +exceptiongroup==1.0.0rc8 +filelock==3.8.0 +Flask==2.2.2 +Flask-AppBuilder==4.1.3 +Flask-Babel==2.0.0 +Flask-Caching==2.0.1 +Flask-JWT-Extended==4.4.4 +Flask-Login==0.6.2 +Flask-Session==0.4.0 +Flask-SQLAlchemy==2.5.1 +Flask-WTF==0.15.1 +flower==1.2.0 +fsspec==2022.7.1 +future==0.18.2 +gevent==21.12.0 +google-ads==18.0.0 +google-api-core==2.8.2 +google-api-python-client==1.12.11 +google-auth==2.10.0 +google-auth-httplib2==0.1.0 +google-auth-oauthlib==0.5.2 +google-cloud-aiplatform==1.16.1 +google-cloud-appengine-logging==1.1.3 +google-cloud-audit-log==0.2.4 +google-cloud-automl==2.8.0 +google-cloud-bigquery==2.34.4 +google-cloud-bigquery-datatransfer==3.7.0 +google-cloud-bigquery-storage==2.14.1 +google-cloud-bigtable==1.7.2 +google-cloud-build==3.9.0 +google-cloud-container==2.11.1 +google-cloud-core==2.3.2 +google-cloud-datacatalog==3.9.0 +google-cloud-dataform==0.2.0 +google-cloud-dataplex==1.1.0 +google-cloud-dataproc==5.0.0 +google-cloud-dataproc-metastore==1.6.0 +google-cloud-dlp==1.0.2 +google-cloud-kms==2.12.0 +google-cloud-language==1.3.2 +google-cloud-logging==3.2.1 +google-cloud-memcache==1.4.1 +google-cloud-monitoring==2.11.0 +google-cloud-orchestration-airflow==1.4.1 +google-cloud-os-login==2.7.1 +google-cloud-pubsub==2.13.5 +google-cloud-redis==2.9.0 +google-cloud-resource-manager==1.6.0 +google-cloud-secret-manager==1.0.2 +google-cloud-spanner==1.19.3 +google-cloud-speech==1.3.4 +google-cloud-storage==1.44.0 +google-cloud-tasks==2.10.1 +google-cloud-texttospeech==1.0.3 +google-cloud-translate==1.7.2 +google-cloud-videointelligence==1.16.3 +google-cloud-vision==1.0.2 +google-cloud-workflows==1.7.1 +google-crc32c==1.3.0 +google-resumable-media==2.3.3 +googleapis-common-protos==1.56.4 +graphviz==0.20.1 +greenlet==1.1.2 +grpc-google-iam-v1==0.12.4 +grpcio==1.47.0 +grpcio-gcp==0.2.2 +grpcio-status==1.47.0 +gunicorn==20.1.0 +h11==0.12.0 +HeapDict==1.0.1 +httpcore==0.15.0 +httplib2==0.20.4 +httpx==0.23.0 +humanize==4.3.0 +hvac==1.1.1 +idna==3.3 +inflection==0.5.1 +isodate==0.6.1 +itsdangerous==2.1.2 +Jinja2==3.1.2 +jmespath==0.10.0 +json-merge-patch==0.2 +jsonpath-ng==1.5.3 +jsonschema==4.13.0 +kombu==5.2.4 +kubernetes==23.6.0 +lazy-object-proxy==1.7.1 +ldap3==2.9.1 +linkify-it-py==2.0.0 +locket==1.0.0 +lockfile==0.12.2 +looker-sdk==22.10.0 +lxml==4.9.1 +Mako==1.2.1 +Markdown==3.4.1 +markdown-it-py==2.1.0 +MarkupSafe==2.1.1 +marshmallow==3.17.0 +marshmallow-enum==1.5.1 +marshmallow-oneofschema==3.0.1 +marshmallow-sqlalchemy==0.26.1 +mdit-py-plugins==0.3.0 +mdurl==0.1.2 +msal==1.18.0 +msal-extensions==1.0.0 +msgpack==1.0.4 +msrest==0.7.1 +msrestazure==0.6.4 +mypy-boto3-appflow==1.24.36.post1 +mypy-boto3-rds==1.24.54 +mypy-boto3-redshift-data==1.24.36.post1 +mysql-connector-python==8.0.30 +mysqlclient==2.1.1 +numpy==1.22.4 +oauthlib==3.2.0 +packaging==21.3 +pandas==1.4.3 +pandas-gbq==0.17.8 +paramiko==2.11.0 +partd==1.3.0 +pathspec==0.9.0 +pendulum==2.1.2 +platformdirs==2.5.2 +pluggy==1.0.0 +ply==3.11 +portalocker==2.5.1 +prison==0.2.1 +prometheus-client==0.14.1 +prompt-toolkit==3.0.30 +proto-plus==1.19.6 +protobuf==3.20.0 +psutil==5.9.1 +psycopg2-binary==2.9.3 +pyarrow==6.0.1 +pyasn1==0.4.8 +pyasn1-modules==0.2.8 +pycparser==2.21 +pydata-google-auth==1.4.0 +Pygments==2.13.0 +PyJWT==2.4.0 +PyNaCl==1.5.0 +pyodbc==4.0.34 +pyOpenSSL==22.0.0 +pyparsing==3.0.9 +pyrsistent==0.18.1 +python-daemon==2.3.1 +python-dateutil==2.8.2 +python-http-client==3.3.7 +python-ldap==3.4.2 +python-nvd3==0.15.0 +python-slugify==6.1.2 +pytz==2022.1 +pytzdata==2020.1 +PyYAML==6.0 +redis==3.5.3 +redshift-connector==2.0.908 +requests==2.28.0 +requests-oauthlib==1.3.1 +requests-toolbelt==0.9.1 +rfc3986==1.5.0 +rich==12.5.1 +rsa==4.9 +s3transfer==0.6.0 +scramp==1.4.1 +sendgrid==6.9.7 +setproctitle==1.3.2 +six==1.16.0 +slack-sdk==3.18.1 +sniffio==1.2.0 +sortedcontainers==2.4.0 +soupsieve==2.3.2.post1 +SQLAlchemy==1.4.27 +sqlalchemy-bigquery==1.4.4 +SQLAlchemy-JSONField==1.0.0 +sqlalchemy-redshift==0.8.11 +SQLAlchemy-Utils==0.38.3 +sqlparse==0.4.2 +sshtunnel==0.4.0 +starkbank-ecdsa==2.0.3 +statsd==3.3.0 +swagger-ui-bundle==0.0.9 +tabulate==0.8.10 +tblib==1.7.0 +tenacity==8.0.1 +termcolor==1.1.0 +text-unidecode==1.3 +toolz==0.12.0 +tornado==6.1 +typing_extensions==4.3.0 +uamqp==1.6.0 +uc-micro-py==1.0.1 +unicodecsv==0.14.1 +uritemplate==3.0.1 +urllib3==1.26.11 +vine==5.0.0 +virtualenv==20.16.3 +watchtower==2.0.1 +wcwidth==0.2.5 +websocket-client==1.3.3 +Werkzeug==2.2.2 +wrapt==1.14.1 +WTForms==2.3.3 +zict==2.2.0 +zope.event==4.5.0 +zope.interface==5.4.0 diff --git a/scripts/requirements/airflow2-req.in b/scripts/requirements/airflow2-req.in new file mode 100644 index 000000000000..70ed2851a9d8 --- /dev/null +++ b/scripts/requirements/airflow2-req.in @@ -0,0 +1,7 @@ +# Run with: +# ``` +# uv pip compile scripts/requirements/airflow2-req.in -c scripts/requirements/airflow2-constraints.txt --universal --python-version 3.8 +# ``` +apache-airflow[amazon,async,google,google_auth,grpc,hashicorp,http,ldap,mysql,odbc,pandas,postgres,redis,sftp,slack,ssh,statsd,virtualenv]==2.3.4 +alembic>=1.8.1 +ipython>=8.4.0 diff --git a/scripts/scenarios/generate.py b/scripts/scenarios/generate.py index 6b9c4e462115..d6db67eeb354 100755 --- a/scripts/scenarios/generate.py +++ b/scripts/scenarios/generate.py @@ -269,9 +269,9 @@ def update_common_mod_rs(packse_version: str): url_matcher = re.compile( re.escape(before_version) + '[^"]+' + re.escape(after_version) ) - assert ( - len(url_matcher.findall(test_common)) == 1 - ), f"PACKSE_VERSION not found in {TESTS_COMMON_MOD_RS}" + assert len(url_matcher.findall(test_common)) == 1, ( + f"PACKSE_VERSION not found in {TESTS_COMMON_MOD_RS}" + ) test_common = url_matcher.sub(build_vendor_links_url, test_common) TESTS_COMMON_MOD_RS.write_text(test_common) diff --git a/scripts/scenarios/requirements.txt b/scripts/scenarios/requirements.txt index d6c9203d0956..733e9ea67a49 100644 --- a/scripts/scenarios/requirements.txt +++ b/scripts/scenarios/requirements.txt @@ -10,7 +10,7 @@ msgspec==0.18.6 # via packse packaging==24.2 # via hatchling -packse==0.3.42 +packse==0.3.44 # via -r scripts/scenarios/requirements.in pathspec==0.12.1 # via hatchling diff --git a/scripts/scenarios/templates/install.mustache b/scripts/scenarios/templates/install.mustache index 48f86e7ffe1a..be425f92948c 100644 --- a/scripts/scenarios/templates/install.mustache +++ b/scripts/scenarios/templates/install.mustache @@ -88,6 +88,9 @@ fn {{module_name}}() { .arg("--no-binary") .arg("{{.}}") {{/resolver_options.no_binary}} + {{#resolver_options.python_platform}} + .arg("--python-platform={{.}}") + {{/resolver_options.python_platform}} {{#root.requires}} .arg("{{requirement}}") {{/root.requires}}, @r###" diff --git a/uv.schema.json b/uv.schema.json index de5b6eaae721..5304a2a6fe52 100644 --- a/uv.schema.json +++ b/uv.schema.json @@ -443,6 +443,17 @@ "$ref": "#/definitions/PackageName" } }, + "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`.", + "anyOf": [ + { + "$ref": "#/definitions/RequiredVersion" + }, + { + "type": "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`).", "anyOf": [ @@ -949,7 +960,7 @@ ] }, "exclude-newer": { - "description": "Limit candidate packages to those that were uploaded prior to the given date.\n\nAccepts both [RFC 3339](https://www.rfc-editor.org/rfc/rfc3339.html) timestamps (e.g., `2006-12-02T02:07:43Z`) and local dates in the same format (e.g., `2006-12-02`) in your system's configured time zone.", + "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.", "anyOf": [ { "$ref": "#/definitions/ExcludeNewer" @@ -1409,6 +1420,10 @@ "type": "string", "pattern": "^3\\.\\d+(\\.\\d+)?$" }, + "RequiredVersion": { + "description": "A version specifier, e.g. `>=0.5.0` or `==0.5.0`.", + "type": "string" + }, "Requirement": { "description": "A PEP 508 dependency specifier, e.g., `ruff >= 0.6.0`", "type": "string"