diff --git a/.github/renovate.json5 b/.github/renovate.json5 index dd64e6620..73b666158 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -29,6 +29,18 @@ matchManagers: ["pre-commit"], description: "Weekly update of pre-commit dependencies", }, + { + groupName: "Rust dev-dependencies", + matchManagers: ["cargo"], + matchDepTypes: ["devDependencies"], + description: "Weekly update of Rust development dependencies", + }, + { + groupName: "pyo3", + matchManagers: ["cargo"], + matchPackagePatterns: ["pyo3"], + description: "Weekly update of pyo3 dependencies", + }, ], vulnerabilityAlerts: { commitMessageSuffix: "", diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 760b71e31..c4e0e628c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,33 +72,23 @@ jobs: name: "cargo test | ${{ matrix.os }}" steps: - uses: actions/checkout@v4 - - - if: ${{ matrix.os == 'macos' }} - name: "Install bootstrap dependencies" - run: brew install coreutils - - - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - - name: "Install required Python versions" - run: | - python -m pip install "zstandard==0.22.0" - python scripts/bootstrap/install.py - - name: "Install Rust toolchain" run: rustup show - - name: "Install cargo nextest" - uses: taiki-e/install-action@v2 - with: - tool: cargo-nextest - - if: ${{ matrix.os != 'windows' }} uses: rui314/setup-mold@v1 - uses: Swatinem/rust-cache@v2 + - name: "Install required Python versions" + run: | + cargo run -p uv-dev -- fetch-python + + - name: "Install cargo nextest" + uses: taiki-e/install-action@v2 + with: + tool: cargo-nextest + - name: "Cargo test" run: | cargo nextest run --workspace --status-level skip --failure-output immediate-final --no-fail-fast -j 12 --final-status-level slow @@ -224,6 +214,42 @@ jobs: path: ./target/debug/uv.exe retention-days: 1 + ecosystem-test: + needs: build-binary-linux + name: "ecosystem test | ${{ matrix.repo }}" + runs-on: ubuntu-latest + strategy: + matrix: + include: + - repo: "prefecthq/prefect" + command: "uv pip install -e '.[dev]'" + python: "3.9" + - repo: "pallets/flask" + command: "uv pip install -r requirements/dev.txt" + python: "3.12" + fail-fast: false + steps: + - uses: actions/checkout@v4 + with: + repository: ${{ matrix.repo }} + + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + + - name: "Download binary" + uses: actions/download-artifact@v4 + with: + name: uv-linux-${{ github.sha }} + + - name: "Prepare binary" + run: chmod +x ./uv + + - name: "Test" + run: | + ./uv venv + ./${{ matrix.command }} + cache-test-ubuntu: needs: build-binary-linux name: "check cache | ubuntu" @@ -274,12 +300,12 @@ jobs: needs: build-binary-linux name: "check system | python on debian" runs-on: ubuntu-latest - container: debian:bullseye + container: debian:bookworm steps: - uses: actions/checkout@v4 - name: "Install Python" - run: apt-get update && apt-get install -y python3.9 python3-pip python3.9-venv + run: apt-get update && apt-get install -y python3.11 python3-pip python3.11-venv - name: "Download binary" uses: actions/download-artifact@v4 @@ -290,16 +316,16 @@ jobs: run: chmod +x ./uv - name: "Print Python path" - run: echo $(which python3.9) + run: echo $(which python3.11) - name: "Validate global Python install" - run: python3.9 scripts/check_system_python.py --uv ./uv + run: python3.11 scripts/check_system_python.py --uv ./uv --externally-managed system-test-fedora: needs: build-binary-linux name: "check system | python on fedora" runs-on: ubuntu-latest - container: fedora:39 + container: fedora:41 steps: - uses: actions/checkout@v4 @@ -346,6 +372,8 @@ jobs: run: python scripts/check_system_python.py --uv ./uv system-test-centos: + # https://github.com/astral-sh/uv/issues/2915 + if: false needs: build-binary-linux name: "check system | python on centos" runs-on: ubuntu-latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d965279d2..289b7a1c4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -65,7 +65,7 @@ jobs: # we specify bash to get pipefail; it guards against the `curl` command # failing. otherwise `sh` won't catch that `curl` returned non-0 shell: bash - run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.13.0-prerelease.3/cargo-dist-installer.sh | sh" + run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.13.0/cargo-dist-installer.sh | sh" # sure would be cool if github gave us proper conditionals... # so here's a doubly-nested ternary-via-truthiness to try to provide the best possible # functionality based on whether this is a pull_request, and whether it's from a fork. @@ -107,7 +107,7 @@ jobs: submodules: recursive - name: Install cargo-dist shell: bash - run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.13.0-prerelease.3/cargo-dist-installer.sh | sh" + run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.13.0/cargo-dist-installer.sh | sh" # Get all the local artifacts for the global tasks to use (for e.g. checksums) - name: Fetch local artifacts uses: actions/download-artifact@v4 @@ -151,7 +151,7 @@ jobs: with: submodules: recursive - name: Install cargo-dist - run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.13.0-prerelease.3/cargo-dist-installer.sh | sh" + run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.13.0/cargo-dist-installer.sh | sh" # Fetch artifacts from scratch-storage - name: Fetch artifacts uses: actions/download-artifact@v4 diff --git a/.gitignore b/.gitignore index b195ac35c..1a106a64e 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,7 @@ target/ target-alpine/ # Bootstrapped Python versions -bin/ +/bin/ # These are backup files generated by rustfmt **/*.rs.bk diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index abd3eb80d..21d863457 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.18.2 + rev: v1.20.4 hooks: - id: typos @@ -32,7 +32,7 @@ repos: types_or: [yaml, json5] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.2.1 + rev: v0.3.5 hooks: - id: ruff-format - id: ruff diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d902a336..eba387b78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,54 @@ # Changelog +## 0.1.31 + +### Bug fixes + +- Ignore direct URL distributions in prefetcher ([#2943](https://github.com/astral-sh/uv/pull/2943)) + +## 0.1.30 + +### Enhancements + +- Show resolution diagnostics after `pip install` ([#2829](https://github.com/astral-sh/uv/pull/2829)) + +### Performance + +- Speed up cold-cache `urllib3`-`boto3`-`botocore` performance with batched prefetching ([#2452](https://github.com/astral-sh/uv/pull/2452)) + +### Bug fixes + +- Backtrack on distributions with invalid metadata ([#2834](https://github.com/astral-sh/uv/pull/2834)) +- Include LICENSE files in source distribution ([#2855](https://github.com/astral-sh/uv/pull/2855)) +- Respect `--no-build` and `--no-binary` in `--find-links` ([#2826](https://github.com/astral-sh/uv/pull/2826)) +- Respect cached local `--find-links` in install plan ([#2907](https://github.com/astral-sh/uv/pull/2907)) +- Avoid panic with multiple confirmation handlers ([#2903](https://github.com/astral-sh/uv/pull/2903)) +- Use scheme parsing to determine absolute vs. relative URLs ([#2904](https://github.com/astral-sh/uv/pull/2904)) +- Remove additional 'because' in resolution failure messages ([#2849](https://github.com/astral-sh/uv/pull/2849)) +- Use `miette` when printing `pip sync` resolution failures ([#2848](https://github.com/astral-sh/uv/pull/2848)) + +## 0.1.29 + +### Enhancements + +- Allow conflicting Git URLs that refer to the same commit SHA ([#2769](https://github.com/astral-sh/uv/pull/2769)) +- Allow package lookups across multiple indexes via explicit opt-in (`--index-strategy unsafe-any-match`) ([#2815](https://github.com/astral-sh/uv/pull/2815)) +- Allow no-op `--no-compile` flag on CLI ([#2816](https://github.com/astral-sh/uv/pull/2816)) +- Upgrade `rs-async-zip` to support data descriptors ([#2809](https://github.com/astral-sh/uv/pull/2809)) + +### Bug fixes + +- Avoid unused extras check in `pip install` for source trees ([#2811](https://github.com/astral-sh/uv/pull/2811)) +- Deduplicate editables during install commands ([#2820](https://github.com/astral-sh/uv/pull/2820)) +- Fix windows lock race: lock exclusive after all try lock errors ([#2800](https://github.com/astral-sh/uv/pull/2800)) +- Preserve `.git` suffixes and casing in Git dependencies ([#2789](https://github.com/astral-sh/uv/pull/2789)) +- Respect Git tags and branches that look like short commits ([#2795](https://github.com/astral-sh/uv/pull/2795)) +- Enable virtualenv creation on Windows with cpython-x86 ([#2707](https://github.com/astral-sh/uv/pull/2707)) + +### Documentation + +- Document that uv is safe to run concurrently ([#2818](https://github.com/astral-sh/uv/pull/2818)) + ## 0.1.28 ### Enhancements diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d620e22e5..9341a3972 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,12 +22,6 @@ CMake may be installed with Homebrew: brew install cmake ``` -The Python bootstrapping script requires `coreutils` and `zstd`; we recommend installing them with Homebrew: - -```shell -brew install coreutils zstd -``` - See the [Python](#python) section for instructions on installing the Python versions. ### Windows @@ -45,13 +39,13 @@ Testing uv requires multiple specific Python versions. You can install them into `/bin` via our bootstrapping script: ```shell -pipx run scripts/bootstrap/install.py +cargo run -p uv-dev -- fetch-python ``` -Alternatively, you can install `zstandard` from PyPI, then run: +You may need to add the versions to your `PATH`: ```shell -python3.12 scripts/bootstrap/install.py +source .env ``` You can configure the bootstrapping directory with `UV_BOOTSTRAP_DIR`. diff --git a/Cargo.lock b/Cargo.lock index b8cba8cb4..6a58eb025 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,9 +30,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -123,15 +123,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" [[package]] name = "arc-swap" -version = "1.7.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b3d0060af21e8d11a926981cc00c6c1541aa91dd64b9f881985c3da1094425f" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" [[package]] name = "arrayref" @@ -200,9 +200,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.6" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a116f46a969224200a0a97f29cfd4c50e7534e4b4826bd23ea2c3c533039c82c" +checksum = "07dbbf24db18d609b1462965249abdf49129ccad073ec257da372adc83259c60" dependencies = [ "brotli", "flate2", @@ -211,6 +211,8 @@ dependencies = [ "memchr", "pin-project-lite", "tokio", + "zstd", + "zstd-safe", ] [[package]] @@ -221,14 +223,14 @@ checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] name = "async_http_range_reader" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8eeab30c68da4dc2c51f3afc4327ab06fe0f3f028ca423f7ca398c7ed8c5e7" +checksum = "8561e6613f8361df8bed11c0eef05b98384643bc81f6b753eec7c1d91f097509" dependencies = [ "bisection", "futures", @@ -246,8 +248,8 @@ dependencies = [ [[package]] name = "async_zip" -version = "0.0.16" -source = "git+https://github.com/charliermarsh/rs-async-zip?rev=d76801da0943de985254fc6255c0e476b57c5836#d76801da0943de985254fc6255c0e476b57c5836" +version = "0.0.17" +source = "git+https://github.com/charliermarsh/rs-async-zip?rev=1dcb40cfe1bf5325a6fd4bfcf9894db40241f585#1dcb40cfe1bf5325a6fd4bfcf9894db40241f585" dependencies = [ "async-compression", "crc32fast", @@ -260,15 +262,15 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "axoasset" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dce2f189800bafe8322ef3a4d361ee7373bfc2f8fe052afda404230166dc45f" +checksum = "5e05853b0d9abfab8e7532cad0d07ec396dd95c1a81926b49ab3cfa121a9d8d6" dependencies = [ "camino", "image", @@ -293,13 +295,25 @@ dependencies = [ ] [[package]] -name = "axoupdater" -version = "0.3.3" +name = "axotag" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e18b628756d7e73bcd3b7330e5834a44f841b115e92bad8563c3dc616a64131" +checksum = "d888fac0b73e64cbdf36a743fc5a25af5ae955c357535cb420b389bf1e1a6c54" +dependencies = [ + "miette", + "semver", + "thiserror", +] + +[[package]] +name = "axoupdater" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2245976a147040f18b5b02ecb72b8705efa60f75225e32f51dd717cd5f9d79d1" dependencies = [ "axoasset", "axoprocess", + "axotag", "camino", "homedir", "miette", @@ -326,9 +340,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", @@ -360,6 +374,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" + [[package]] name = "bench" version = "0.0.0" @@ -383,9 +403,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "bitvec" @@ -410,9 +430,9 @@ dependencies = [ [[package]] name = "brotli" -version = "3.4.0" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f" +checksum = "125740193d7fee5cc63ab9e16c2fdc4e07c74ba755cc53b327d6ea029e9fc569" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -421,9 +441,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "2.5.1" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" +checksum = "65622a320492e09b5e0ac436b14c54ff68199bac392d0e89a6832c4518eea525" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -442,9 +462,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.15.4" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytecheck" @@ -470,9 +490,9 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.14.3" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f" +checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" [[package]] name = "byteorder" @@ -482,9 +502,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cache-key" @@ -544,9 +564,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.90" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" +checksum = "2678b2e3449475e95b0aa6f9b506a28e61b3dc8996592b983695e8ebb58a8b41" dependencies = [ "jobserver", "libc", @@ -640,9 +660,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.1" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "885e4d7d5af40bfb99ae6f9433e292feac98d452dcb3ec3d25dfe7552b77da8c" +checksum = "dd79504325bf38b10165b02e89b4347300f855f273c4cb30c4a3209e6583275e" dependencies = [ "clap", ] @@ -688,7 +708,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -1045,9 +1065,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] @@ -1079,9 +1099,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b5fb89194fa3cad959b833185b3063ba881dbfc7030680b314250779fb4cc91" +checksum = "6d9944b8ca13534cdfb2800775f8dd4902ff3fc75a50101466decadfdf322a24" dependencies = [ "concurrent-queue", "parking", @@ -1090,9 +1110,9 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291" +checksum = "332f51cb23d20b0de8458b86580878211da09bcd4503cb579c225b3d124cabb3" dependencies = [ "event-listener", "pin-project-lite", @@ -1100,9 +1120,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" [[package]] name = "fdeflate" @@ -1263,9 +1283,9 @@ checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-lite" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ "fastrand", "futures-core", @@ -1282,7 +1302,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -1327,9 +1347,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ "cfg-if", "js-sys", @@ -1356,11 +1376,11 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "git2" -version = "0.18.2" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b3ba52851e73b46a4c3df1d89343741112003f0f6f13beb0dfac9e457c3fdcd" +checksum = "232e6a7bfe35766bf715e55a88b39a700596c0ccfd88cd3680b4cdb40d66ef70" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "libc", "libgit2-sys", "log", @@ -1385,7 +1405,7 @@ dependencies = [ "bstr", "log", "regex-automata 0.4.6", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", ] [[package]] @@ -1394,42 +1414,23 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "ignore", "walkdir", ] [[package]] name = "h2" -version = "0.3.24" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +checksum = "816ec7294445779408f36fe57bc5b7fc1cf59664059096c65f905c1c61f58069" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", - "http 0.2.12", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "h2" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31d030e59af851932b72ceebadf4a2b5986dba4c3b99dd2493f8273a0f151943" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 1.1.0", + "http", "indexmap", "slab", "tokio", @@ -1439,9 +1440,9 @@ dependencies = [ [[package]] name = "half" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5eceaaeec696539ddaf7b333340f1af35a5aa87ae3e4f3ead0532f72affab2e" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" dependencies = [ "cfg-if", "crunchy", @@ -1527,17 +1528,6 @@ dependencies = [ "utf8-width", ] -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - [[package]] name = "http" version = "1.1.0" @@ -1549,17 +1539,6 @@ dependencies = [ "itoa", ] -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", -] - [[package]] name = "http-body" version = "1.0.0" @@ -1567,7 +1546,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" dependencies = [ "bytes", - "http 1.1.0", + "http", ] [[package]] @@ -1578,8 +1557,8 @@ checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" dependencies = [ "bytes", "futures-core", - "http 1.1.0", - "http-body 1.0.0", + "http", + "http-body", "pin-project-lite", ] @@ -1601,30 +1580,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" -[[package]] -name = "hyper" -version = "0.14.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2 0.3.24", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - [[package]] name = "hyper" version = "1.2.0" @@ -1634,9 +1589,9 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.2", - "http 1.1.0", - "http-body 1.0.0", + "h2", + "http", + "http-body", "httparse", "httpdate", "itoa", @@ -1648,16 +1603,19 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.24.2" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" dependencies = [ "futures-util", - "http 0.2.12", - "hyper 0.14.28", + "http", + "hyper", + "hyper-util", "rustls", + "rustls-pki-types", "tokio", "tokio-rustls", + "tower-service", ] [[package]] @@ -1667,13 +1625,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" dependencies = [ "bytes", + "futures-channel", "futures-util", - "http 1.1.0", - "http-body 1.0.0", - "hyper 1.2.0", + "http", + "http-body", + "hyper", "pin-project-lite", "socket2", "tokio", + "tower", + "tower-service", + "tracing", ] [[package]] @@ -1745,9 +1707,9 @@ checksum = "b72ad49b554c1728b1e83254a1b1565aea4161e28dabbfa171fc15fe62299caf" [[package]] name = "indexmap" -version = "2.2.5" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown 0.14.3", @@ -1770,23 +1732,24 @@ dependencies = [ [[package]] name = "indoc" -version = "2.0.4" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" [[package]] name = "insta" -version = "1.36.1" +version = "1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a7c22c4d34ef4788c351e971c52bfdfe7ea2766f8c5466bc175dd46e52ac22e" +checksum = "3eab73f58e59ca6526037208f0e98851159ec1633cf17b6cd2e1f2c3fd5d53cc" dependencies = [ "console", "lazy_static", "linked-hash-map", + "pest", + "pest_derive", "regex", "serde", "similar", - "yaml-rust", ] [[package]] @@ -1877,9 +1840,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" @@ -1971,13 +1934,12 @@ dependencies = [ [[package]] name = "libredox" -version = "0.0.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "libc", - "redox_syscall 0.4.1", ] [[package]] @@ -2006,9 +1968,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.15" +version = "1.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037731f5d3aaa87a5675e895b63ddff1a87624bc29f77004ea829809654e48f6" +checksum = "5e143b5e666b2695d28f6bca6497720813f699c9602dd7f5cac91008b8ada7f9" dependencies = [ "cc", "libc", @@ -2065,10 +2027,20 @@ dependencies = [ ] [[package]] -name = "memchr" -version = "2.7.1" +name = "md-5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "memmap2" @@ -2099,9 +2071,9 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] @@ -2134,7 +2106,7 @@ checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -2152,16 +2124,6 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" -[[package]] -name = "mime_guess" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" -dependencies = [ - "mime", - "unicase", -] - [[package]] name = "miniz_oxide" version = "0.7.2" @@ -2220,7 +2182,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "cfg-if", "cfg_aliases", "libc", @@ -2322,9 +2284,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.101" +version = "0.9.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" dependencies = [ "cc", "libc", @@ -2339,16 +2301,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" -[[package]] -name = "os_info" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "006e42d5b888366f1880eda20371fedde764ed2213dc8496f49622fa0c99cd5e" -dependencies = [ - "log", - "winapi", -] - [[package]] name = "overload" version = "0.1.1" @@ -2470,6 +2422,51 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pest" +version = "2.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f8023d0fb78c8e03784ea1c7f3fa36e68a723138990b8d5a47d916b651e7a8" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0d24f72393fd16ab6ac5738bc33cdb6a9aa73f8b902e8fe29cf4e67d7dd1026" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc17e2a6c7d0a492f0158d7a4bd66cc17280308bbaff78d5bef566dca35ab80" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "pest_meta" +version = "2.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "934cd7631c050f4674352a6e835d5f6711ffbfb9345c2fc0107155ac495ae293" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + [[package]] name = "petgraph" version = "0.6.4" @@ -2503,14 +2500,14 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -2669,7 +2666,7 @@ dependencies = [ "cfg-if", "indoc", "libc", - "memoffset 0.9.0", + "memoffset 0.9.1", "parking_lot 0.12.1", "portable-atomic", "pyo3-build-config", @@ -2718,7 +2715,7 @@ dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -2731,7 +2728,7 @@ dependencies = [ "proc-macro2", "pyo3-build-config", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -2756,9 +2753,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -2807,9 +2804,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -2860,9 +2857,9 @@ dependencies = [ [[package]] name = "redox_users" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ "getrandom", "libredox", @@ -2882,14 +2879,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", "regex-automata 0.4.6", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", ] [[package]] @@ -2909,7 +2906,7 @@ checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", ] [[package]] @@ -2920,9 +2917,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "rend" @@ -2955,45 +2952,45 @@ dependencies = [ "unscanny", "url", "uv-client", + "uv-configuration", "uv-fs", "uv-normalize", - "uv-types", "uv-warnings", ] [[package]] name = "reqwest" -version = "0.11.26" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bf93c4af7a8bb7d879d51cebe797356ff10ae8516ace542b5182d9dcac10b2" +checksum = "3e6cc1e89e689536eb5aeede61520e874df5a4707df811cd5da4aa5fbb2aae19" dependencies = [ "async-compression", - "base64 0.21.7", + "base64 0.22.0", "bytes", - "encoding_rs", + "futures-channel", "futures-core", "futures-util", - "h2 0.3.24", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.28", + "http", + "http-body", + "http-body-util", + "hyper", "hyper-rustls", + "hyper-util", "ipnet", "js-sys", "log", "mime", - "mime_guess", "once_cell", "percent-encoding", "pin-project-lite", "rustls", "rustls-native-certs", "rustls-pemfile", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", - "system-configuration", "tokio", "tokio-rustls", "tokio-util", @@ -3009,37 +3006,36 @@ dependencies = [ [[package]] name = "reqwest-middleware" -version = "0.2.5" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a735987236a8e238bf0296c7e351b999c188ccc11477f311b82b55c93984216" +checksum = "0209efb52486ad88136190094ee214759ef7507068b27992256ed6610eb71a01" dependencies = [ "anyhow", "async-trait", - "http 0.2.12", + "http", "reqwest", "serde", - "task-local-extensions", "thiserror", + "tower-service", ] [[package]] name = "reqwest-retry" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9af20b65c2ee9746cc575acb6bd28a05ffc0d15e25c992a8f4462d8686aacb4f" +checksum = "40f342894422862af74c50e1e9601cf0931accc9c6981e5eb413c46603b616b5" dependencies = [ "anyhow", "async-trait", "chrono", "futures", "getrandom", - "http 0.2.12", - "hyper 0.14.28", + "http", + "hyper", "parking_lot 0.11.2", "reqwest", "reqwest-middleware", "retry-policies", - "task-local-extensions", "tokio", "tracing", "wasm-timer", @@ -3066,9 +3062,9 @@ dependencies = [ [[package]] name = "retry-policies" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17dd00bff1d737c40dbcd47d4375281bf4c17933f9eef0a185fc7bacca23ecbd" +checksum = "493b4243e32d6eedd29f9a398896e35c6943a123b55eec97dcaee98310d25810" dependencies = [ "anyhow", "chrono", @@ -3201,11 +3197,11 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.38.31" +version = "0.38.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", @@ -3214,44 +3210,55 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.10" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +checksum = "99008d7ad0bbbea527ec27bddbc0e432c5b87d8175178cee68d2eec9c4a1813c" dependencies = [ "log", "ring", + "rustls-pki-types", "rustls-webpki", - "sct", + "subtle", + "zeroize", ] [[package]] name = "rustls-native-certs" -version = "0.6.3" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" dependencies = [ "openssl-probe", "rustls-pemfile", + "rustls-pki-types", "schannel", "security-framework", ] [[package]] name = "rustls-pemfile" -version = "1.0.4" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" dependencies = [ - "base64 0.21.7", + "base64 0.22.0", + "rustls-pki-types", ] [[package]] -name = "rustls-webpki" -version = "0.101.7" +name = "rustls-pki-types" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" + +[[package]] +name = "rustls-webpki" +version = "0.102.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" dependencies = [ "ring", + "rustls-pki-types", "untrusted", ] @@ -3301,16 +3308,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "seahash" version = "4.1.0" @@ -3319,9 +3316,9 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "security-framework" -version = "2.9.2" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -3332,14 +3329,20 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" dependencies = [ "core-foundation-sys", "libc", ] +[[package]] +name = "semver" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" + [[package]] name = "serde" version = "1.0.197" @@ -3357,14 +3360,14 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" dependencies = [ "itoa", "ryu", @@ -3452,9 +3455,9 @@ checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" [[package]] name = "similar" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32fea41aca09ee824cc9724996433064c89f7777e60762749a4170a14abbfa21" +checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" [[package]] name = "simplecss" @@ -3482,9 +3485,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smawk" @@ -3519,9 +3522,9 @@ dependencies = [ [[package]] name = "strsim" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" @@ -3599,9 +3602,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.52" +version = "2.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" dependencies = [ "proc-macro2", "quote", @@ -3624,27 +3627,6 @@ dependencies = [ "libc", ] -[[package]] -name = "system-configuration" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "tagu" version = "0.1.6" @@ -3663,20 +3645,11 @@ version = "0.12.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" -[[package]] -name = "task-local-extensions" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba323866e5d033818e3240feeb9f7db2c4296674e4d9e16b97b7bf8f490434e8" -dependencies = [ - "pin-utils", -] - [[package]] name = "temp-dir" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd16aa9ffe15fe021c6ee3766772132c6e98dfa395a167e16864f61a9cfb71d6" +checksum = "1f227968ec00f0e5322f9b8173c7a0cbcff6181a0a5b28e9892491c286277231" [[package]] name = "tempfile" @@ -3724,7 +3697,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -3735,7 +3708,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", "test-case-core", ] @@ -3776,7 +3749,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -3867,9 +3840,9 @@ checksum = "b130bd8a58c163224b44e217b4239ca7b927d82bf6cc2fea1fc561d15056e3f7" [[package]] name = "tokio" -version = "1.36.0" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", @@ -3892,24 +3865,25 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] name = "tokio-rustls" -version = "0.24.1" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" dependencies = [ "rustls", + "rustls-pki-types", "tokio", ] [[package]] name = "tokio-stream" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" dependencies = [ "futures-core", "pin-project-lite", @@ -3970,9 +3944,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.8" +version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c12219811e0c1ba077867254e5ad62ee2c9c190b0d957110750ac0cda1ae96cd" +checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4" dependencies = [ "indexmap", "serde", @@ -3981,6 +3955,28 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + [[package]] name = "tower-service" version = "0.3.2" @@ -4007,7 +4003,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -4123,13 +4119,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] -name = "unicase" -version = "2.7.0" +name = "ucd-trie" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" [[package]] name = "unicode-bidi" @@ -4283,14 +4276,14 @@ checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" [[package]] name = "uv" -version = "0.1.28" +version = "0.1.31" dependencies = [ "anstream", "anyhow", "assert_cmd", "assert_fs", "axoupdater", - "base64 0.21.7", + "base64 0.22.0", "byteorder", "chrono", "clap", @@ -4331,6 +4324,7 @@ dependencies = [ "uv-auth", "uv-cache", "uv-client", + "uv-configuration", "uv-dispatch", "uv-distribution", "uv-fs", @@ -4339,6 +4333,7 @@ dependencies = [ "uv-normalize", "uv-requirements", "uv-resolver", + "uv-toolchain", "uv-types", "uv-virtualenv", "uv-warnings", @@ -4349,13 +4344,13 @@ name = "uv-auth" version = "0.0.1" dependencies = [ "async-trait", - "base64 0.21.7", + "base64 0.22.0", "clap", + "http", "once_cell", "reqwest", "reqwest-middleware", "rust-netrc", - "task-local-extensions", "tempfile", "thiserror", "tokio", @@ -4387,6 +4382,7 @@ dependencies = [ "tokio", "toml", "tracing", + "uv-configuration", "uv-fs", "uv-interpreter", "uv-types", @@ -4431,11 +4427,12 @@ dependencies = [ "fs-err", "futures", "html-escape", - "http 0.2.12", - "hyper 0.14.28", + "http", + "http-body-util", + "hyper", + "hyper-util", "insta", "install-wheel-rs", - "os_info", "pep440_rs", "pep508_rs", "platform-tags", @@ -4445,13 +4442,9 @@ dependencies = [ "reqwest-retry", "rkyv", "rmp-serde", - "rustc-hash", - "rustls", - "rustls-native-certs", "serde", "serde_json", "sys-info", - "task-local-extensions", "tempfile", "thiserror", "tl", @@ -4462,11 +4455,25 @@ dependencies = [ "urlencoding", "uv-auth", "uv-cache", + "uv-configuration", "uv-fs", "uv-normalize", "uv-version", "uv-warnings", - "webpki-roots", +] + +[[package]] +name = "uv-configuration" +version = "0.0.1" +dependencies = [ + "anyhow", + "clap", + "itertools 0.12.1", + "pep508_rs", + "rustc-hash", + "serde", + "serde_json", + "uv-normalize", ] [[package]] @@ -4503,11 +4510,14 @@ dependencies = [ "uv-build", "uv-cache", "uv-client", + "uv-configuration", "uv-dispatch", + "uv-fs", "uv-installer", "uv-interpreter", "uv-normalize", "uv-resolver", + "uv-toolchain", "uv-types", "walkdir", ] @@ -4526,9 +4536,9 @@ dependencies = [ "uv-build", "uv-cache", "uv-client", + "uv-configuration", "uv-installer", "uv-interpreter", - "uv-requirements", "uv-resolver", "uv-types", ] @@ -4544,6 +4554,7 @@ dependencies = [ "fs-err", "futures", "install-wheel-rs", + "md-5", "nanoid", "once_cell", "pep440_rs", @@ -4555,6 +4566,7 @@ dependencies = [ "rmp-serde", "rustc-hash", "serde", + "sha2", "tempfile", "thiserror", "tokio", @@ -4563,6 +4575,7 @@ dependencies = [ "url", "uv-cache", "uv-client", + "uv-configuration", "uv-extract", "uv-fs", "uv-git", @@ -4579,8 +4592,11 @@ dependencies = [ "async_zip", "fs-err", "futures", + "md-5", + "pypi-types", "rayon", "rustc-hash", + "sha2", "thiserror", "tokio", "tokio-tar", @@ -4612,7 +4628,7 @@ name = "uv-git" version = "0.0.1" dependencies = [ "anyhow", - "base64 0.21.7", + "base64 0.22.0", "cache-key", "cargo-util", "fs-err", @@ -4645,6 +4661,7 @@ dependencies = [ "pypi-types", "rayon", "requirements-txt", + "rmp-serde", "rustc-hash", "serde", "tempfile", @@ -4655,6 +4672,7 @@ dependencies = [ "url", "uv-cache", "uv-client", + "uv-configuration", "uv-distribution", "uv-extract", "uv-fs", @@ -4692,6 +4710,7 @@ dependencies = [ "tracing", "uv-cache", "uv-fs", + "uv-toolchain", "which", "winapi", ] @@ -4728,6 +4747,7 @@ dependencies = [ "tracing", "url", "uv-client", + "uv-configuration", "uv-distribution", "uv-fs", "uv-normalize", @@ -4753,6 +4773,7 @@ dependencies = [ "futures", "indexmap", "insta", + "install-wheel-rs", "itertools 0.12.1", "once-map", "once_cell", @@ -4766,6 +4787,7 @@ dependencies = [ "requirements-txt", "rkyv", "rustc-hash", + "textwrap", "thiserror", "tokio", "tokio-stream", @@ -4773,6 +4795,7 @@ dependencies = [ "url", "uv-cache", "uv-client", + "uv-configuration", "uv-distribution", "uv-interpreter", "uv-normalize", @@ -4780,26 +4803,55 @@ dependencies = [ "uv-warnings", ] +[[package]] +name = "uv-toolchain" +version = "0.1.0" +dependencies = [ + "anyhow", + "fs-err", + "futures", + "once_cell", + "pep440_rs", + "pep508_rs", + "reqwest", + "reqwest-middleware", + "tempfile", + "thiserror", + "tokio", + "tokio-util", + "tracing", + "url", + "uv-client", + "uv-extract", + "uv-fs", +] + [[package]] name = "uv-types" version = "0.0.1" dependencies = [ "anyhow", + "clap", "distribution-types", "itertools 0.12.1", "once-map", + "pep440_rs", "pep508_rs", + "pypi-types", "rustc-hash", "serde", "serde_json", + "thiserror", + "url", "uv-cache", + "uv-configuration", "uv-interpreter", "uv-normalize", ] [[package]] name = "uv-version" -version = "0.1.28" +version = "0.1.31" [[package]] name = "uv-virtualenv" @@ -4937,7 +4989,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", "wasm-bindgen-shared", ] @@ -4971,7 +5023,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5022,9 +5074,12 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.25.4" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" +dependencies = [ + "rustls-pki-types", +] [[package]] name = "weezl" @@ -5034,22 +5089,21 @@ checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" [[package]] name = "which" -version = "6.0.0" +version = "6.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fa5e0c10bf77f44aac573e498d1a82d5fbd5e91f6fc0a99e7be4b38e85e101c" +checksum = "8211e4f58a2b2805adfbefbc07bab82958fc91e3836339b1ab7ae32465dce0d7" dependencies = [ "either", "home", - "once_cell", "rustix", - "windows-sys 0.52.0", + "winsafe", ] [[package]] name = "widestring" -version = "1.0.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" [[package]] name = "winapi" @@ -5131,7 +5185,7 @@ checksum = "12168c33176773b86799be25e2a2ba07c7aab9968b37541f1094dbd7a60c8946" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -5142,7 +5196,7 @@ checksum = "9d8dc32e0095a7eeccebd0e3f09e9509365ecb3fc6ac4d6f5f14a3f6392942d1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -5297,14 +5351,20 @@ dependencies = [ [[package]] name = "winreg" -version = "0.50.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" dependencies = [ "cfg-if", "windows-sys 0.48.0", ] +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + [[package]] name = "wiremock" version = "0.6.0" @@ -5316,9 +5376,9 @@ dependencies = [ "base64 0.21.7", "deadpool", "futures", - "http 1.1.0", + "http", "http-body-util", - "hyper 1.2.0", + "hyper", "hyper-util", "log", "once_cell", @@ -5370,13 +5430,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" [[package]] -name = "yaml-rust" -version = "0.4.5" +name = "zeroize" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" [[package]] name = "zip" @@ -5389,3 +5446,31 @@ dependencies = [ "crossbeam-utils", "flate2", ] + +[[package]] +name = "zstd" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.10+zstd.1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index 1b05003d5..b253d8a36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,21 +44,23 @@ uv-normalize = { path = "crates/uv-normalize" } uv-requirements = { path = "crates/uv-requirements" } uv-resolver = { path = "crates/uv-resolver" } uv-types = { path = "crates/uv-types" } +uv-configuration = { path = "crates/uv-configuration" } uv-trampoline = { path = "crates/uv-trampoline" } uv-version = { path = "crates/uv-version" } uv-virtualenv = { path = "crates/uv-virtualenv" } uv-warnings = { path = "crates/uv-warnings" } +uv-toolchain = { path = "crates/uv-toolchain" } anstream = { version = "0.6.13" } anyhow = { version = "1.0.80" } async-channel = { version = "2.2.0" } async-compression = { version = "0.4.6" } async-trait = { version = "0.1.78" } -async_http_range_reader = { version = "0.7.0" } -async_zip = { git = "https://github.com/charliermarsh/rs-async-zip", rev = "d76801da0943de985254fc6255c0e476b57c5836", features = ["deflate"] } -axoupdater = { version = "0.3.1", default-features = false } +async_http_range_reader = { version = "0.7.1" } +async_zip = { git = "https://github.com/charliermarsh/rs-async-zip", rev = "1dcb40cfe1bf5325a6fd4bfcf9894db40241f585", features = ["deflate"] } +axoupdater = { version = "0.4.0", default-features = false } backoff = { version = "0.4.0" } -base64 = { version = "0.21.7" } +base64 = { version = "0.22.0" } cachedir = { version = "0.3.1" } cargo-util = { version = "0.2.8" } chrono = { version = "0.4.31" } @@ -85,13 +87,14 @@ hex = { version = "0.4.3" } hmac = { version = "0.12.1" } home = { version = "0.5.9" } html-escape = { version = "0.2.13" } -http = { version = "0.2.12" } +http = { version = "1.1.0" } indexmap = { version = "2.2.5" } indicatif = { version = "0.17.7" } indoc = { version = "2.0.4" } itertools = { version = "0.12.1" } junction = { version = "1.0.0" } mailparse = { version = "0.14.0" } +md-5 = { version = "0.10.6" } miette = { version = "7.2.0" } nanoid = { version = "0.4.0" } once_cell = { version = "1.19.0" } @@ -106,9 +109,9 @@ rand = { version = "0.8.5" } rayon = { version = "1.8.0" } reflink-copy = { version = "0.1.15" } regex = { version = "1.10.2" } -reqwest = { version = "0.11.23", default-features = false, features = ["json", "gzip", "brotli", "stream", "rustls-tls", "rustls-tls-native-roots"] } -reqwest-middleware = { version = "0.2.4" } -reqwest-retry = { version = "0.3.0" } +reqwest = { version = "0.12.3", default-features = false, features = ["json", "gzip", "brotli", "stream", "rustls-tls", "rustls-tls-native-roots"] } +reqwest-middleware = { version = "0.3.0" } +reqwest-retry = { version = "0.5.0" } rkyv = { version = "0.7.43", features = ["strict", "validation"] } rmp-serde = { version = "1.1.2" } rust-netrc = { version = "0.1.1" } @@ -120,7 +123,6 @@ serde_json = { version = "1.0.114" } sha1 = { version = "0.10.6" } sha2 = { version = "0.10.8" } sys-info = { version = "0.9.1" } -task-local-extensions = { version = "0.1.4" } tempfile = { version = "3.9.0" } textwrap = { version = "0.16.1" } thiserror = { version = "1.0.56" } @@ -133,7 +135,7 @@ toml = { version = "0.8.12" } tracing = { version = "0.1.40" } tracing-durations-export = { version = "0.2.0", features = ["plot"] } tracing-indicatif = { version = "0.3.6" } -tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json"] } +tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json", "registry"] } tracing-tree = { version = "0.3.0" } unicode-width = { version = "0.1.11" } unscanny = { version = "0.1.0" } @@ -197,7 +199,7 @@ lto = "thin" # Config for 'cargo dist' [workspace.metadata.dist] # The preferred cargo-dist version to use in CI (Cargo.toml SemVer syntax) -cargo-dist-version = "0.13.0-prerelease.3" +cargo-dist-version = "0.13.0" # CI backends to support ci = ["github"] # The installers to generate for each app @@ -207,8 +209,24 @@ windows-archive = ".zip" # The archive format to use for non-windows builds (defaults .tar.xz) unix-archive = ".tar.gz" # Target platforms to build apps for (Rust target-triple syntax) -targets = ["aarch64-unknown-linux-gnu", "x86_64-unknown-linux-gnu", "i686-unknown-linux-gnu", "aarch64-apple-darwin", "x86_64-apple-darwin", "aarch64-unknown-linux-musl", "x86_64-unknown-linux-musl", "i686-unknown-linux-musl", "x86_64-pc-windows-msvc", "i686-pc-windows-msvc", "armv7-unknown-linux-gnueabihf", "powerpc64-unknown-linux-gnu", "powerpc64le-unknown-linux-gnu", "s390x-unknown-linux-gnu", "armv7-unknown-linux-musleabihf", "arm-unknown-linux-musleabihf"] -# Whether to auto-include files like READMEs and CHANGELOGs (default true) +targets = [ + "aarch64-apple-darwin", + "aarch64-unknown-linux-gnu", + "aarch64-unknown-linux-musl", + "arm-unknown-linux-musleabihf", + "armv7-unknown-linux-gnueabihf", + "armv7-unknown-linux-musleabihf", + "i686-pc-windows-msvc", + "i686-unknown-linux-gnu", + "i686-unknown-linux-musl", + "powerpc64-unknown-linux-gnu", + "powerpc64le-unknown-linux-gnu", + "s390x-unknown-linux-gnu", + "x86_64-apple-darwin", + "x86_64-pc-windows-msvc", + "x86_64-unknown-linux-gnu", + "x86_64-unknown-linux-musl", +]# Whether to auto-include files like READMEs and CHANGELOGs (default true) auto-includes = false # Whether cargo-dist should create a Github Release or use an existing draft create-release = true diff --git a/PIP_COMPATIBILITY.md b/PIP_COMPATIBILITY.md index 413f117c6..df70d3487 100644 --- a/PIP_COMPATIBILITY.md +++ b/PIP_COMPATIBILITY.md @@ -30,7 +30,7 @@ drawbacks: target tool the user is expecting to use. 4. It prevents uv from introducing any settings or configuration that don't exist in the target tool, since otherwise `pip.conf` (or similar) would no longer be usable with `pip`. -5. It can lead user confusion, since uv would be reading settings that don't actually affect its +5. It can lead to user confusion, since uv would be reading settings that don't actually affect its behavior, and many users may _not_ expect uv to read configuration files intended for other tools. @@ -107,7 +107,7 @@ the available versions of a given package. However, uv and `pip` differ in how t packages that exist on multiple indexes. For example, imagine that a company publishes an internal version of `requests` on a private index -(`--extra-index-url`), but also allow installing packages from PyPI by default. In this case, the +(`--extra-index-url`), but also allows installing packages from PyPI by default. In this case, the private `requests` would conflict with the public [`requests`](https://pypi.org/project/requests/) on PyPI. @@ -117,7 +117,7 @@ finds a match. This means that if a package exists on multiple indexes, uv will candidate versions to those present in the first index that contains the package. `pip`, meanwhile, will combine the candidate versions from all indexes, and select the best -version from the combined set., though it makes [no guarantees around the order](https://github.com/pypa/pip/issues/5045#issuecomment-369521345) +version from the combined set, though it makes [no guarantees around the order](https://github.com/pypa/pip/issues/5045#issuecomment-369521345) in which it searches indexes, and expects that packages are unique up to name and version, even across indexes. @@ -128,6 +128,12 @@ internal package, thus causing the malicious package to be installed instead of package. See, for example, [the `torchtriton` attack](https://pytorch.org/blog/compromised-nightly-dependency/) from December 2022. +As of v0.1.29, users can opt in to `pip`-style behavior for multiple indexes via the +`--index-strategy unsafe-any-match` command-line option, or the `UV_INDEX_STRATEGY` environment +variable. When enabled, uv will search for each package across all indexes, and consider all +available versions when resolving dependencies, prioritizing the `--extra-index-url` indexes over +the default index URL. (Versions that are duplicated _across_ indexes will be ignored.) + In the future, uv will support pinning packages to dedicated indexes (see: [#171](https://github.com/astral-sh/uv/issues/171)). Additionally, [PEP 708](https://peps.python.org/pep-0708/) is a provisional standard that aims to address the "dependency confusion" issue across package registries and installers. @@ -253,14 +259,6 @@ When uv resolutions differ from `pip` in undesirable ways, it's often a sign tha are too loose, and that the user should consider tightening them. For example, in the case of `starlette` and `fastapi`, the user could require `fastapi>=0.110.0`. -## Hash-checking mode - -While uv will include hashes via `uv pip compile --generate-hashes`, it does not support -hash-checking mode, which is a feature of `pip` that allows users to verify the integrity of -downloaded packages by checking their hashes against those provided in the `requirements.txt` file. - -In the future, uv will support hash-checking mode. For more, see [#474](https://github.com/astral-sh/uv/issues/474). - ## `pip check` At present, `uv pip check` will surface the following diagnostics: diff --git a/README.md b/README.md index 8bba8c9fb..a41081cc4 100644 --- a/README.md +++ b/README.md @@ -261,8 +261,18 @@ The specifics of uv's caching semantics vary based on the nature of the dependen - **For Git dependencies**, uv caches based on the fully-resolved Git commit hash. As such, `uv pip compile` will pin Git dependencies to a specific commit hash when writing the resolved dependency set. -- **For local dependencies**, uv caches based on the last-modified time of the `setup.py` or - `pyproject.toml` file. +- **For local dependencies**, uv caches based on the last-modified time of the source archive (i.e., + the local `.whl` or `.tar.gz` file). For directories, uv caches based on the last-modified time of + the `pyproject.toml`, `setup.py`, or `setup.cfg` file. + +It's safe to run multiple `uv` commands concurrently, even against the same virtual environment. +uv's cache is designed to be thread-safe and append-only, and thus robust to multiple concurrent +readers and writers. uv applies a file-based lock to the target virtual environment when installing, +to avoid concurrent modifications across processes. + +Note that it's _not_ safe to modify the uv cache directly (e.g., `uv cache clean`) while other `uv` +commands are running, and _never_ safe to modify the cache directly (e.g., by removing a file or +directory). If you're running into caching issues, uv includes a few escape hatches: @@ -443,12 +453,22 @@ uv accepts the following command-line arguments as environment variables: `lowest-direct`, uv will install the lowest compatible versions of all direct dependencies. - `UV_PRERELEASE`: Equivalent to the `--prerelease` command-line argument. For example, if set to `allow`, uv will allow pre-release versions for all dependencies. -- `UV_SYSTEM_PYTHON`: Equivalent to the `--system` command-line argument. If set to `true`, uv +- `UV_SYSTEM_PYTHON`: Equivalent to the `--system` command-line argument. If set to `true`, uv will use the first Python interpreter found in the system `PATH`. - WARNING: `UV_SYSTEM_PYTHON=true` is intended for use in continuous integration (CI) environments and - should be used with caution, as it can modify the system Python installation. + WARNING: `UV_SYSTEM_PYTHON=true` is intended for use in continuous integration (CI) or + containerized environments and should be used with caution, as modifying the system Python + can lead to unexpected behavior. +- `UV_BREAK_SYSTEM_PACKAGES`: Equivalent to the `--break-system-packages` command-line argument. If + set to `true`, uv will allow the installation of packages that conflict with system-installed + packages. + WARNING: `UV_BREAK_SYSTEM_PACKAGES=true` is intended for use in continuous integration (CI) or + containerized environments and should be used with caution, as modifying the system Python + can lead to unexpected behavior. - `UV_NATIVE_TLS`: Equivalent to the `--native-tls` command-line argument. If set to `true`, uv will use the system's trust store instead of the bundled `webpki-roots` crate. +- `UV_INDEX_STRATEGY`: Equivalent to the `--index-strategy` command-line argument. For example, if + set to `unsafe-any-match`, uv will consider versions of a given package available across all + index URLs, rather than limiting its search to the first index URL that contains the package. In each case, the corresponding command-line argument takes precedence over an environment variable. diff --git a/crates/distribution-types/src/buildable.rs b/crates/distribution-types/src/buildable.rs index 1b105d299..42e7fe789 100644 --- a/crates/distribution-types/src/buildable.rs +++ b/crates/distribution-types/src/buildable.rs @@ -1,6 +1,7 @@ use std::borrow::Cow; use std::path::Path; +use pep440_rs::Version; use url::Url; use uv_normalize::PackageName; @@ -28,6 +29,15 @@ impl BuildableSource<'_> { } } + /// Return the [`Version`] of the source, if available. + pub fn version(&self) -> Option<&Version> { + match self { + Self::Dist(SourceDist::Registry(dist)) => Some(&dist.filename.version), + Self::Dist(_) => None, + Self::Url(_) => None, + } + } + /// Return the [`BuildableSource`] as a [`SourceDist`], if it is a distribution. pub fn as_dist(&self) -> Option<&SourceDist> { match self { diff --git a/crates/distribution-types/src/cached.rs b/crates/distribution-types/src/cached.rs index 2a0da761d..b7ddf5155 100644 --- a/crates/distribution-types/src/cached.rs +++ b/crates/distribution-types/src/cached.rs @@ -4,9 +4,11 @@ use anyhow::Result; use distribution_filename::WheelFilename; use pep508_rs::VerbatimUrl; +use pypi_types::HashDigest; use uv_normalize::PackageName; use crate::direct_url::{DirectUrl, LocalFileUrl}; +use crate::hash::Hashed; use crate::{ BuiltDist, Dist, DistributionMetadata, InstalledMetadata, InstalledVersion, Name, SourceDist, VersionOrUrl, @@ -25,6 +27,7 @@ pub enum CachedDist { pub struct CachedRegistryDist { pub filename: WheelFilename, pub path: PathBuf, + pub hashes: Vec, } #[derive(Debug, Clone)] @@ -33,45 +36,60 @@ pub struct CachedDirectUrlDist { pub url: VerbatimUrl, pub path: PathBuf, pub editable: bool, + pub hashes: Vec, } impl CachedDist { /// Initialize a [`CachedDist`] from a [`Dist`]. - pub fn from_remote(remote: Dist, filename: WheelFilename, path: PathBuf) -> Self { + pub fn from_remote( + remote: Dist, + filename: WheelFilename, + hashes: Vec, + path: PathBuf, + ) -> Self { match remote { - Dist::Built(BuiltDist::Registry(_dist)) => { - Self::Registry(CachedRegistryDist { filename, path }) - } + Dist::Built(BuiltDist::Registry(_dist)) => Self::Registry(CachedRegistryDist { + filename, + path, + hashes, + }), Dist::Built(BuiltDist::DirectUrl(dist)) => Self::Url(CachedDirectUrlDist { filename, url: dist.url, + hashes, path, editable: false, }), Dist::Built(BuiltDist::Path(dist)) => Self::Url(CachedDirectUrlDist { filename, url: dist.url, + hashes, path, editable: false, }), - Dist::Source(SourceDist::Registry(_dist)) => { - Self::Registry(CachedRegistryDist { filename, path }) - } + Dist::Source(SourceDist::Registry(_dist)) => Self::Registry(CachedRegistryDist { + filename, + path, + hashes, + }), Dist::Source(SourceDist::DirectUrl(dist)) => Self::Url(CachedDirectUrlDist { filename, url: dist.url, + hashes, path, editable: false, }), Dist::Source(SourceDist::Git(dist)) => Self::Url(CachedDirectUrlDist { filename, url: dist.url, + hashes, path, editable: false, }), Dist::Source(SourceDist::Path(dist)) => Self::Url(CachedDirectUrlDist { filename, url: dist.url, + hashes, path, editable: dist.editable, }), @@ -104,6 +122,7 @@ impl CachedDist { } } + /// Returns `true` if the distribution is editable. pub fn editable(&self) -> bool { match self { Self::Registry(_) => false, @@ -111,6 +130,7 @@ impl CachedDist { } } + /// Returns the [`WheelFilename`] of the distribution. pub fn filename(&self) -> &WheelFilename { match self { Self::Registry(dist) => &dist.filename, @@ -119,12 +139,24 @@ impl CachedDist { } } +impl Hashed for CachedRegistryDist { + fn hashes(&self) -> &[HashDigest] { + &self.hashes + } +} + impl CachedDirectUrlDist { /// Initialize a [`CachedDirectUrlDist`] from a [`WheelFilename`], [`url::Url`], and [`Path`]. - pub fn from_url(filename: WheelFilename, url: VerbatimUrl, path: PathBuf) -> Self { + pub fn from_url( + filename: WheelFilename, + url: VerbatimUrl, + hashes: Vec, + path: PathBuf, + ) -> Self { Self { filename, url, + hashes, path, editable: false, } diff --git a/crates/distribution-types/src/editable.rs b/crates/distribution-types/src/editable.rs index 6b5c397df..5894b1151 100644 --- a/crates/distribution-types/src/editable.rs +++ b/crates/distribution-types/src/editable.rs @@ -1,4 +1,6 @@ use std::borrow::Cow; +use std::collections::btree_map::Entry; +use std::collections::BTreeMap; use std::path::PathBuf; use url::Url; @@ -41,3 +43,53 @@ impl std::fmt::Display for LocalEditable { std::fmt::Display::fmt(&self.url, f) } } + +/// A collection of [`LocalEditable`]s. +#[derive(Debug, Clone)] +pub struct LocalEditables(Vec); + +impl LocalEditables { + /// Merge and dedupe a list of [`LocalEditable`]s. + /// + /// This function will deduplicate any editables that point to identical paths, merging their + /// extras. + pub fn from_editables(editables: impl Iterator) -> Self { + let mut map = BTreeMap::new(); + for editable in editables { + match map.entry(editable.path.clone()) { + Entry::Vacant(entry) => { + entry.insert(editable); + } + Entry::Occupied(mut entry) => { + let existing = entry.get_mut(); + existing.extras.extend(editable.extras); + } + } + } + Self(map.into_values().collect()) + } + + /// Return the number of editables. + pub fn len(&self) -> usize { + self.0.len() + } + + /// Return whether the editables are empty. + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Return the editables as a vector. + pub fn into_vec(self) -> Vec { + self.0 + } +} + +impl IntoIterator for LocalEditables { + type Item = LocalEditable; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} diff --git a/crates/distribution-types/src/file.rs b/crates/distribution-types/src/file.rs index dd96e63ff..ea31738ff 100644 --- a/crates/distribution-types/src/file.rs +++ b/crates/distribution-types/src/file.rs @@ -6,7 +6,8 @@ use thiserror::Error; use url::Url; use pep440_rs::{VersionSpecifiers, VersionSpecifiersParseError}; -use pypi_types::{DistInfoMetadata, Hashes, Yanked}; +use pep508_rs::split_scheme; +use pypi_types::{DistInfoMetadata, HashDigest, Yanked}; /// Error converting [`pypi_types::File`] to [`distribution_type::File`]. #[derive(Debug, Error)] @@ -24,9 +25,9 @@ pub enum FileConversionError { #[archive(check_bytes)] #[archive_attr(derive(Debug))] pub struct File { - pub dist_info_metadata: Option, + pub dist_info_metadata: bool, pub filename: String, - pub hashes: Hashes, + pub hashes: Vec, pub requires_python: Option, pub size: Option, // N.B. We don't use a chrono DateTime here because it's a little @@ -42,19 +43,24 @@ impl File { /// `TryFrom` instead of `From` to filter out files with invalid requires python version specifiers pub fn try_from(file: pypi_types::File, base: &Url) -> Result { Ok(Self { - dist_info_metadata: file.dist_info_metadata, + dist_info_metadata: file + .dist_info_metadata + .as_ref() + .is_some_and(DistInfoMetadata::is_available), filename: file.filename, - hashes: file.hashes, + hashes: file.hashes.into_digests(), requires_python: file .requires_python .transpose() .map_err(|err| FileConversionError::RequiresPython(err.line().clone(), err))?, size: file.size, upload_time_utc_ms: file.upload_time.map(|dt| dt.timestamp_millis()), - url: if file.url.contains("://") { - FileLocation::AbsoluteUrl(file.url) - } else { - FileLocation::RelativeUrl(base.to_string(), file.url) + url: { + if split_scheme(&file.url).is_some() { + FileLocation::AbsoluteUrl(file.url) + } else { + FileLocation::RelativeUrl(base.to_string(), file.url) + } }, yanked: file.yanked, }) diff --git a/crates/distribution-types/src/hash.rs b/crates/distribution-types/src/hash.rs new file mode 100644 index 000000000..553a74f55 --- /dev/null +++ b/crates/distribution-types/src/hash.rs @@ -0,0 +1,84 @@ +use pypi_types::{HashAlgorithm, HashDigest}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum HashPolicy<'a> { + /// No hash policy is specified. + None, + /// Hashes should be generated (specifically, a SHA-256 hash), but not validated. + Generate, + /// 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]), +} + +impl<'a> HashPolicy<'a> { + /// Returns `true` if the hash policy is `None`. + pub fn is_none(&self) -> bool { + 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(_)) + } + + /// Return the algorithms used in the hash policy. + pub fn algorithms(&self) -> Vec { + match self { + Self::None => vec![], + Self::Generate => vec![HashAlgorithm::Sha256], + Self::Validate(hashes) => { + let mut algorithms = hashes.iter().map(HashDigest::algorithm).collect::>(); + algorithms.sort(); + algorithms.dedup(); + algorithms + } + } + } + + /// Return the digests used in the hash policy. + pub fn digests(&self) -> &[HashDigest] { + match self { + Self::None => &[], + Self::Generate => &[], + Self::Validate(hashes) => hashes, + } + } +} + +pub trait Hashed { + /// Return the [`HashDigest`]s for the archive. + fn hashes(&self) -> &[HashDigest]; + + /// Returns `true` if the archive satisfies the given hash policy. + fn satisfies(&self, hashes: HashPolicy) -> bool { + match hashes { + HashPolicy::None => true, + HashPolicy::Generate => self + .hashes() + .iter() + .any(|hash| hash.algorithm == HashAlgorithm::Sha256), + HashPolicy::Validate(hashes) => self.hashes().iter().any(|hash| hashes.contains(hash)), + } + } + + /// Returns `true` if the archive includes a hash for at least one of the given algorithms. + fn has_digests(&self, hashes: HashPolicy) -> bool { + match hashes { + HashPolicy::None => true, + HashPolicy::Generate => self + .hashes() + .iter() + .any(|hash| hash.algorithm == HashAlgorithm::Sha256), + HashPolicy::Validate(hashes) => hashes + .iter() + .map(HashDigest::algorithm) + .any(|algorithm| self.hashes().iter().any(|hash| hash.algorithm == algorithm)), + } + } +} diff --git a/crates/distribution-types/src/id.rs b/crates/distribution-types/src/id.rs index c6e2a2a8b..989e566e7 100644 --- a/crates/distribution-types/src/id.rs +++ b/crates/distribution-types/src/id.rs @@ -1,30 +1,66 @@ use std::fmt::{Display, Formatter}; +use std::path::PathBuf; +use cache_key::{CanonicalUrl, RepositoryUrl}; use url::Url; use pep440_rs::Version; +use pypi_types::HashDigest; use uv_normalize::PackageName; -/// A unique identifier for a package (e.g., `black==23.10.0`). +/// A unique identifier for a package. A package can either be identified by a name (e.g., `black`) +/// or a URL (e.g., `git+https://github.com/psf/black`). #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum PackageId { - NameVersion(PackageName, Version), - Url(String), + /// The identifier consists of a package name. + Name(PackageName), + /// The identifier consists of a URL. + Url(CanonicalUrl), } impl PackageId { /// Create a new [`PackageId`] from a package name and version. - pub fn from_registry(name: PackageName, version: Version) -> Self { - Self::NameVersion(name, version) + pub fn from_registry(name: PackageName) -> Self { + Self::Name(name) } /// Create a new [`PackageId`] from a URL. pub fn from_url(url: &Url) -> Self { - Self::Url(cache_key::digest(&cache_key::CanonicalUrl::new(url))) + Self::Url(CanonicalUrl::new(url)) } } impl Display for PackageId { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Name(name) => write!(f, "{name}"), + Self::Url(url) => write!(f, "{url}"), + } + } +} + +/// A unique identifier for a package at a specific version (e.g., `black==23.10.0`). +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum VersionId { + /// The identifier consists of a package name and version. + NameVersion(PackageName, Version), + /// The identifier consists of a URL. + Url(CanonicalUrl), +} + +impl VersionId { + /// Create a new [`VersionId`] from a package name and version. + pub fn from_registry(name: PackageName, version: Version) -> Self { + Self::NameVersion(name, version) + } + + /// Create a new [`VersionId`] from a URL. + pub fn from_url(url: &Url) -> Self { + Self::Url(CanonicalUrl::new(url)) + } +} + +impl Display for VersionId { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { Self::NameVersion(name, version) => write!(f, "{name}-{version}"), @@ -33,33 +69,39 @@ impl Display for PackageId { } } -/// A unique identifier for a distribution (e.g., `black-23.10.0-py3-none-any.whl`). +/// A unique resource identifier for the distribution, like a SHA-256 hash of the distribution's +/// contents. +/// +/// A distribution is a specific archive of a package at a specific version. For a given package +/// version, there may be multiple distributions, e.g., source distribution, along with +/// multiple binary distributions (wheels) for different platforms. As a concrete example, +/// `black-23.10.0-py3-none-any.whl` would represent a (binary) distribution of the `black` package +/// at version `23.10.0`. +/// +/// The distribution ID is used to uniquely identify a distribution. Ideally, the distribution +/// ID should be a hash of the distribution's contents, though in practice, it's only required +/// that the ID is unique within a single invocation of the resolver (and so, e.g., a hash of +/// the URL would also be sufficient). #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct DistributionId(String); - -impl DistributionId { - pub fn new(id: impl Into) -> Self { - Self(id.into()) - } -} - -impl DistributionId { - pub fn as_str(&self) -> &str { - &self.0 - } +pub enum DistributionId { + Url(CanonicalUrl), + PathBuf(PathBuf), + Digest(HashDigest), + AbsoluteUrl(String), + RelativeUrl(String, String), } /// A unique identifier for a resource, like a URL or a Git repository. #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct ResourceId(String); - -impl ResourceId { - pub fn new(id: impl Into) -> Self { - Self(id.into()) - } +pub enum ResourceId { + Url(RepositoryUrl), + PathBuf(PathBuf), + Digest(HashDigest), + AbsoluteUrl(String), + RelativeUrl(String, String), } -impl From<&Self> for PackageId { +impl From<&Self> for VersionId { /// Required for `WaitMap::wait`. fn from(value: &Self) -> Self { value.clone() diff --git a/crates/distribution-types/src/index_url.rs b/crates/distribution-types/src/index_url.rs index edfdfea3d..3b0893fc2 100644 --- a/crates/distribution-types/src/index_url.rs +++ b/crates/distribution-types/src/index_url.rs @@ -24,6 +24,7 @@ static DEFAULT_INDEX_URL: Lazy = pub enum IndexUrl { Pypi(VerbatimUrl), Url(VerbatimUrl), + Path(VerbatimUrl), } impl IndexUrl { @@ -32,6 +33,7 @@ impl IndexUrl { match self { Self::Pypi(url) => url.raw(), Self::Url(url) => url.raw(), + Self::Path(url) => url.raw(), } } } @@ -41,6 +43,7 @@ impl Display for IndexUrl { match self { Self::Pypi(url) => Display::fmt(url, f), Self::Url(url) => Display::fmt(url, f), + Self::Path(url) => Display::fmt(url, f), } } } @@ -50,6 +53,7 @@ impl Verbatim for IndexUrl { match self { Self::Pypi(url) => url.verbatim(), Self::Url(url) => url.verbatim(), + Self::Path(url) => url.verbatim(), } } } @@ -83,6 +87,7 @@ impl From for Url { match index { IndexUrl::Pypi(url) => url.to_url(), IndexUrl::Url(url) => url.to_url(), + IndexUrl::Path(url) => url.to_url(), } } } @@ -94,6 +99,7 @@ impl Deref for IndexUrl { match &self { Self::Pypi(url) => url, Self::Url(url) => url, + Self::Path(url) => url, } } } @@ -165,15 +171,7 @@ impl Display for FlatIndexLocation { } } -/// The index locations to use for fetching packages. -/// -/// By default, uses the PyPI index. -/// -/// "pip treats all package sources equally" (), -/// and so do we, i.e., you can't rely that on any particular order of querying indices. -/// -/// If the fields are none and empty, ignore the package index, instead rely on local archives and -/// caches. +/// The index locations to use for fetching packages. By default, uses the PyPI index. /// /// From a pip perspective, this type merges `--index-url`, `--extra-index-url`, and `--find-links`. #[derive(Debug, Clone)] @@ -338,7 +336,9 @@ impl<'a> IndexUrls { } } - /// Return an iterator over all [`IndexUrl`] entries. + /// Return an iterator over all [`IndexUrl`] entries in order. + /// + /// Prioritizes the extra indexes over the main index. /// /// If `no_index` was enabled, then this always returns an empty /// iterator. diff --git a/crates/distribution-types/src/lib.rs b/crates/distribution-types/src/lib.rs index 95ca6c912..600745586 100644 --- a/crates/distribution-types/src/lib.rs +++ b/crates/distribution-types/src/lib.rs @@ -51,6 +51,7 @@ pub use crate::direct_url::*; pub use crate::editable::*; pub use crate::error::*; pub use crate::file::*; +pub use crate::hash::*; pub use crate::id::*; pub use crate::index_url::*; pub use crate::installed::*; @@ -66,6 +67,7 @@ mod direct_url; mod editable; mod error; mod file; +mod hash; mod id; mod index_url; mod installed; @@ -371,6 +373,14 @@ impl Dist { } } + /// Returns the [`IndexUrl`], if the distribution is from a registry. + pub fn index(&self) -> Option<&IndexUrl> { + match self { + Self::Built(dist) => dist.index(), + Self::Source(dist) => dist.index(), + } + } + /// Returns the [`File`] instance, if this dist is from a registry with simple json api support pub fn file(&self) -> Option<&File> { match self { @@ -388,7 +398,16 @@ impl Dist { } impl BuiltDist { - /// Returns the [`File`] instance, if this dist is from a registry with simple json api support + /// Returns the [`IndexUrl`], if the distribution is from a registry. + pub fn index(&self) -> Option<&IndexUrl> { + match self { + Self::Registry(registry) => Some(®istry.index), + Self::DirectUrl(_) => None, + Self::Path(_) => None, + } + } + + /// Returns the [`File`] instance, if this distribution is from a registry. pub fn file(&self) -> Option<&File> { match self { Self::Registry(registry) => Some(®istry.file), @@ -406,6 +425,14 @@ impl BuiltDist { } impl SourceDist { + /// Returns the [`IndexUrl`], if the distribution is from a registry. + pub fn index(&self) -> Option<&IndexUrl> { + match self { + Self::Registry(registry) => Some(®istry.index), + Self::DirectUrl(_) | Self::Git(_) | Self::Path(_) => None, + } + } + /// Returns the [`File`] instance, if this dist is from a registry with simple json api support pub fn file(&self) -> Option<&File> { match self { @@ -764,26 +791,26 @@ impl RemoteSource for Dist { impl Identifier for Url { fn distribution_id(&self) -> DistributionId { - DistributionId::new(cache_key::digest(&cache_key::CanonicalUrl::new(self))) + DistributionId::Url(cache_key::CanonicalUrl::new(self)) } fn resource_id(&self) -> ResourceId { - ResourceId::new(cache_key::digest(&cache_key::RepositoryUrl::new(self))) + ResourceId::Url(cache_key::RepositoryUrl::new(self)) } } impl Identifier for File { fn distribution_id(&self) -> DistributionId { - if let Some(hash) = self.hashes.as_str() { - DistributionId::new(hash) + if let Some(hash) = self.hashes.first() { + DistributionId::Digest(hash.clone()) } else { self.url.distribution_id() } } fn resource_id(&self) -> ResourceId { - if let Some(hash) = self.hashes.as_str() { - ResourceId::new(hash) + if let Some(hash) = self.hashes.first() { + ResourceId::Digest(hash.clone()) } else { self.url.resource_id() } @@ -792,67 +819,31 @@ impl Identifier for File { impl Identifier for Path { fn distribution_id(&self) -> DistributionId { - DistributionId::new(cache_key::digest(&self)) + DistributionId::PathBuf(self.to_path_buf()) } fn resource_id(&self) -> ResourceId { - ResourceId::new(cache_key::digest(&self)) - } -} - -impl Identifier for String { - fn distribution_id(&self) -> DistributionId { - DistributionId::new(cache_key::digest(&self)) - } - - fn resource_id(&self) -> ResourceId { - ResourceId::new(cache_key::digest(&self)) - } -} - -impl Identifier for &str { - fn distribution_id(&self) -> DistributionId { - DistributionId::new(cache_key::digest(&self)) - } - - fn resource_id(&self) -> ResourceId { - ResourceId::new(cache_key::digest(&self)) - } -} - -impl Identifier for (&str, &str) { - fn distribution_id(&self) -> DistributionId { - DistributionId::new(cache_key::digest(&self)) - } - - fn resource_id(&self) -> ResourceId { - ResourceId::new(cache_key::digest(&self)) - } -} - -impl Identifier for (&Url, &str) { - fn distribution_id(&self) -> DistributionId { - DistributionId::new(cache_key::digest(&self)) - } - - fn resource_id(&self) -> ResourceId { - ResourceId::new(cache_key::digest(&self)) + ResourceId::PathBuf(self.to_path_buf()) } } impl Identifier for FileLocation { fn distribution_id(&self) -> DistributionId { match self { - Self::RelativeUrl(base, url) => (base.as_str(), url.as_str()).distribution_id(), - Self::AbsoluteUrl(url) => url.distribution_id(), + Self::RelativeUrl(base, url) => { + DistributionId::RelativeUrl(base.to_string(), url.to_string()) + } + Self::AbsoluteUrl(url) => DistributionId::AbsoluteUrl(url.to_string()), Self::Path(path) => path.distribution_id(), } } fn resource_id(&self) -> ResourceId { match self { - Self::RelativeUrl(base, url) => (base.as_str(), url.as_str()).resource_id(), - Self::AbsoluteUrl(url) => url.resource_id(), + Self::RelativeUrl(base, url) => { + ResourceId::RelativeUrl(base.to_string(), url.to_string()) + } + Self::AbsoluteUrl(url) => ResourceId::AbsoluteUrl(url.to_string()), Self::Path(path) => path.resource_id(), } } diff --git a/crates/distribution-types/src/prioritized_distribution.rs b/crates/distribution-types/src/prioritized_distribution.rs index e039eb311..0a13a4b28 100644 --- a/crates/distribution-types/src/prioritized_distribution.rs +++ b/crates/distribution-types/src/prioritized_distribution.rs @@ -1,8 +1,8 @@ use std::fmt::{Display, Formatter}; use pep440_rs::VersionSpecifiers; -use platform_tags::{IncompatibleTag, TagCompatibility, TagPriority}; -use pypi_types::{Hashes, Yanked}; +use platform_tags::{IncompatibleTag, TagPriority}; +use pypi_types::{HashDigest, Yanked}; use crate::{Dist, InstalledDist, ResolvedDistRef}; @@ -18,7 +18,7 @@ struct PrioritizedDistInner { /// The highest-priority wheel. wheel: Option<(Dist, WheelCompatibility)>, /// The hashes for each distribution. - hashes: Vec, + hashes: Vec, } /// A distribution that can be used for both resolution and installation. @@ -113,7 +113,7 @@ impl Display for IncompatibleDist { #[derive(Debug, Clone, PartialEq, Eq)] pub enum WheelCompatibility { Incompatible(IncompatibleWheel), - Compatible(TagPriority), + Compatible(Hash, TagPriority), } #[derive(Debug, PartialEq, Eq, Clone)] @@ -128,7 +128,7 @@ pub enum IncompatibleWheel { #[derive(Debug, Clone, PartialEq, Eq)] pub enum SourceDistCompatibility { Incompatible(IncompatibleSource), - Compatible, + Compatible(Hash), } #[derive(Debug, PartialEq, Eq, Clone)] @@ -139,26 +139,40 @@ pub enum IncompatibleSource { NoBuild, } +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum Hash { + /// The hash is present, but does not match the expected value. + Mismatched, + /// The hash is missing. + Missing, + /// The hash matches the expected value. + Matched, +} + impl PrioritizedDist { /// Create a new [`PrioritizedDist`] from the given wheel distribution. - pub fn from_built(dist: Dist, hash: Option, compatibility: WheelCompatibility) -> Self { + pub fn from_built( + dist: Dist, + hashes: Vec, + compatibility: WheelCompatibility, + ) -> Self { Self(Box::new(PrioritizedDistInner { wheel: Some((dist, compatibility)), source: None, - hashes: hash.map(|hash| vec![hash]).unwrap_or_default(), + hashes, })) } /// Create a new [`PrioritizedDist`] from the given source distribution. pub fn from_source( dist: Dist, - hash: Option, + hashes: Vec, compatibility: SourceDistCompatibility, ) -> Self { Self(Box::new(PrioritizedDistInner { wheel: None, source: Some((dist, compatibility)), - hashes: hash.map(|hash| vec![hash]).unwrap_or_default(), + hashes, })) } @@ -166,7 +180,7 @@ impl PrioritizedDist { pub fn insert_built( &mut self, dist: Dist, - hash: Option, + hashes: Vec, compatibility: WheelCompatibility, ) { // Track the highest-priority wheel. @@ -178,16 +192,14 @@ impl PrioritizedDist { self.0.wheel = Some((dist, compatibility)); } - if let Some(hash) = hash { - self.0.hashes.push(hash); - } + self.0.hashes.extend(hashes); } /// Insert the given source distribution into the [`PrioritizedDist`]. pub fn insert_source( &mut self, dist: Dist, - hash: Option, + hashes: Vec, compatibility: SourceDistCompatibility, ) { // Track the highest-priority source. @@ -199,16 +211,25 @@ impl PrioritizedDist { self.0.source = Some((dist, compatibility)); } - if let Some(hash) = hash { - self.0.hashes.push(hash); - } + self.0.hashes.extend(hashes); } /// Return the highest-priority distribution for the package version, if any. pub fn get(&self) -> Option { match (&self.0.wheel, &self.0.source) { + // If both are compatible, break ties based on the hash. + ( + Some((wheel, WheelCompatibility::Compatible(wheel_hash, tag_priority))), + Some((source_dist, SourceDistCompatibility::Compatible(source_hash))), + ) => { + if source_hash > wheel_hash { + Some(CompatibleDist::SourceDist(source_dist)) + } else { + Some(CompatibleDist::CompatibleWheel(wheel, *tag_priority)) + } + } // Prefer the highest-priority, platform-compatible wheel. - (Some((wheel, WheelCompatibility::Compatible(tag_priority))), _) => { + (Some((wheel, WheelCompatibility::Compatible(_, tag_priority))), _) => { Some(CompatibleDist::CompatibleWheel(wheel, *tag_priority)) } // If we have a compatible source distribution and an incompatible wheel, return the @@ -217,64 +238,42 @@ impl PrioritizedDist { // using the wheel is faster. ( Some((wheel, WheelCompatibility::Incompatible(_))), - Some((source_dist, SourceDistCompatibility::Compatible)), + Some((source_dist, SourceDistCompatibility::Compatible(_))), ) => Some(CompatibleDist::IncompatibleWheel { source_dist, wheel }), // Otherwise, if we have a source distribution, return it. - (None, Some((source_dist, SourceDistCompatibility::Compatible))) => { + (None, Some((source_dist, SourceDistCompatibility::Compatible(_)))) => { Some(CompatibleDist::SourceDist(source_dist)) } _ => None, } } - /// Return the compatible source distribution, if any. - pub fn compatible_source(&self) -> Option<&Dist> { - self.0 - .source - .as_ref() - .and_then(|(dist, compatibility)| match compatibility { - SourceDistCompatibility::Compatible => Some(dist), - SourceDistCompatibility::Incompatible(_) => None, - }) - } - /// Return the incompatible source distribution, if any. pub fn incompatible_source(&self) -> Option<(&Dist, &IncompatibleSource)> { self.0 .source .as_ref() .and_then(|(dist, compatibility)| match compatibility { - SourceDistCompatibility::Compatible => None, + SourceDistCompatibility::Compatible(_) => None, SourceDistCompatibility::Incompatible(incompatibility) => { Some((dist, incompatibility)) } }) } - /// Return the compatible built distribution, if any. - pub fn compatible_wheel(&self) -> Option<(&Dist, TagPriority)> { - self.0 - .wheel - .as_ref() - .and_then(|(dist, compatibility)| match compatibility { - WheelCompatibility::Compatible(priority) => Some((dist, *priority)), - WheelCompatibility::Incompatible(_) => None, - }) - } - /// Return the incompatible built distribution, if any. pub fn incompatible_wheel(&self) -> Option<(&Dist, &IncompatibleWheel)> { self.0 .wheel .as_ref() .and_then(|(dist, compatibility)| match compatibility { - WheelCompatibility::Compatible(_) => None, + WheelCompatibility::Compatible(_, _) => None, WheelCompatibility::Incompatible(incompatibility) => Some((dist, incompatibility)), }) } /// Return the hashes for each distribution. - pub fn hashes(&self) -> &[Hashes] { + pub fn hashes(&self) -> &[HashDigest] { &self.0.hashes } @@ -311,11 +310,23 @@ impl<'a> CompatibleDist<'a> { } => ResolvedDistRef::Installable(source_dist), } } + + /// Returns whether the distribution is a source distribution. + /// + /// Avoid building source distributions we don't need. + pub fn prefetchable(&self) -> bool { + match *self { + CompatibleDist::SourceDist(_) => false, + CompatibleDist::InstalledDist(_) + | CompatibleDist::CompatibleWheel(_, _) + | CompatibleDist::IncompatibleWheel { .. } => true, + } + } } impl WheelCompatibility { pub fn is_compatible(&self) -> bool { - matches!(self, Self::Compatible(_)) + matches!(self, Self::Compatible(_, _)) } /// Return `true` if the current compatibility is more compatible than another. @@ -324,11 +335,12 @@ impl WheelCompatibility { /// Compatible wheel ordering is determined by tag priority. pub fn is_more_compatible(&self, other: &Self) -> bool { match (self, other) { - (Self::Compatible(_), Self::Incompatible(_)) => true, - (Self::Compatible(tag_priority), Self::Compatible(other_tag_priority)) => { - tag_priority > other_tag_priority - } - (Self::Incompatible(_), Self::Compatible(_)) => false, + (Self::Compatible(_, _), Self::Incompatible(_)) => true, + ( + Self::Compatible(hash, tag_priority), + Self::Compatible(other_hash, other_tag_priority), + ) => (hash, tag_priority) > (other_hash, other_tag_priority), + (Self::Incompatible(_), Self::Compatible(_, _)) => false, (Self::Incompatible(incompatibility), Self::Incompatible(other_incompatibility)) => { incompatibility.is_more_compatible(other_incompatibility) } @@ -344,9 +356,11 @@ impl SourceDistCompatibility { /// Incompatible source distribution priority selects a source distribution that was "closest" to being usable. pub fn is_more_compatible(&self, other: &Self) -> bool { match (self, other) { - (Self::Compatible, Self::Incompatible(_)) => true, - (Self::Compatible, Self::Compatible) => false, // Arbitrary - (Self::Incompatible(_), Self::Compatible) => false, + (Self::Compatible(_), Self::Incompatible(_)) => true, + (Self::Compatible(compatibility), Self::Compatible(other_compatibility)) => { + compatibility > other_compatibility + } + (Self::Incompatible(_), Self::Compatible(_)) => false, (Self::Incompatible(incompatibility), Self::Incompatible(other_incompatibility)) => { incompatibility.is_more_compatible(other_incompatibility) } @@ -354,15 +368,6 @@ impl SourceDistCompatibility { } } -impl From for WheelCompatibility { - fn from(value: TagCompatibility) -> Self { - match value { - TagCompatibility::Compatible(priority) => Self::Compatible(priority), - TagCompatibility::Incompatible(tag) => Self::Incompatible(IncompatibleWheel::Tag(tag)), - } - } -} - impl IncompatibleSource { fn is_more_compatible(&self, other: &Self) -> bool { match self { diff --git a/crates/distribution-types/src/resolved.rs b/crates/distribution-types/src/resolved.rs index 36a269c18..1f72ba4a0 100644 --- a/crates/distribution-types/src/resolved.rs +++ b/crates/distribution-types/src/resolved.rs @@ -1,10 +1,10 @@ -use std::fmt::Display; +use std::fmt::{Display, Formatter}; use pep508_rs::PackageName; use crate::{ - Dist, DistributionId, DistributionMetadata, Identifier, InstalledDist, Name, ResourceId, - VersionOrUrl, + Dist, DistributionId, DistributionMetadata, Identifier, IndexUrl, InstalledDist, Name, + ResourceId, VersionOrUrl, }; /// A distribution that can be used for resolution and installation. @@ -31,6 +31,14 @@ impl ResolvedDist { Self::Installed(dist) => dist.is_editable(), } } + + /// Returns the [`IndexUrl`], if the distribution is from a registry. + pub fn index(&self) -> Option<&IndexUrl> { + match self { + Self::Installable(dist) => dist.index(), + Self::Installed(_) => None, + } + } } impl ResolvedDistRef<'_> { @@ -42,6 +50,15 @@ impl ResolvedDistRef<'_> { } } +impl Display for ResolvedDistRef<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Installable(dist) => Display::fmt(dist, f), + Self::Installed(dist) => Display::fmt(dist, f), + } + } +} + impl Name for ResolvedDistRef<'_> { fn name(&self) -> &PackageName { match self { diff --git a/crates/distribution-types/src/traits.rs b/crates/distribution-types/src/traits.rs index 4f72961bd..5d87219dc 100644 --- a/crates/distribution-types/src/traits.rs +++ b/crates/distribution-types/src/traits.rs @@ -10,7 +10,8 @@ use crate::{ BuiltDist, CachedDirectUrlDist, CachedDist, CachedRegistryDist, DirectUrlBuiltDist, DirectUrlSourceDist, Dist, DistributionId, GitSourceDist, InstalledDirectUrlDist, InstalledDist, InstalledRegistryDist, InstalledVersion, LocalDist, PackageId, PathBuiltDist, - PathSourceDist, RegistryBuiltDist, RegistrySourceDist, ResourceId, SourceDist, VersionOrUrl, + PathSourceDist, RegistryBuiltDist, RegistrySourceDist, ResourceId, SourceDist, VersionId, + VersionOrUrl, }; pub trait Name { @@ -25,16 +26,29 @@ pub trait DistributionMetadata: Name { /// for URL-based distributions. fn version_or_url(&self) -> VersionOrUrl; - /// Returns a unique identifier for the package. + /// Returns a unique identifier for the package at the given version (e.g., `black==23.10.0`). /// /// Note that this is not equivalent to a unique identifier for the _distribution_, as multiple /// registry-based distributions (e.g., different wheels for the same package and version) - /// will return the same package ID, but different distribution IDs. - fn package_id(&self) -> PackageId { + /// will return the same version ID, but different distribution IDs. + fn version_id(&self) -> VersionId { match self.version_or_url() { VersionOrUrl::Version(version) => { - PackageId::from_registry(self.name().clone(), version.clone()) + VersionId::from_registry(self.name().clone(), version.clone()) } + VersionOrUrl::Url(url) => VersionId::from_url(url), + } + } + + /// Returns a unique identifier for a package. A package can either be identified by a name + /// (e.g., `black`) or a URL (e.g., `git+https://github.com/psf/black`). + /// + /// Note that this is not equivalent to a unique identifier for the _distribution_, as multiple + /// registry-based distributions (e.g., different wheels for the same package and version) + /// will return the same version ID, but different distribution IDs. + fn package_id(&self) -> PackageId { + match self.version_or_url() { + VersionOrUrl::Version(_) => PackageId::from_registry(self.name().clone()), VersionOrUrl::Url(url) => PackageId::from_url(url), } } @@ -57,6 +71,17 @@ pub trait RemoteSource { pub trait Identifier { /// Return a unique resource identifier for the distribution, like a SHA-256 hash of the /// distribution's contents. + /// + /// A distribution is a specific archive of a package at a specific version. For a given package + /// version, there may be multiple distributions, e.g., source distribution, along with + /// multiple binary distributions (wheels) for different platforms. As a concrete example, + /// `black-23.10.0-py3-none-any.whl` would represent a (binary) distribution of the `black` package + /// at version `23.10.0`. + /// + /// The distribution ID is used to uniquely identify a distribution. Ideally, the distribution + /// ID should be a hash of the distribution's contents, though in practice, it's only required + /// that the ID is unique within a single invocation of the resolver (and so, e.g., a hash of + /// the URL would also be sufficient). fn distribution_id(&self) -> DistributionId; /// Return a unique resource identifier for the underlying resource backing the distribution. diff --git a/crates/pep508-rs/src/lib.rs b/crates/pep508-rs/src/lib.rs index d37eff711..c8091af7d 100644 --- a/crates/pep508-rs/src/lib.rs +++ b/crates/pep508-rs/src/lib.rs @@ -72,7 +72,7 @@ pub enum Pep508ErrorSource { String(String), /// A URL parsing error. #[error(transparent)] - UrlError(#[from] verbatim_url::VerbatimUrlError), + UrlError(#[from] VerbatimUrlError), /// The version requirement is not supported. #[error("{0}")] UnsupportedRequirement(String), diff --git a/crates/pypi-types/src/base_url.rs b/crates/pypi-types/src/base_url.rs index d4d991cdb..428920743 100644 --- a/crates/pypi-types/src/base_url.rs +++ b/crates/pypi-types/src/base_url.rs @@ -1,37 +1,19 @@ use serde::{Deserialize, Serialize}; use url::Url; -/// Join a possibly relative URL to a base URL. -/// -/// When `maybe_relative` is not relative, then it is parsed and returned with -/// `base` being ignored. -/// -/// This is useful for parsing URLs that may be absolute or relative, with a -/// known base URL, and that doesn't require having already parsed a `BaseUrl`. -pub fn base_url_join_relative(base: &str, maybe_relative: &str) -> Result { - match Url::parse(maybe_relative) { - Ok(absolute) => Ok(absolute), - Err(err) => { - if err == url::ParseError::RelativeUrlWithoutBase { - let base_url = Url::parse(base).map_err(|err| JoinRelativeError::ParseError { - original: base.to_string(), - source: err, - })?; +/// Join a relative URL to a base URL. +pub fn base_url_join_relative(base: &str, relative: &str) -> Result { + let base_url = Url::parse(base).map_err(|err| JoinRelativeError::ParseError { + original: base.to_string(), + source: err, + })?; - base_url - .join(maybe_relative) - .map_err(|_| JoinRelativeError::ParseError { - original: format!("{base}/{maybe_relative}"), - source: err, - }) - } else { - Err(JoinRelativeError::ParseError { - original: maybe_relative.to_string(), - source: err, - }) - } - } - } + base_url + .join(relative) + .map_err(|err| JoinRelativeError::ParseError { + original: format!("{base}/{relative}"), + source: err, + }) } /// An error that occurs when `base_url_join_relative` fails. diff --git a/crates/pypi-types/src/metadata.rs b/crates/pypi-types/src/metadata.rs index f3b267a00..a5653ed91 100644 --- a/crates/pypi-types/src/metadata.rs +++ b/crates/pypi-types/src/metadata.rs @@ -1,9 +1,8 @@ //! Derived from `pypi_types_crate`. -use indexmap::IndexMap; -use std::io; use std::str::FromStr; +use indexmap::IndexMap; use mailparse::{MailHeaderMap, MailParseError}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -39,38 +38,17 @@ pub struct Metadata23 { /// /// The error type #[derive(Error, Debug)] -pub enum Error { - /// I/O error - #[error(transparent)] - Io(#[from] io::Error), - /// mail parse error +pub enum MetadataError { #[error(transparent)] MailParse(#[from] MailParseError), - /// TOML parse error #[error(transparent)] Toml(#[from] toml::de::Error), - /// Metadata field not found #[error("metadata field {0} not found")] FieldNotFound(&'static str), - /// Unknown distribution type - #[error("unknown distribution type")] - UnknownDistributionType, - /// Metadata file not found - #[error("metadata file not found")] - MetadataNotFound, - /// Invalid project URL (no comma) - #[error("Invalid Project-URL field (missing comma): '{0}'")] - InvalidProjectUrl(String), - /// Multiple metadata files found - #[error("found multiple metadata files: {0:?}")] - MultipleMetadataFiles(Vec), - /// Invalid Version #[error("invalid version: {0}")] Pep440VersionError(VersionParseError), - /// Invalid VersionSpecifier #[error(transparent)] Pep440Error(#[from] VersionSpecifiersParseError), - /// Invalid Requirement #[error(transparent)] Pep508Error(#[from] Pep508Error), #[error(transparent)] @@ -86,20 +64,20 @@ pub enum Error { /// From impl Metadata23 { /// Parse the [`Metadata23`] from a `METADATA` file, as included in a built distribution (wheel). - pub fn parse_metadata(content: &[u8]) -> Result { + pub fn parse_metadata(content: &[u8]) -> Result { let headers = Headers::parse(content)?; let name = PackageName::new( headers .get_first_value("Name") - .ok_or(Error::FieldNotFound("Name"))?, + .ok_or(MetadataError::FieldNotFound("Name"))?, )?; let version = Version::from_str( &headers .get_first_value("Version") - .ok_or(Error::FieldNotFound("Version"))?, + .ok_or(MetadataError::FieldNotFound("Version"))?, ) - .map_err(Error::Pep440VersionError)?; + .map_err(MetadataError::Pep440VersionError)?; let requires_dist = headers .get_all_values("Requires-Dist") .map(|requires_dist| { @@ -135,28 +113,28 @@ impl Metadata23 { /// Read the [`Metadata23`] from a source distribution's `PKG-INFO` file, if it uses Metadata 2.2 /// or later _and_ none of the required fields (`Requires-Python`, `Requires-Dist`, and /// `Provides-Extra`) are marked as dynamic. - pub fn parse_pkg_info(content: &[u8]) -> Result { + pub fn parse_pkg_info(content: &[u8]) -> Result { let headers = Headers::parse(content)?; // To rely on a source distribution's `PKG-INFO` file, the `Metadata-Version` field must be // present and set to a value of at least `2.2`. let metadata_version = headers .get_first_value("Metadata-Version") - .ok_or(Error::FieldNotFound("Metadata-Version"))?; + .ok_or(MetadataError::FieldNotFound("Metadata-Version"))?; // Parse the version into (major, minor). let (major, minor) = parse_version(&metadata_version)?; if (major, minor) < (2, 2) || (major, minor) >= (3, 0) { - return Err(Error::UnsupportedMetadataVersion(metadata_version)); + return Err(MetadataError::UnsupportedMetadataVersion(metadata_version)); } // If any of the fields we need are marked as dynamic, we can't use the `PKG-INFO` file. let dynamic = headers.get_all_values("Dynamic").collect::>(); for field in dynamic { match field.as_str() { - "Requires-Python" => return Err(Error::DynamicField("Requires-Python")), - "Requires-Dist" => return Err(Error::DynamicField("Requires-Dist")), - "Provides-Extra" => return Err(Error::DynamicField("Provides-Extra")), + "Requires-Python" => return Err(MetadataError::DynamicField("Requires-Python")), + "Requires-Dist" => return Err(MetadataError::DynamicField("Requires-Dist")), + "Provides-Extra" => return Err(MetadataError::DynamicField("Provides-Extra")), _ => (), } } @@ -165,14 +143,14 @@ impl Metadata23 { let name = PackageName::new( headers .get_first_value("Name") - .ok_or(Error::FieldNotFound("Name"))?, + .ok_or(MetadataError::FieldNotFound("Name"))?, )?; let version = Version::from_str( &headers .get_first_value("Version") - .ok_or(Error::FieldNotFound("Version"))?, + .ok_or(MetadataError::FieldNotFound("Version"))?, ) - .map_err(Error::Pep440VersionError)?; + .map_err(MetadataError::Pep440VersionError)?; // The remaining fields are required to be present. let requires_dist = headers @@ -208,29 +186,31 @@ impl Metadata23 { } /// Extract the metadata from a `pyproject.toml` file, as specified in PEP 621. - pub fn parse_pyproject_toml(contents: &str) -> Result { + pub fn parse_pyproject_toml(contents: &str) -> Result { let pyproject_toml: PyProjectToml = toml::from_str(contents)?; let project = pyproject_toml .project - .ok_or(Error::FieldNotFound("project"))?; + .ok_or(MetadataError::FieldNotFound("project"))?; // If any of the fields we need were declared as dynamic, we can't use the `pyproject.toml` file. let dynamic = project.dynamic.unwrap_or_default(); for field in dynamic { match field.as_str() { - "dependencies" => return Err(Error::DynamicField("dependencies")), + "dependencies" => return Err(MetadataError::DynamicField("dependencies")), "optional-dependencies" => { - return Err(Error::DynamicField("optional-dependencies")) + return Err(MetadataError::DynamicField("optional-dependencies")) } - "requires-python" => return Err(Error::DynamicField("requires-python")), - "version" => return Err(Error::DynamicField("version")), + "requires-python" => return Err(MetadataError::DynamicField("requires-python")), + "version" => return Err(MetadataError::DynamicField("version")), _ => (), } } let name = project.name; - let version = project.version.ok_or(Error::FieldNotFound("version"))?; + let version = project + .version + .ok_or(MetadataError::FieldNotFound("version"))?; let requires_python = project.requires_python.map(VersionSpecifiers::from); // Extract the requirements. @@ -309,28 +289,31 @@ pub struct Metadata10 { impl Metadata10 { /// Parse the [`Metadata10`] from a `PKG-INFO` file, as included in a source distribution. - pub fn parse_pkg_info(content: &[u8]) -> Result { + pub fn parse_pkg_info(content: &[u8]) -> Result { let headers = Headers::parse(content)?; let name = PackageName::new( headers .get_first_value("Name") - .ok_or(Error::FieldNotFound("Name"))?, + .ok_or(MetadataError::FieldNotFound("Name"))?, )?; Ok(Self { name }) } } /// Parse a `Metadata-Version` field into a (major, minor) tuple. -fn parse_version(metadata_version: &str) -> Result<(u8, u8), Error> { - let (major, minor) = metadata_version - .split_once('.') - .ok_or(Error::InvalidMetadataVersion(metadata_version.to_string()))?; +fn parse_version(metadata_version: &str) -> Result<(u8, u8), MetadataError> { + let (major, minor) = + metadata_version + .split_once('.') + .ok_or(MetadataError::InvalidMetadataVersion( + metadata_version.to_string(), + ))?; let major = major .parse::() - .map_err(|_| Error::InvalidMetadataVersion(metadata_version.to_string()))?; + .map_err(|_| MetadataError::InvalidMetadataVersion(metadata_version.to_string()))?; let minor = minor .parse::() - .map_err(|_| Error::InvalidMetadataVersion(metadata_version.to_string()))?; + .map_err(|_| MetadataError::InvalidMetadataVersion(metadata_version.to_string()))?; Ok((major, minor)) } @@ -373,7 +356,7 @@ mod tests { use pep440_rs::Version; use uv_normalize::PackageName; - use crate::Error; + use crate::MetadataError; use super::Metadata23; @@ -381,11 +364,11 @@ mod tests { fn test_parse_metadata() { let s = "Metadata-Version: 1.0"; let meta = Metadata23::parse_metadata(s.as_bytes()); - assert!(matches!(meta, Err(Error::FieldNotFound("Name")))); + assert!(matches!(meta, Err(MetadataError::FieldNotFound("Name")))); let s = "Metadata-Version: 1.0\nName: asdf"; let meta = Metadata23::parse_metadata(s.as_bytes()); - assert!(matches!(meta, Err(Error::FieldNotFound("Version")))); + assert!(matches!(meta, Err(MetadataError::FieldNotFound("Version")))); let s = "Metadata-Version: 1.0\nName: asdf\nVersion: 1.0"; let meta = Metadata23::parse_metadata(s.as_bytes()).unwrap(); @@ -404,22 +387,25 @@ mod tests { let s = "Metadata-Version: 1.0\nName: =?utf-8?q?=C3=A4_space?= \nVersion: 1.0"; let meta = Metadata23::parse_metadata(s.as_bytes()); - assert!(matches!(meta, Err(Error::InvalidName(_)))); + assert!(matches!(meta, Err(MetadataError::InvalidName(_)))); } #[test] fn test_parse_pkg_info() { let s = "Metadata-Version: 2.1"; let meta = Metadata23::parse_pkg_info(s.as_bytes()); - assert!(matches!(meta, Err(Error::UnsupportedMetadataVersion(_)))); + assert!(matches!( + meta, + Err(MetadataError::UnsupportedMetadataVersion(_)) + )); let s = "Metadata-Version: 2.2\nName: asdf"; let meta = Metadata23::parse_pkg_info(s.as_bytes()); - assert!(matches!(meta, Err(Error::FieldNotFound("Version")))); + assert!(matches!(meta, Err(MetadataError::FieldNotFound("Version")))); let s = "Metadata-Version: 2.3\nName: asdf"; let meta = Metadata23::parse_pkg_info(s.as_bytes()); - assert!(matches!(meta, Err(Error::FieldNotFound("Version")))); + assert!(matches!(meta, Err(MetadataError::FieldNotFound("Version")))); let s = "Metadata-Version: 2.3\nName: asdf\nVersion: 1.0"; let meta = Metadata23::parse_pkg_info(s.as_bytes()).unwrap(); @@ -428,7 +414,7 @@ mod tests { let s = "Metadata-Version: 2.3\nName: asdf\nVersion: 1.0\nDynamic: Requires-Dist"; let meta = Metadata23::parse_pkg_info(s.as_bytes()).unwrap_err(); - assert!(matches!(meta, Error::DynamicField("Requires-Dist"))); + assert!(matches!(meta, MetadataError::DynamicField("Requires-Dist"))); let s = "Metadata-Version: 2.3\nName: asdf\nVersion: 1.0\nRequires-Dist: foo"; let meta = Metadata23::parse_pkg_info(s.as_bytes()).unwrap(); @@ -444,7 +430,7 @@ mod tests { name = "asdf" "#; let meta = Metadata23::parse_pyproject_toml(s); - assert!(matches!(meta, Err(Error::FieldNotFound("version")))); + assert!(matches!(meta, Err(MetadataError::FieldNotFound("version")))); let s = r#" [project] @@ -452,7 +438,7 @@ mod tests { dynamic = ["version"] "#; let meta = Metadata23::parse_pyproject_toml(s); - assert!(matches!(meta, Err(Error::DynamicField("version")))); + assert!(matches!(meta, Err(MetadataError::DynamicField("version")))); let s = r#" [project] diff --git a/crates/pypi-types/src/simple_json.rs b/crates/pypi-types/src/simple_json.rs index 342d54b2b..199b3c7c5 100644 --- a/crates/pypi-types/src/simple_json.rs +++ b/crates/pypi-types/src/simple_json.rs @@ -68,11 +68,7 @@ where )) } -#[derive( - Debug, Clone, Serialize, Deserialize, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, -)] -#[archive(check_bytes)] -#[archive_attr(derive(Debug))] +#[derive(Debug, Clone, Deserialize)] #[serde(untagged)] pub enum DistInfoMetadata { Bool(bool), @@ -125,23 +121,7 @@ impl Default for Yanked { /// A dictionary mapping a hash name to a hex encoded digest of the file. /// /// PEP 691 says multiple hashes can be included and the interpretation is left to the client. -#[derive( - Debug, - Clone, - Ord, - PartialOrd, - Eq, - PartialEq, - Hash, - Default, - Serialize, - Deserialize, - rkyv::Archive, - rkyv::Deserialize, - rkyv::Serialize, -)] -#[archive(check_bytes)] -#[archive_attr(derive(Debug))] +#[derive(Debug, Clone, Eq, PartialEq, Default, Deserialize)] pub struct Hashes { pub md5: Option>, pub sha256: Option>, @@ -150,31 +130,34 @@ pub struct Hashes { } impl Hashes { - /// Format as `:`. - pub fn to_string(&self) -> Option { - self.sha512 - .as_ref() - .map(|sha512| format!("sha512:{sha512}")) - .or_else(|| { - self.sha384 - .as_ref() - .map(|sha384| format!("sha384:{sha384}")) - }) - .or_else(|| { - self.sha256 - .as_ref() - .map(|sha256| format!("sha256:{sha256}")) - }) - .or_else(|| self.md5.as_ref().map(|md5| format!("md5:{md5}"))) - } - - /// Return the hash digest. - pub fn as_str(&self) -> Option<&str> { - self.sha512 - .as_deref() - .or(self.sha384.as_deref()) - .or(self.sha256.as_deref()) - .or(self.md5.as_deref()) + /// Convert a set of [`Hashes`] into a list of [`HashDigest`]s. + pub fn into_digests(self) -> Vec { + let mut digests = Vec::new(); + if let Some(sha512) = self.sha512 { + digests.push(HashDigest { + algorithm: HashAlgorithm::Sha512, + digest: sha512, + }); + } + if let Some(sha384) = self.sha384 { + digests.push(HashDigest { + algorithm: HashAlgorithm::Sha384, + digest: sha384, + }); + } + if let Some(sha256) = self.sha256 { + digests.push(HashDigest { + algorithm: HashAlgorithm::Sha256, + digest: sha256, + }); + } + if let Some(md5) = self.md5 { + digests.push(HashDigest { + algorithm: HashAlgorithm::Md5, + digest: md5, + }); + } + digests } } @@ -239,6 +222,118 @@ impl FromStr for Hashes { } } +#[derive( + Debug, + Clone, + Copy, + Ord, + PartialOrd, + Eq, + PartialEq, + Hash, + Serialize, + Deserialize, + rkyv::Archive, + rkyv::Deserialize, + rkyv::Serialize, +)] +#[archive(check_bytes)] +#[archive_attr(derive(Debug))] +pub enum HashAlgorithm { + Md5, + Sha256, + Sha384, + Sha512, +} + +impl FromStr for HashAlgorithm { + type Err = HashError; + + fn from_str(s: &str) -> Result { + match s { + "md5" => Ok(Self::Md5), + "sha256" => Ok(Self::Sha256), + "sha384" => Ok(Self::Sha384), + "sha512" => Ok(Self::Sha512), + _ => Err(HashError::UnsupportedHashAlgorithm(s.to_string())), + } + } +} + +impl std::fmt::Display for HashAlgorithm { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Md5 => write!(f, "md5"), + Self::Sha256 => write!(f, "sha256"), + Self::Sha384 => write!(f, "sha384"), + Self::Sha512 => write!(f, "sha512"), + } + } +} + +/// A hash name and hex encoded digest of the file. +#[derive( + Debug, + Clone, + Ord, + PartialOrd, + Eq, + PartialEq, + Hash, + Serialize, + Deserialize, + rkyv::Archive, + rkyv::Deserialize, + rkyv::Serialize, +)] +#[archive(check_bytes)] +#[archive_attr(derive(Debug))] +pub struct HashDigest { + pub algorithm: HashAlgorithm, + pub digest: Box, +} + +impl HashDigest { + /// Return the [`HashAlgorithm`] of the digest. + pub fn algorithm(&self) -> HashAlgorithm { + self.algorithm + } +} + +impl std::fmt::Display for HashDigest { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}:{}", self.algorithm, self.digest) + } +} + +impl FromStr for HashDigest { + type Err = HashError; + + fn from_str(s: &str) -> Result { + let mut parts = s.split(':'); + + // Extract the key and value. + let name = parts + .next() + .ok_or_else(|| HashError::InvalidStructure(s.to_string()))?; + let value = parts + .next() + .ok_or_else(|| HashError::InvalidStructure(s.to_string()))?; + + // Ensure there are no more parts. + if parts.next().is_some() { + return Err(HashError::InvalidStructure(s.to_string())); + } + + let algorithm = HashAlgorithm::from_str(name)?; + + Ok(HashDigest { + algorithm, + digest: value.to_owned().into_boxed_str(), + }) + } +} + #[derive(thiserror::Error, Debug)] pub enum HashError { #[error("Unexpected hash (expected `:`): {0}")] diff --git a/crates/requirements-txt/Cargo.toml b/crates/requirements-txt/Cargo.toml index 17c7525b6..0d91710db 100644 --- a/crates/requirements-txt/Cargo.toml +++ b/crates/requirements-txt/Cargo.toml @@ -17,7 +17,7 @@ pep508_rs = { workspace = true, features = ["rkyv", "serde", "non-pep508-extensi uv-client = { workspace = true } uv-fs = { workspace = true } uv-normalize = { workspace = true } -uv-types = { workspace = true } +uv-configuration = { workspace = true } uv-warnings = { workspace = true } fs-err = { workspace = true } diff --git a/crates/requirements-txt/src/lib.rs b/crates/requirements-txt/src/lib.rs index 47968a3e3..8c897289f 100644 --- a/crates/requirements-txt/src/lib.rs +++ b/crates/requirements-txt/src/lib.rs @@ -52,9 +52,9 @@ use pep508_rs::{ #[cfg(feature = "http")] use uv_client::BaseClient; use uv_client::BaseClientBuilder; +use uv_configuration::{NoBinary, NoBuild, PackageNameSpecifier}; use uv_fs::{normalize_url_path, Simplified}; use uv_normalize::ExtraName; -use uv_types::{NoBinary, NoBuild, PackageNameSpecifier}; use uv_warnings::warn_user; /// We emit one of those for each requirements.txt entry @@ -293,21 +293,16 @@ impl Display for EditableRequirement { /// A [Requirement] with additional metadata from the requirements.txt, currently only hashes but in /// the future also editable an similar information -#[derive(Debug, Deserialize, Clone, Eq, PartialEq, Serialize)] +#[derive(Debug, Deserialize, Clone, Eq, PartialEq, Hash, Serialize)] pub struct RequirementEntry { /// The actual PEP 508 requirement pub requirement: RequirementsTxtRequirement, /// Hashes of the downloadable packages pub hashes: Vec, - /// Editable installation, see e.g. - pub editable: bool, } impl Display for RequirementEntry { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - if self.editable { - write!(f, "-e ")?; - } write!(f, "{}", self.requirement)?; for hash in &self.hashes { write!(f, " --hash {hash}")?; @@ -670,7 +665,6 @@ fn parse_entry( RequirementsTxtStatement::RequirementEntry(RequirementEntry { requirement, hashes, - editable: false, }) } else if let Some(char) = s.peek() { let (line, column) = calculate_row_column(content, s.cursor()); @@ -1743,7 +1737,6 @@ mod test { }, ), hashes: [], - editable: false, }, ], constraints: [], @@ -1798,7 +1791,6 @@ mod test { }, ), hashes: [], - editable: false, }, ], constraints: [], diff --git a/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-basic.txt.snap b/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-basic.txt.snap index aeefa208a..1ea308583 100644 --- a/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-basic.txt.snap +++ b/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-basic.txt.snap @@ -27,7 +27,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, RequirementEntry { requirement: Pep508( @@ -52,7 +51,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, RequirementEntry { requirement: Pep508( @@ -77,7 +75,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, RequirementEntry { requirement: Pep508( @@ -102,7 +99,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, RequirementEntry { requirement: Pep508( @@ -127,7 +123,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, RequirementEntry { requirement: Pep508( @@ -152,7 +147,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, ], constraints: [], diff --git a/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-constraints-a.txt.snap b/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-constraints-a.txt.snap index 3bae2fc6f..eed853303 100644 --- a/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-constraints-a.txt.snap +++ b/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-constraints-a.txt.snap @@ -27,7 +27,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, ], constraints: [ diff --git a/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-constraints-b.txt.snap b/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-constraints-b.txt.snap index 8c311f4b9..2ac70d195 100644 --- a/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-constraints-b.txt.snap +++ b/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-constraints-b.txt.snap @@ -27,7 +27,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, RequirementEntry { requirement: Pep508( @@ -52,7 +51,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, ], constraints: [], diff --git a/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-editable.txt.snap b/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-editable.txt.snap index e8a8a5090..ede00ad21 100644 --- a/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-editable.txt.snap +++ b/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-editable.txt.snap @@ -16,7 +16,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, RequirementEntry { requirement: Pep508( @@ -57,7 +56,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, ], constraints: [], diff --git a/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-for-poetry.txt.snap b/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-for-poetry.txt.snap index 513d09259..ff6db6eab 100644 --- a/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-for-poetry.txt.snap +++ b/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-for-poetry.txt.snap @@ -27,7 +27,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, RequirementEntry { requirement: Pep508( @@ -52,7 +51,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, RequirementEntry { requirement: Pep508( @@ -66,7 +64,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, RequirementEntry { requirement: Pep508( @@ -99,7 +96,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, ], constraints: [], diff --git a/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-include-a.txt.snap b/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-include-a.txt.snap index f14751af9..82edeab41 100644 --- a/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-include-a.txt.snap +++ b/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-include-a.txt.snap @@ -16,7 +16,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, RequirementEntry { requirement: Pep508( @@ -41,7 +40,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, ], constraints: [], diff --git a/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-include-b.txt.snap b/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-include-b.txt.snap index 07f69a01f..745bacabc 100644 --- a/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-include-b.txt.snap +++ b/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-include-b.txt.snap @@ -16,7 +16,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, ], constraints: [], diff --git a/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-poetry-with-hashes.txt.snap b/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-poetry-with-hashes.txt.snap index 318f3047d..25e816bb5 100644 --- a/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-poetry-with-hashes.txt.snap +++ b/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-poetry-with-hashes.txt.snap @@ -56,7 +56,6 @@ RequirementsTxt { hashes: [ "sha256:2e1ccc9417d4da358b9de6f174e3ac094391ea1d4fbef2d667865d819dfd0afe", ], - editable: false, }, RequirementEntry { requirement: Pep508( @@ -110,7 +109,6 @@ RequirementsTxt { hashes: [ "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305", ], - editable: false, }, RequirementEntry { requirement: Pep508( @@ -175,7 +173,6 @@ RequirementsTxt { hashes: [ "sha256:e4d039def5768a47e4afec8e89e83ec3ae5a26bf00ad851f914d1240b444d2b1", ], - editable: false, }, RequirementEntry { requirement: Pep508( @@ -230,7 +227,6 @@ RequirementsTxt { "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5", "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a", ], - editable: false, }, RequirementEntry { requirement: Pep508( @@ -287,7 +283,6 @@ RequirementsTxt { "sha256:1a5c7d7d577e0eabfcf15eb87d1e19314c8c4f0e722a301f98e0e3a65e238b4e", "sha256:1e5a38aa85bd660c53947bd28aeaafb6a97d70423606f1ccb044a03a1203fe4a", ], - editable: false, }, ], constraints: [], diff --git a/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-small.txt.snap b/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-small.txt.snap index 717c063d4..bc4cb551f 100644 --- a/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-small.txt.snap +++ b/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-small.txt.snap @@ -27,7 +27,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, RequirementEntry { requirement: Pep508( @@ -52,7 +51,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, ], constraints: [], diff --git a/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-whitespace.txt.snap b/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-whitespace.txt.snap index e8a8a5090..ede00ad21 100644 --- a/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-whitespace.txt.snap +++ b/crates/requirements-txt/src/snapshots/requirements_txt__test__line-endings-whitespace.txt.snap @@ -16,7 +16,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, RequirementEntry { requirement: Pep508( @@ -57,7 +56,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, ], constraints: [], diff --git a/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-basic.txt.snap b/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-basic.txt.snap index aeefa208a..1ea308583 100644 --- a/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-basic.txt.snap +++ b/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-basic.txt.snap @@ -27,7 +27,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, RequirementEntry { requirement: Pep508( @@ -52,7 +51,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, RequirementEntry { requirement: Pep508( @@ -77,7 +75,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, RequirementEntry { requirement: Pep508( @@ -102,7 +99,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, RequirementEntry { requirement: Pep508( @@ -127,7 +123,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, RequirementEntry { requirement: Pep508( @@ -152,7 +147,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, ], constraints: [], diff --git a/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-constraints-a.txt.snap b/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-constraints-a.txt.snap index 3bae2fc6f..eed853303 100644 --- a/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-constraints-a.txt.snap +++ b/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-constraints-a.txt.snap @@ -27,7 +27,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, ], constraints: [ diff --git a/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-constraints-b.txt.snap b/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-constraints-b.txt.snap index 8c311f4b9..2ac70d195 100644 --- a/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-constraints-b.txt.snap +++ b/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-constraints-b.txt.snap @@ -27,7 +27,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, RequirementEntry { requirement: Pep508( @@ -52,7 +51,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, ], constraints: [], diff --git a/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-for-poetry.txt.snap b/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-for-poetry.txt.snap index 513d09259..ff6db6eab 100644 --- a/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-for-poetry.txt.snap +++ b/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-for-poetry.txt.snap @@ -27,7 +27,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, RequirementEntry { requirement: Pep508( @@ -52,7 +51,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, RequirementEntry { requirement: Pep508( @@ -66,7 +64,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, RequirementEntry { requirement: Pep508( @@ -99,7 +96,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, ], constraints: [], diff --git a/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-include-a.txt.snap b/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-include-a.txt.snap index f14751af9..82edeab41 100644 --- a/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-include-a.txt.snap +++ b/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-include-a.txt.snap @@ -16,7 +16,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, RequirementEntry { requirement: Pep508( @@ -41,7 +40,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, ], constraints: [], diff --git a/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-include-b.txt.snap b/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-include-b.txt.snap index 07f69a01f..745bacabc 100644 --- a/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-include-b.txt.snap +++ b/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-include-b.txt.snap @@ -16,7 +16,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, ], constraints: [], diff --git a/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-poetry-with-hashes.txt.snap b/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-poetry-with-hashes.txt.snap index 318f3047d..25e816bb5 100644 --- a/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-poetry-with-hashes.txt.snap +++ b/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-poetry-with-hashes.txt.snap @@ -56,7 +56,6 @@ RequirementsTxt { hashes: [ "sha256:2e1ccc9417d4da358b9de6f174e3ac094391ea1d4fbef2d667865d819dfd0afe", ], - editable: false, }, RequirementEntry { requirement: Pep508( @@ -110,7 +109,6 @@ RequirementsTxt { hashes: [ "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305", ], - editable: false, }, RequirementEntry { requirement: Pep508( @@ -175,7 +173,6 @@ RequirementsTxt { hashes: [ "sha256:e4d039def5768a47e4afec8e89e83ec3ae5a26bf00ad851f914d1240b444d2b1", ], - editable: false, }, RequirementEntry { requirement: Pep508( @@ -230,7 +227,6 @@ RequirementsTxt { "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5", "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a", ], - editable: false, }, RequirementEntry { requirement: Pep508( @@ -287,7 +283,6 @@ RequirementsTxt { "sha256:1a5c7d7d577e0eabfcf15eb87d1e19314c8c4f0e722a301f98e0e3a65e238b4e", "sha256:1e5a38aa85bd660c53947bd28aeaafb6a97d70423606f1ccb044a03a1203fe4a", ], - editable: false, }, ], constraints: [], diff --git a/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-small.txt.snap b/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-small.txt.snap index 717c063d4..bc4cb551f 100644 --- a/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-small.txt.snap +++ b/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-small.txt.snap @@ -27,7 +27,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, RequirementEntry { requirement: Pep508( @@ -52,7 +51,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, ], constraints: [], diff --git a/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-unix-bare-url.txt.snap b/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-unix-bare-url.txt.snap index 1e71a4be4..6ebf240d1 100644 --- a/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-unix-bare-url.txt.snap +++ b/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-unix-bare-url.txt.snap @@ -28,7 +28,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, RequirementEntry { requirement: Unnamed( @@ -58,7 +57,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, RequirementEntry { requirement: Unnamed( @@ -84,7 +82,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, ], constraints: [], diff --git a/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-whitespace.txt.snap b/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-whitespace.txt.snap index e8a8a5090..ede00ad21 100644 --- a/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-whitespace.txt.snap +++ b/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-whitespace.txt.snap @@ -16,7 +16,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, RequirementEntry { requirement: Pep508( @@ -57,7 +56,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, ], constraints: [], diff --git a/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-windows-bare-url.txt.snap b/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-windows-bare-url.txt.snap index d78405047..0f05ef8ea 100644 --- a/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-windows-bare-url.txt.snap +++ b/crates/requirements-txt/src/snapshots/requirements_txt__test__parse-windows-bare-url.txt.snap @@ -28,7 +28,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, RequirementEntry { requirement: Unnamed( @@ -58,7 +57,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, RequirementEntry { requirement: Unnamed( @@ -84,7 +82,6 @@ RequirementsTxt { }, ), hashes: [], - editable: false, }, ], constraints: [], diff --git a/crates/uv-auth/Cargo.toml b/crates/uv-auth/Cargo.toml index 0e4851fe1..87677f03d 100644 --- a/crates/uv-auth/Cargo.toml +++ b/crates/uv-auth/Cargo.toml @@ -7,15 +7,15 @@ edition = "2021" async-trait = { workspace = true } base64 = { workspace = true } clap = { workspace = true, features = ["derive", "env"], optional = true } +http = { workspace = true } +once_cell = { workspace = true } reqwest = { workspace = true } reqwest-middleware = { workspace = true } rust-netrc = { workspace = true } -task-local-extensions = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } url = { workspace = true } urlencoding = { workspace = true } -once_cell = { workspace = true } [dev-dependencies] tempfile = { workspace = true } diff --git a/crates/uv-auth/src/middleware.rs b/crates/uv-auth/src/middleware.rs index d21ca7847..5d5e14c4a 100644 --- a/crates/uv-auth/src/middleware.rs +++ b/crates/uv-auth/src/middleware.rs @@ -1,9 +1,9 @@ +use http::Extensions; use std::path::Path; use netrc::Netrc; use reqwest::{header::HeaderValue, Request, Response}; use reqwest_middleware::{Middleware, Next}; -use task_local_extensions::Extensions; use tracing::{debug, warn}; use crate::{ diff --git a/crates/uv-build/Cargo.toml b/crates/uv-build/Cargo.toml index 1bfb137d7..2d8f70278 100644 --- a/crates/uv-build/Cargo.toml +++ b/crates/uv-build/Cargo.toml @@ -20,6 +20,7 @@ pep508_rs = { workspace = true } uv-fs = { workspace = true } uv-interpreter = { workspace = true } uv-types = { workspace = true, features = ["serde"] } +uv-configuration = { workspace = true, features = ["serde"] } uv-virtualenv = { workspace = true } anyhow = { workspace = true } diff --git a/crates/uv-build/src/lib.rs b/crates/uv-build/src/lib.rs index 0606c371f..7d8ff3795 100644 --- a/crates/uv-build/src/lib.rs +++ b/crates/uv-build/src/lib.rs @@ -28,11 +28,10 @@ use tracing::{debug, info_span, instrument, Instrument}; use distribution_types::Resolution; use pep440_rs::Version; use pep508_rs::{PackageName, Requirement}; +use uv_configuration::{BuildKind, ConfigSettings, SetupPyStrategy}; use uv_fs::{PythonExt, Simplified}; use uv_interpreter::{Interpreter, PythonEnvironment}; -use uv_types::{ - BuildContext, BuildIsolation, BuildKind, ConfigSettings, SetupPyStrategy, SourceBuildTrait, -}; +use uv_types::{BuildContext, BuildIsolation, SourceBuildTrait}; /// e.g. `pygraphviz/graphviz_wrap.c:3020:10: fatal error: graphviz/cgraph.h: No such file or directory` static MISSING_HEADER_RE: Lazy = Lazy::new(|| { @@ -113,7 +112,7 @@ pub enum MissingLibrary { #[derive(Debug, Error)] pub struct MissingHeaderCause { missing_library: MissingLibrary, - package_id: String, + version_id: String, } impl Display for MissingHeaderCause { @@ -123,22 +122,22 @@ impl Display for MissingHeaderCause { write!( f, "This error likely indicates that you need to install a library that provides \"{}\" for {}", - header, self.package_id + header, self.version_id ) } MissingLibrary::Linker(library) => { write!( f, "This error likely indicates that you need to install the library that provides a shared library \ - for {library} for {package_id} (e.g. lib{library}-dev)", - library = library, package_id = self.package_id + for {library} for {version_id} (e.g. lib{library}-dev)", + library = library, version_id = self.version_id ) } MissingLibrary::PythonPackage(package) => { write!( f, - "This error likely indicates that you need to `uv pip install {package}` into the build environment for {package_id}", - package = package, package_id = self.package_id + "This error likely indicates that you need to `uv pip install {package}` into the build environment for {version_id}", + package = package, version_id = self.version_id ) } } @@ -149,7 +148,7 @@ impl Error { fn from_command_output( message: String, output: &Output, - package_id: impl Into, + version_id: impl Into, ) -> Self { let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string(); let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string(); @@ -179,7 +178,7 @@ impl Error { stderr, missing_header_cause: MissingHeaderCause { missing_library, - package_id: package_id.into(), + version_id: version_id.into(), }, }; } @@ -365,7 +364,7 @@ pub struct SourceBuild { /// > it created. metadata_directory: Option, /// Package id such as `foo-1.2.3`, for error reporting - package_id: String, + version_id: String, /// Whether we do a regular PEP 517 build or an PEP 660 editable build build_kind: BuildKind, /// Modified PATH that contains the `venv_bin`, `user_path` and `system_path` variables in that order @@ -386,7 +385,7 @@ impl SourceBuild { interpreter: &Interpreter, build_context: &impl BuildContext, source_build_context: SourceBuildContext, - package_id: String, + version_id: String, setup_py: SetupPyStrategy, config_settings: ConfigSettings, build_isolation: BuildIsolation<'_>, @@ -478,7 +477,7 @@ impl SourceBuild { &venv, pep517_backend, build_context, - &package_id, + &version_id, build_kind, &config_settings, &environment_variables, @@ -498,7 +497,7 @@ impl SourceBuild { build_kind, config_settings, metadata_directory: None, - package_id, + version_id, environment_variables, modified_path, }) @@ -696,7 +695,7 @@ impl SourceBuild { return Err(Error::from_command_output( "Build backend failed to determine metadata through `prepare_metadata_for_build_wheel`".to_string(), &output, - &self.package_id, + &self.version_id, )); } @@ -715,8 +714,8 @@ impl SourceBuild { /// dir. /// /// - #[instrument(skip_all, fields(package_id = self.package_id))] - pub async fn build(&self, wheel_dir: &Path) -> Result { + #[instrument(skip_all, fields(version_id = self.version_id))] + pub async fn build_wheel(&self, wheel_dir: &Path) -> Result { // The build scripts run with the extracted root as cwd, so they need the absolute path. let wheel_dir = fs::canonicalize(wheel_dir)?; @@ -751,7 +750,7 @@ impl SourceBuild { return Err(Error::from_command_output( "Failed building wheel through setup.py".to_string(), &output, - &self.package_id, + &self.version_id, )); } let dist = fs::read_dir(self.source_tree.join("dist"))?; @@ -762,7 +761,7 @@ impl SourceBuild { "Expected exactly wheel in `dist/` after invoking setup.py, found {dist_dir:?}" ), &output, - &self.package_id) + &self.version_id) ); }; @@ -832,7 +831,7 @@ impl SourceBuild { self.build_kind ), &output, - &self.package_id, + &self.version_id, )); } @@ -844,7 +843,7 @@ impl SourceBuild { self.build_kind ), &output, - &self.package_id, + &self.version_id, )); } Ok(distribution_filename) @@ -857,7 +856,7 @@ impl SourceBuildTrait for SourceBuild { } async fn wheel<'a>(&'a self, wheel_dir: &'a Path) -> anyhow::Result { - Ok(self.build(wheel_dir).await?) + Ok(self.build_wheel(wheel_dir).await?) } } @@ -874,7 +873,7 @@ async fn create_pep517_build_environment( venv: &PythonEnvironment, pep517_backend: &Pep517Backend, build_context: &impl BuildContext, - package_id: &str, + version_id: &str, build_kind: BuildKind, config_settings: &ConfigSettings, environment_variables: &FxHashMap, @@ -928,7 +927,7 @@ async fn create_pep517_build_environment( return Err(Error::from_command_output( format!("Build backend failed to determine extra requires with `build_{build_kind}()`"), &output, - package_id, + version_id, )); } @@ -939,7 +938,7 @@ async fn create_pep517_build_environment( "Build backend failed to read extra requires from `get_requires_for_build_{build_kind}`: {err}" ), &output, - package_id, + version_id, ) })?; @@ -950,7 +949,7 @@ async fn create_pep517_build_environment( "Build backend failed to return extra requires with `get_requires_for_build_{build_kind}`: {err}" ), &output, - package_id, + version_id, ) })?; diff --git a/crates/uv-cache/src/archive.rs b/crates/uv-cache/src/archive.rs new file mode 100644 index 000000000..31243f25c --- /dev/null +++ b/crates/uv-cache/src/archive.rs @@ -0,0 +1,24 @@ +use std::path::Path; + +/// A unique identifier for an archive (unzipped wheel) in the cache. +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct ArchiveId(String); + +impl Default for ArchiveId { + fn default() -> Self { + Self::new() + } +} + +impl ArchiveId { + /// Generate a new unique identifier for an archive. + pub fn new() -> Self { + Self(nanoid::nanoid!()) + } +} + +impl AsRef for ArchiveId { + fn as_ref(&self) -> &Path { + self.0.as_ref() + } +} diff --git a/crates/uv-cache/src/lib.rs b/crates/uv-cache/src/lib.rs index 6aaea35a1..5c07c3902 100644 --- a/crates/uv-cache/src/lib.rs +++ b/crates/uv-cache/src/lib.rs @@ -23,7 +23,9 @@ use crate::removal::{rm_rf, Removal}; pub use crate::timestamp::Timestamp; pub use crate::wheel::WheelCache; use crate::wheel::WheelCacheKind; +pub use archive::ArchiveId; +mod archive; mod by_timestamp; #[cfg(feature = "clap")] mod cli; @@ -71,6 +73,12 @@ impl CacheEntry { } } +impl AsRef for CacheEntry { + fn as_ref(&self) -> &Path { + &self.0 + } +} + /// A subdirectory within the cache. #[derive(Debug, Clone)] pub struct CacheShard(PathBuf); @@ -167,6 +175,11 @@ impl Cache { CacheEntry::new(self.bucket(cache_bucket).join(dir), file) } + /// Return the path to an archive in the cache. + pub fn archive(&self, id: &ArchiveId) -> PathBuf { + self.bucket(CacheBucket::Archive).join(id) + } + /// Returns `true` if a cache entry must be revalidated given the [`Refresh`] policy. pub fn must_revalidate(&self, package: &PackageName) -> bool { match &self.refresh { @@ -208,18 +221,18 @@ impl Cache { } } - /// Persist a temporary directory to the artifact store. + /// Persist a temporary directory to the artifact store, returning its unique ID. pub async fn persist( &self, temp_dir: impl AsRef, path: impl AsRef, - ) -> io::Result { + ) -> io::Result { // Create a unique ID for the artifact. // TODO(charlie): Support content-addressed persistence via SHAs. - let id = nanoid::nanoid!(); + let id = ArchiveId::new(); // Move the temporary directory into the directory store. - let archive_entry = self.entry(CacheBucket::Archive, "", id); + let archive_entry = self.entry(CacheBucket::Archive, "", &id); fs_err::create_dir_all(archive_entry.dir())?; uv_fs::rename_with_retry(temp_dir.as_ref(), archive_entry.path()).await?; @@ -227,7 +240,7 @@ impl Cache { fs_err::create_dir_all(path.as_ref().parent().expect("Cache entry to have parent"))?; uv_fs::replace_symlink(archive_entry.path(), path.as_ref())?; - Ok(archive_entry.into_path_buf()) + Ok(id) } /// Initialize a directory for use as a cache. @@ -594,12 +607,12 @@ pub enum CacheBucket { impl CacheBucket { fn to_str(self) -> &'static str { match self { - Self::BuiltWheels => "built-wheels-v2", + Self::BuiltWheels => "built-wheels-v3", Self::FlatIndex => "flat-index-v0", Self::Git => "git-v0", Self::Interpreter => "interpreter-v0", - Self::Simple => "simple-v6", - Self::Wheels => "wheels-v0", + Self::Simple => "simple-v7", + Self::Wheels => "wheels-v1", Self::Archive => "archive-v0", } } @@ -795,6 +808,12 @@ impl ArchiveTimestamp { } } + /// Return the modification timestamp for a file. + pub fn from_file(path: impl AsRef) -> Result { + let metadata = fs_err::metadata(path.as_ref())?; + Ok(Self::Exact(Timestamp::from_metadata(&metadata))) + } + /// Return the modification timestamp for an archive. pub fn timestamp(&self) -> Timestamp { match self { diff --git a/crates/uv-client/Cargo.toml b/crates/uv-client/Cargo.toml index 0d89d290b..bd8b7521d 100644 --- a/crates/uv-client/Cargo.toml +++ b/crates/uv-client/Cargo.toml @@ -11,13 +11,14 @@ install-wheel-rs = { workspace = true } pep440_rs = { workspace = true } pep508_rs = { workspace = true } platform-tags = { workspace = true } +pypi-types = { workspace = true } uv-auth = { workspace = true } uv-cache = { workspace = true } +uv-configuration = { workspace = true } uv-fs = { workspace = true, features = ["tokio"] } uv-normalize = { workspace = true } uv-version = { workspace = true } uv-warnings = { workspace = true } -pypi-types = { workspace = true } anyhow = { workspace = true } async-trait = { workspace = true } @@ -33,11 +34,9 @@ reqwest-middleware = { workspace = true } reqwest-retry = { workspace = true } rkyv = { workspace = true } rmp-serde = { workspace = true } -rustc-hash = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } sys-info = { workspace = true } -task-local-extensions = { workspace = true } tempfile = { workspace = true } thiserror = { workspace = true } tl = { workspace = true } @@ -47,14 +46,10 @@ tracing = { workspace = true } url = { workspace = true } urlencoding = { workspace = true } -# These must be kept in-sync with those used by `reqwest`. -rustls = { version = "0.21.10" } -rustls-native-certs = { version = "0.6.3" } -webpki-roots = { version = "0.25.4" } - [dev-dependencies] anyhow = { workspace = true } -hyper = { version = "0.14.28", features = ["server", "http1"] } -insta = { version = "1.36.1" } -os_info = { version = "3.7.0", default-features = false } +http-body-util = { version = "0.1.0" } +hyper = { version = "1.2.0", features = ["server", "http1"] } +hyper-util = { version = "0.1.3", features = ["tokio"] } +insta = { version = "1.36.1" , features = ["filters", "json", "redactions"] } tokio = { workspace = true, features = ["fs", "macros"] } diff --git a/crates/uv-client/src/base_client.rs b/crates/uv-client/src/base_client.rs index c8a9f424d..fffcf0848 100644 --- a/crates/uv-client/src/base_client.rs +++ b/crates/uv-client/src/base_client.rs @@ -16,10 +16,9 @@ use uv_warnings::warn_user_once; use crate::linehaul::LineHaul; use crate::middleware::OfflineMiddleware; -use crate::tls::Roots; -use crate::{tls, Connectivity}; +use crate::Connectivity; -/// A builder for an [`RegistryClient`]. +/// A builder for an [`BaseClient`]. #[derive(Debug, Clone)] pub struct BaseClientBuilder<'a> { keyring_provider: KeyringProvider, @@ -140,19 +139,20 @@ impl<'a> BaseClientBuilder<'a> { } path_exists }); - // Load the TLS configuration. - let tls = tls::load(if self.native_tls || ssl_cert_file_exists { - Roots::Native - } else { - Roots::Webpki - }) - .expect("Failed to load TLS configuration."); + // Configure the builder. let client_core = ClientBuilder::new() .user_agent(user_agent_string) .pool_max_idle_per_host(20) .timeout(std::time::Duration::from_secs(timeout)) - .use_preconfigured_tls(tls); + .tls_built_in_root_certs(false); + + // Configure TLS. + let client_core = if self.native_tls || ssl_cert_file_exists { + client_core.tls_built_in_native_certs(true) + } else { + client_core.tls_built_in_webpki_certs(true) + }; client_core.build().expect("Failed to build HTTP client.") }); diff --git a/crates/uv-client/src/cached_client.rs b/crates/uv-client/src/cached_client.rs index a80726e8b..672785328 100644 --- a/crates/uv-client/src/cached_client.rs +++ b/crates/uv-client/src/cached_client.rs @@ -299,6 +299,34 @@ impl CachedClient { } } + /// Make a request without checking whether the cache is fresh. + pub async fn skip_cache< + Payload: Serialize + DeserializeOwned + Send + 'static, + CallBackError, + Callback, + CallbackReturn, + >( + &self, + req: Request, + cache_entry: &CacheEntry, + response_callback: Callback, + ) -> Result> + where + Callback: FnOnce(Response) -> CallbackReturn + Send, + CallbackReturn: Future> + Send, + { + let (response, cache_policy) = self.fresh_request(req).await?; + + let payload = self + .run_response_callback(cache_entry, cache_policy, response, move |resp| async { + let payload = response_callback(resp).await?; + Ok(SerdeCacheable { inner: payload }) + }) + .await?; + + Ok(payload) + } + async fn resend_and_heal_cache( &self, req: Request, diff --git a/crates/uv-client/src/error.rs b/crates/uv-client/src/error.rs index 38bc66bad..af69e27a0 100644 --- a/crates/uv-client/src/error.rs +++ b/crates/uv-client/src/error.rs @@ -132,7 +132,7 @@ pub enum ErrorKind { /// Dist-info error #[error(transparent)] - InstallWheel(#[from] install_wheel_rs::Error), + DistInfo(#[from] install_wheel_rs::Error), #[error("{0} isn't available locally, but making network requests to registries was banned.")] NoIndex(String), @@ -146,7 +146,11 @@ pub enum ErrorKind { /// The metadata file could not be parsed. #[error("Couldn't parse metadata of {0} from {1}")] - MetadataParseError(WheelFilename, String, #[source] Box), + MetadataParseError( + WheelFilename, + String, + #[source] Box, + ), /// The metadata file was not found in the wheel. #[error("Metadata file `{0}` was not found in {1}")] diff --git a/crates/uv-client/src/flat_index.rs b/crates/uv-client/src/flat_index.rs index a042f4f10..a468cc640 100644 --- a/crates/uv-client/src/flat_index.rs +++ b/crates/uv-client/src/flat_index.rs @@ -1,24 +1,14 @@ -use std::collections::btree_map::Entry; -use std::collections::BTreeMap; use std::path::PathBuf; use futures::{FutureExt, StreamExt}; use reqwest::Response; -use rustc_hash::FxHashMap; -use tracing::{debug, info_span, instrument, warn, Instrument}; +use tracing::{debug, info_span, warn, Instrument}; use url::Url; use distribution_filename::DistFilename; -use distribution_types::{ - BuiltDist, Dist, File, FileLocation, FlatIndexLocation, IndexUrl, PrioritizedDist, - RegistryBuiltDist, RegistrySourceDist, SourceDist, SourceDistCompatibility, -}; -use pep440_rs::Version; +use distribution_types::{File, FileLocation, FlatIndexLocation, IndexUrl}; use pep508_rs::VerbatimUrl; -use platform_tags::Tags; -use pypi_types::Hashes; use uv_cache::{Cache, CacheBucket}; -use uv_normalize::PackageName; use crate::cached_client::{CacheControl, CachedClientError}; use crate::html::SimpleHtml; @@ -36,10 +26,10 @@ pub enum FlatIndexError { #[derive(Debug, Default, Clone)] pub struct FlatIndexEntries { /// The list of `--find-links` entries. - entries: Vec<(DistFilename, File, IndexUrl)>, + pub entries: Vec<(DistFilename, File, IndexUrl)>, /// Whether any `--find-links` entries could not be resolved due to a lack of network /// connectivity. - offline: bool, + pub offline: bool, } impl FlatIndexEntries { @@ -215,7 +205,7 @@ impl<'a> FlatIndexClient<'a> { fn read_from_directory(path: &PathBuf) -> Result { // Absolute paths are required for the URL conversion. let path = fs_err::canonicalize(path)?; - let index_url = IndexUrl::Url(VerbatimUrl::from_path(&path)); + let index_url = IndexUrl::Path(VerbatimUrl::from_path(&path)); let mut dists = Vec::new(); for entry in fs_err::read_dir(path)? { @@ -234,9 +224,9 @@ impl<'a> FlatIndexClient<'a> { }; let file = File { - dist_info_metadata: None, + dist_info_metadata: false, filename: filename.to_string(), - hashes: Hashes::default(), + hashes: Vec::new(), requires_python: None, size: None, upload_time_utc_ms: None, @@ -256,132 +246,3 @@ impl<'a> FlatIndexClient<'a> { Ok(FlatIndexEntries::from_entries(dists)) } } - -/// A set of [`PrioritizedDist`] from a `--find-links` entry, indexed by [`PackageName`] -/// and [`Version`]. -#[derive(Debug, Clone, Default)] -pub struct FlatIndex { - /// The list of [`FlatDistributions`] from the `--find-links` entries, indexed by package name. - index: FxHashMap, - /// Whether any `--find-links` entries could not be resolved due to a lack of network - /// connectivity. - offline: bool, -} - -impl FlatIndex { - /// Collect all files from a `--find-links` target into a [`FlatIndex`]. - #[instrument(skip_all)] - pub fn from_entries(entries: FlatIndexEntries, tags: &Tags) -> Self { - // Collect compatible distributions. - let mut index = FxHashMap::default(); - for (filename, file, url) in entries.entries { - let distributions = index.entry(filename.name().clone()).or_default(); - Self::add_file(distributions, file, filename, tags, url); - } - - // Collect offline entries. - let offline = entries.offline; - - Self { index, offline } - } - - fn add_file( - distributions: &mut FlatDistributions, - file: File, - filename: DistFilename, - tags: &Tags, - index: IndexUrl, - ) { - // No `requires-python` here: for source distributions, we don't have that information; - // for wheels, we read it lazily only when selected. - match filename { - DistFilename::WheelFilename(filename) => { - let compatibility = filename.compatibility(tags); - let version = filename.version.clone(); - - let dist = Dist::Built(BuiltDist::Registry(RegistryBuiltDist { - filename, - file: Box::new(file), - index, - })); - match distributions.0.entry(version) { - Entry::Occupied(mut entry) => { - entry - .get_mut() - .insert_built(dist, None, compatibility.into()); - } - Entry::Vacant(entry) => { - entry.insert(PrioritizedDist::from_built( - dist, - None, - compatibility.into(), - )); - } - } - } - DistFilename::SourceDistFilename(filename) => { - let dist = Dist::Source(SourceDist::Registry(RegistrySourceDist { - filename: filename.clone(), - file: Box::new(file), - index, - })); - match distributions.0.entry(filename.version) { - Entry::Occupied(mut entry) => { - entry.get_mut().insert_source( - dist, - None, - SourceDistCompatibility::Compatible, - ); - } - Entry::Vacant(entry) => { - entry.insert(PrioritizedDist::from_source( - dist, - None, - SourceDistCompatibility::Compatible, - )); - } - } - } - } - } - - /// Get the [`FlatDistributions`] for the given package name. - pub fn get(&self, package_name: &PackageName) -> Option<&FlatDistributions> { - self.index.get(package_name) - } - - /// Returns `true` if there are any offline `--find-links` entries. - pub fn offline(&self) -> bool { - self.offline - } -} - -/// A set of [`PrioritizedDist`] from a `--find-links` entry for a single package, indexed -/// by [`Version`]. -#[derive(Debug, Clone, Default)] -pub struct FlatDistributions(BTreeMap); - -impl FlatDistributions { - pub fn iter(&self) -> impl Iterator { - self.0.iter() - } - - pub fn remove(&mut self, version: &Version) -> Option { - self.0.remove(version) - } -} - -impl IntoIterator for FlatDistributions { - type Item = (Version, PrioritizedDist); - type IntoIter = std::collections::btree_map::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl From for BTreeMap { - fn from(distributions: FlatDistributions) -> Self { - distributions.0 - } -} diff --git a/crates/uv-client/src/html.rs b/crates/uv-client/src/html.rs index 2bffce9b6..5d315c6d4 100644 --- a/crates/uv-client/src/html.rs +++ b/crates/uv-client/src/html.rs @@ -164,6 +164,9 @@ impl SimpleHtml { .last() .ok_or_else(|| Error::MissingFilename(href.to_string()))?; + // Strip any query string from the filename. + let filename = filename.split('?').next().unwrap_or(filename); + // Unquote the filename. let filename = urlencoding::decode(filename) .map_err(|_| Error::UnsupportedFilename(filename.to_string()))?; @@ -681,6 +684,60 @@ mod tests { "###); } + #[test] + fn parse_query_string() { + let text = r#" + + + +

Links for jinja2

+ Jinja2-3.1.2-py3-none-any.whl
+ + + + "#; + let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap(); + 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: [ + File { + dist_info_metadata: None, + filename: "Jinja2-3.1.2-py3-none-any.whl", + hashes: Hashes { + md5: None, + sha256: None, + sha384: None, + sha512: None, + }, + requires_python: None, + size: None, + upload_time: None, + url: "/whl/Jinja2-3.1.2-py3-none-any.whl?project=legacy", + yanked: None, + }, + ], + } + "###); + } + #[test] fn parse_missing_hash_value() { let text = r#" diff --git a/crates/uv-client/src/lib.rs b/crates/uv-client/src/lib.rs index 62fef2313..24f842d8e 100644 --- a/crates/uv-client/src/lib.rs +++ b/crates/uv-client/src/lib.rs @@ -1,7 +1,7 @@ pub use base_client::{BaseClient, BaseClientBuilder}; pub use cached_client::{CacheControl, CachedClient, CachedClientError, DataWithCachePolicy}; pub use error::{BetterReqwestError, Error, ErrorKind}; -pub use flat_index::{FlatDistributions, FlatIndex, FlatIndexClient, FlatIndexError}; +pub use flat_index::{FlatIndexClient, FlatIndexEntries, FlatIndexError}; pub use linehaul::LineHaul; pub use registry_client::{ Connectivity, RegistryClient, RegistryClientBuilder, SimpleMetadata, SimpleMetadatum, @@ -20,4 +20,3 @@ mod middleware; mod registry_client; mod remote_metadata; mod rkyvutil; -mod tls; diff --git a/crates/uv-client/src/middleware.rs b/crates/uv-client/src/middleware.rs index e83d45547..37b991b3b 100644 --- a/crates/uv-client/src/middleware.rs +++ b/crates/uv-client/src/middleware.rs @@ -1,8 +1,8 @@ +use http::Extensions; use std::fmt::Debug; use reqwest::{Request, Response}; use reqwest_middleware::{Middleware, Next}; -use task_local_extensions::Extensions; use url::Url; /// A custom error type for the offline middleware. diff --git a/crates/uv-client/src/registry_client.rs b/crates/uv-client/src/registry_client.rs index 8ae51f6b2..5873058e0 100644 --- a/crates/uv-client/src/registry_client.rs +++ b/crates/uv-client/src/registry_client.rs @@ -9,7 +9,7 @@ use http::HeaderMap; use reqwest::{Client, Response, StatusCode}; use serde::{Deserialize, Serialize}; use tokio::io::AsyncReadExt; -use tokio_util::compat::FuturesAsyncReadCompatExt; +use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt}; use tracing::{info_span, instrument, trace, warn, Instrument}; use url::Url; @@ -22,6 +22,7 @@ use platform_tags::Platform; use pypi_types::{Metadata23, SimpleJson}; use uv_auth::KeyringProvider; use uv_cache::{Cache, CacheBucket, WheelCache}; +use uv_configuration::IndexStrategy; use uv_normalize::PackageName; use crate::base_client::{BaseClient, BaseClientBuilder}; @@ -35,6 +36,7 @@ use crate::{CachedClient, CachedClientError, Error, ErrorKind}; #[derive(Debug, Clone)] pub struct RegistryClientBuilder<'a> { index_urls: IndexUrls, + index_strategy: IndexStrategy, keyring_provider: KeyringProvider, native_tls: bool, retries: u32, @@ -49,6 +51,7 @@ impl RegistryClientBuilder<'_> { pub fn new(cache: Cache) -> Self { Self { index_urls: IndexUrls::default(), + index_strategy: IndexStrategy::default(), keyring_provider: KeyringProvider::default(), native_tls: false, cache, @@ -68,6 +71,12 @@ impl<'a> RegistryClientBuilder<'a> { self } + #[must_use] + pub fn index_strategy(mut self, index_strategy: IndexStrategy) -> Self { + self.index_strategy = index_strategy; + self + } + #[must_use] pub fn keyring_provider(mut self, keyring_provider: KeyringProvider) -> Self { self.keyring_provider = keyring_provider; @@ -147,6 +156,7 @@ impl<'a> RegistryClientBuilder<'a> { RegistryClient { index_urls: self.index_urls, + index_strategy: self.index_strategy, cache: self.cache, connectivity, client, @@ -160,6 +170,8 @@ impl<'a> RegistryClientBuilder<'a> { pub struct RegistryClient { /// The index URLs to use for fetching packages. index_urls: IndexUrls, + /// The strategy to use when fetching across multiple indexes. + index_strategy: IndexStrategy, /// The underlying HTTP client. client: CachedClient, /// Used for the remote wheel METADATA cache. @@ -206,17 +218,23 @@ impl RegistryClient { pub async fn simple( &self, package_name: &PackageName, - ) -> Result<(IndexUrl, OwnedArchive), Error> { + ) -> Result)>, Error> { let mut it = self.index_urls.indexes().peekable(); if it.peek().is_none() { return Err(ErrorKind::NoIndex(package_name.as_ref().to_string()).into()); } + let mut results = Vec::new(); for index in it { - let result = self.simple_single_index(package_name, index).await?; + match self.simple_single_index(package_name, index).await? { + Ok(metadata) => { + results.push((index.clone(), metadata)); - return match result { - Ok(metadata) => Ok((index.clone(), metadata)), + // If we're only using the first match, we can stop here. + if self.index_strategy == IndexStrategy::FirstMatch { + break; + } + } Err(CachedClientError::Client(err)) => match err.into_kind() { ErrorKind::Offline(_) => continue, ErrorKind::ReqwestError(err) => { @@ -225,20 +243,24 @@ impl RegistryClient { { continue; } - Err(ErrorKind::from(err).into()) + return Err(ErrorKind::from(err).into()); } - other => Err(other.into()), + other => return Err(other.into()), }, - Err(CachedClientError::Callback(err)) => Err(err), + Err(CachedClientError::Callback(err)) => return Err(err), }; } - match self.connectivity { - Connectivity::Online => { - Err(ErrorKind::PackageNotFound(package_name.to_string()).into()) - } - Connectivity::Offline => Err(ErrorKind::Offline(package_name.to_string()).into()), + if results.is_empty() { + return match self.connectivity { + Connectivity::Online => { + Err(ErrorKind::PackageNotFound(package_name.to_string()).into()) + } + Connectivity::Offline => Err(ErrorKind::Offline(package_name.to_string()).into()), + }; } + + Ok(results) } async fn simple_single_index( @@ -263,6 +285,7 @@ impl RegistryClient { Path::new(&match index { IndexUrl::Pypi(_) => "pypi".to_string(), IndexUrl::Url(url) => cache_key::digest(&cache_key::CanonicalUrl::new(url)), + IndexUrl::Path(url) => cache_key::digest(&cache_key::CanonicalUrl::new(url)), }), format!("{package_name}.rkyv"), ); @@ -402,11 +425,7 @@ impl RegistryClient { ) -> Result { // If the metadata file is available at its own url (PEP 658), download it from there. let filename = WheelFilename::from_str(&file.filename).map_err(ErrorKind::WheelFilename)?; - if file - .dist_info_metadata - .as_ref() - .is_some_and(pypi_types::DistInfoMetadata::is_available) - { + if file.dist_info_metadata { let mut url = url.clone(); url.set_path(&format!("{}.metadata", url.path())); @@ -596,7 +615,8 @@ async fn read_metadata_async_seek( debug_source: String, reader: impl tokio::io::AsyncRead + tokio::io::AsyncSeek + Unpin, ) -> Result { - let mut zip_reader = async_zip::tokio::read::seek::ZipFileReader::with_tokio(reader) + let reader = futures::io::BufReader::new(reader.compat()); + let mut zip_reader = async_zip::base::read::seek::ZipFileReader::new(reader) .await .map_err(|err| ErrorKind::Zip(filename.clone(), err))?; @@ -609,7 +629,7 @@ async fn read_metadata_async_seek( .enumerate() .filter_map(|(index, entry)| Some((index, entry.filename().as_str().ok()?))), ) - .map_err(ErrorKind::InstallWheel)?; + .map_err(ErrorKind::DistInfo)?; // Read the contents of the `METADATA` file. let mut contents = Vec::new(); @@ -633,6 +653,7 @@ async fn read_metadata_async_stream( debug_source: String, reader: R, ) -> Result { + let reader = futures::io::BufReader::with_capacity(128 * 1024, reader); let mut zip = async_zip::base::read::stream::ZipFileReader::new(reader); while let Some(mut entry) = zip diff --git a/crates/uv-client/src/remote_metadata.rs b/crates/uv-client/src/remote_metadata.rs index 548968e2f..954212588 100644 --- a/crates/uv-client/src/remote_metadata.rs +++ b/crates/uv-client/src/remote_metadata.rs @@ -1,5 +1,5 @@ use async_http_range_reader::AsyncHttpRangeReader; -use async_zip::tokio::read::seek::ZipFileReader; +use futures::io::BufReader; use tokio_util::compat::TokioAsyncReadCompatExt; use distribution_filename::WheelFilename; @@ -61,7 +61,8 @@ pub(crate) async fn wheel_metadata_from_remote_zip( .await; // Construct a zip reader to uses the stream. - let mut reader = ZipFileReader::new(reader.compat()) + let buf = BufReader::new(reader.compat()); + let mut reader = async_zip::base::read::seek::ZipFileReader::new(buf) .await .map_err(|err| ErrorKind::Zip(filename.clone(), err))?; @@ -74,7 +75,7 @@ pub(crate) async fn wheel_metadata_from_remote_zip( .enumerate() .filter_map(|(idx, e)| Some(((idx, e), e.filename().as_str().ok()?))), ) - .map_err(ErrorKind::InstallWheel)?; + .map_err(ErrorKind::DistInfo)?; let offset = metadata_entry.header_offset(); let size = metadata_entry.compressed_size() @@ -90,6 +91,7 @@ pub(crate) async fn wheel_metadata_from_remote_zip( reader .inner_mut() .get_mut() + .get_mut() .prefetch(offset..offset + size) .await; diff --git a/crates/uv-client/src/tls.rs b/crates/uv-client/src/tls.rs deleted file mode 100644 index 7118a4c05..000000000 --- a/crates/uv-client/src/tls.rs +++ /dev/null @@ -1,102 +0,0 @@ -use rustls::ClientConfig; -use tracing::warn; - -#[derive(thiserror::Error, Debug)] -pub(crate) enum TlsError { - #[error(transparent)] - Rustls(#[from] rustls::Error), - #[error("zero valid certificates found in native root store")] - ZeroCertificates, - #[error("failed to load native root certificates")] - NativeCertificates(#[source] std::io::Error), -} - -#[derive(Debug, Clone, Copy)] -pub(crate) enum Roots { - /// Use reqwest's `rustls-tls-webpki-roots` behavior for loading root certificates. - Webpki, - /// Use reqwest's `rustls-tls-native-roots` behavior for loading root certificates. - Native, -} - -/// Initialize a TLS configuration for the client. -/// -/// This is equivalent to the TLS initialization `reqwest` when `rustls-tls` is enabled, -/// with two notable changes: -/// -/// 1. It enables _either_ the `webpki-roots` or the `native-certs` feature, but not both. -/// 2. It assumes the following builder settings (which match the defaults): -/// - `root_certs: vec![]` -/// - `min_tls_version: None` -/// - `max_tls_version: None` -/// - `identity: None` -/// - `certs_verification: false` -/// - `tls_sni: true` -/// - `http_version_pref: HttpVersionPref::All` -/// -/// See: -pub(crate) fn load(roots: Roots) -> Result { - // Set root certificates. - let mut root_cert_store = rustls::RootCertStore::empty(); - - match roots { - Roots::Webpki => { - // Use `rustls-tls-webpki-roots` - use rustls::OwnedTrustAnchor; - - let trust_anchors = webpki_roots::TLS_SERVER_ROOTS.iter().map(|trust_anchor| { - OwnedTrustAnchor::from_subject_spki_name_constraints( - trust_anchor.subject, - trust_anchor.spki, - trust_anchor.name_constraints, - ) - }); - - root_cert_store.add_trust_anchors(trust_anchors); - } - Roots::Native => { - // Use: `rustls-tls-native-roots` - let mut valid_count = 0; - let mut invalid_count = 0; - for cert in - rustls_native_certs::load_native_certs().map_err(TlsError::NativeCertificates)? - { - let cert = rustls::Certificate(cert.0); - // Continue on parsing errors, as native stores often include ancient or syntactically - // invalid certificates, like root certificates without any X509 extensions. - // Inspiration: https://github.com/rustls/rustls/blob/633bf4ba9d9521a95f68766d04c22e2b01e68318/rustls/src/anchors.rs#L105-L112 - match root_cert_store.add(&cert) { - Ok(_) => valid_count += 1, - Err(err) => { - invalid_count += 1; - warn!( - "rustls failed to parse DER certificate {:?} {:?}", - &err, &cert - ); - } - } - } - if valid_count == 0 && invalid_count > 0 { - return Err(TlsError::ZeroCertificates); - } - } - } - - // Build TLS config - let config_builder = ClientConfig::builder() - .with_safe_default_cipher_suites() - .with_safe_default_kx_groups() - .with_protocol_versions(rustls::ALL_VERSIONS)? - .with_root_certificates(root_cert_store); - - // Finalize TLS config - let mut tls = config_builder.with_no_client_auth(); - - // Enable SNI - tls.enable_sni = true; - - // ALPN protocol - tls.alpn_protocols = vec!["h2".into(), "http/1.1".into()]; - - Ok(tls) -} diff --git a/crates/uv-client/tests/netrc_auth.rs b/crates/uv-client/tests/netrc_auth.rs index 4678f176b..8d657169f 100644 --- a/crates/uv-client/tests/netrc_auth.rs +++ b/crates/uv-client/tests/netrc_auth.rs @@ -3,10 +3,13 @@ use std::io::Write; use anyhow::Result; use futures::future; -use hyper::header::AUTHORIZATION; -use hyper::server::conn::Http; +use http::header::AUTHORIZATION; +use http_body_util::Full; +use hyper::body::Bytes; +use hyper::server::conn::http1; use hyper::service::service_fn; -use hyper::{Body, Request, Response}; +use hyper::{Request, Response}; +use hyper_util::rt::TokioIo; use tempfile::NamedTempFile; use tokio::net::TcpListener; @@ -21,7 +24,7 @@ async fn test_client_with_netrc_credentials() -> Result<()> { // Spawn the server loop in a background task tokio::spawn(async move { - let svc = service_fn(move |req: Request| { + let svc = service_fn(move |req: Request| { // Get User Agent Header and send it back in the response let auth = req .headers() @@ -29,16 +32,19 @@ async fn test_client_with_netrc_credentials() -> Result<()> { .and_then(|v| v.to_str().ok()) .map(|s| s.to_string()) .unwrap_or_default(); // Empty Default - future::ok::<_, hyper::Error>(Response::new(Body::from(auth))) + future::ok::<_, hyper::Error>(Response::new(Full::new(Bytes::from(auth)))) }); - // Start Hyper Server + // Start Server (not wrapped in loop {} since we want a single response server) + // If you want server to accept multiple connections, wrap it in loop {} let (socket, _) = listener.accept().await.unwrap(); - Http::new() - .http1_keep_alive(false) - .serve_connection(socket, svc) - .with_upgrades() - .await - .expect("Server Started"); + let socket = TokioIo::new(socket); + tokio::task::spawn(async move { + http1::Builder::new() + .serve_connection(socket, svc) + .with_upgrades() + .await + .expect("Server Started"); + }); }); // Create a netrc file diff --git a/crates/uv-client/tests/user_agent_version.rs b/crates/uv-client/tests/user_agent_version.rs index 6058ec929..2dd67f5f9 100644 --- a/crates/uv-client/tests/user_agent_version.rs +++ b/crates/uv-client/tests/user_agent_version.rs @@ -1,9 +1,13 @@ use anyhow::Result; use futures::future; +use http_body_util::Full; +use hyper::body::Bytes; use hyper::header::USER_AGENT; -use hyper::server::conn::Http; +use hyper::server::conn::http1; use hyper::service::service_fn; -use hyper::{Body, Request, Response}; +use hyper::{Request, Response}; +use hyper_util::rt::TokioIo; +use insta::{assert_json_snapshot, assert_snapshot, with_settings}; use pep508_rs::{MarkerEnvironment, StringVersion}; use platform_tags::{Arch, Os, Platform}; use tokio::net::TcpListener; @@ -19,8 +23,8 @@ async fn test_user_agent_has_version() -> Result<()> { let addr = listener.local_addr()?; // Spawn the server loop in a background task - tokio::spawn(async move { - let svc = service_fn(move |req: Request| { + let server_task = tokio::spawn(async move { + let svc = service_fn(move |req: Request| { // Get User Agent Header and send it back in the response let user_agent = req .headers() @@ -28,16 +32,19 @@ async fn test_user_agent_has_version() -> Result<()> { .and_then(|v| v.to_str().ok()) .map(|s| s.to_string()) .unwrap_or_default(); // Empty Default - future::ok::<_, hyper::Error>(Response::new(Body::from(user_agent))) + future::ok::<_, hyper::Error>(Response::new(Full::new(Bytes::from(user_agent)))) }); - // Start Hyper Server + // Start Server (not wrapped in loop {} since we want a single response server) + // If you want server to accept multiple connections, wrap it in loop {} let (socket, _) = listener.accept().await.unwrap(); - Http::new() - .http1_keep_alive(false) - .serve_connection(socket, svc) - .with_upgrades() - .await - .expect("Server Started"); + let socket = TokioIo::new(socket); + tokio::task::spawn(async move { + http1::Builder::new() + .serve_connection(socket, svc) + .with_upgrades() + .await + .expect("Server Started"); + }); }); // Initialize uv-client @@ -46,7 +53,8 @@ async fn test_user_agent_has_version() -> Result<()> { // Send request to our dummy server let res = client - .uncached_client() + .cached_client() + .uncached() .get(format!("http://{addr}")) .send() .await?; @@ -60,6 +68,9 @@ async fn test_user_agent_has_version() -> Result<()> { // Verify body matches regex assert_eq!(body, format!("uv/{}", version())); + // Wait for the server task to complete, to be a good citizen. + server_task.await?; + Ok(()) } @@ -70,8 +81,8 @@ async fn test_user_agent_has_linehaul() -> Result<()> { let addr = listener.local_addr()?; // Spawn the server loop in a background task - tokio::spawn(async move { - let svc = service_fn(move |req: Request| { + let server_task = tokio::spawn(async move { + let svc = service_fn(move |req: Request| { // Get User Agent Header and send it back in the response let user_agent = req .headers() @@ -79,16 +90,19 @@ async fn test_user_agent_has_linehaul() -> Result<()> { .and_then(|v| v.to_str().ok()) .map(|s| s.to_string()) .unwrap_or_default(); // Empty Default - future::ok::<_, hyper::Error>(Response::new(Body::from(user_agent))) + future::ok::<_, hyper::Error>(Response::new(Full::new(Bytes::from(user_agent)))) }); - // Start Hyper Server + // Start Server (not wrapped in loop {} since we want a single response server) + // If you want server to accept multiple connections, wrap it in loop {} let (socket, _) = listener.accept().await.unwrap(); - Http::new() - .http1_keep_alive(false) - .serve_connection(socket, svc) - .with_upgrades() - .await - .expect("Server Started"); + let socket = TokioIo::new(socket); + tokio::task::spawn(async move { + http1::Builder::new() + .serve_connection(socket, svc) + .with_upgrades() + .await + .expect("Server Started"); + }); }); // Add some representative markers for an Ubuntu CI runner @@ -142,7 +156,8 @@ async fn test_user_agent_has_linehaul() -> Result<()> { // Send request to our dummy server let res = client - .uncached_client() + .cached_client() + .uncached() .get(format!("http://{addr}")) .send() .await?; @@ -153,6 +168,9 @@ async fn test_user_agent_has_linehaul() -> Result<()> { // Check User Agent let body = res.text().await?; + // Wait for the server task to complete, to be a good citizen. + server_task.await?; + // Unpack User-Agent with linehaul let (uv_version, uv_linehaul) = body .split_once(' ') @@ -161,62 +179,82 @@ async fn test_user_agent_has_linehaul() -> Result<()> { // Deserializing Linehaul let linehaul: LineHaul = serde_json::from_str(uv_linehaul)?; - // Assert uv version - assert_eq!(uv_version, format!("uv/{}", version())); - - // Assert linehaul - let installer_info = linehaul.installer.unwrap(); - let system_info = linehaul.system.unwrap(); - let impl_info = linehaul.implementation.unwrap(); - - assert_eq!(installer_info.name.unwrap(), "uv".to_string()); - assert_eq!(installer_info.version.unwrap(), version()); - - assert_eq!(system_info.name.unwrap(), markers.platform_system); - assert_eq!(system_info.release.unwrap(), markers.platform_release); - - assert_eq!( - impl_info.name.unwrap(), - markers.platform_python_implementation - ); - assert_eq!( - impl_info.version.unwrap(), - markers.python_full_version.version.to_string() - ); - - assert_eq!( - linehaul.python.unwrap(), - markers.python_full_version.version.to_string() - ); - assert_eq!(linehaul.cpu.unwrap(), markers.platform_machine); - - assert_eq!(linehaul.openssl_version, None); - assert_eq!(linehaul.setuptools_version, None); - assert_eq!(linehaul.rustc_version, None); + // Assert linehaul user agent + let filters = vec![(version(), "[VERSION]")]; + with_settings!({ + filters => filters + }, { + // Assert uv version + assert_snapshot!(uv_version, @"uv/[VERSION]"); + // Assert linehaul json + assert_json_snapshot!(&linehaul, { + ".distro" => "[distro]", + ".ci" => "[ci]" + }, @r###" + { + "installer": { + "name": "uv", + "version": "[VERSION]" + }, + "python": "3.12.2", + "implementation": { + "name": "CPython", + "version": "3.12.2" + }, + "distro": "[distro]", + "system": { + "name": "Linux", + "release": "6.5.0-1016-azure" + }, + "cpu": "x86_64", + "openssl_version": null, + "setuptools_version": null, + "rustc_version": null, + "ci": "[ci]" + } + "###); + }); + // Assert distro if cfg!(windows) { - assert_eq!(linehaul.distro, None); + assert_json_snapshot!(&linehaul.distro, @"null"); } else if cfg!(target_os = "linux") { - // Using `os_info` to confirm our values are as expected in Linux - let info = os_info::get(); - let Some(distro_info) = linehaul.distro else { - panic!("got no distro, but expected one in linehaul") - }; - assert_eq!(distro_info.id.as_deref(), info.codename()); - if let Some(ref name) = distro_info.name { - assert_eq!(name, &info.os_type().to_string()); - } - if let Some(ref version) = distro_info.version { - assert_eq!(version, &info.version().to_string()); - } - assert!(distro_info.libc.is_some()); + assert_json_snapshot!(&linehaul.distro, { + ".id" => "[distro.id]", + ".name" => "[distro.name]", + ".version" => "[distro.version]" + // We mock the libc version already + }, @r###" + { + "name": "[distro.name]", + "version": "[distro.version]", + "id": "[distro.id]", + "libc": { + "lib": "glibc", + "version": "2.38" + } + }"### + ); + // Check dynamic values + let distro_info = linehaul + .distro + .expect("got no distro, but expected one in linehaul"); + // Gather distribution info from /etc/os-release. + let release_info = sys_info::linux_os_release() + .expect("got no os release info, but expected one in linux"); + assert_eq!(distro_info.id, release_info.version_codename); + assert_eq!(distro_info.name, release_info.name); + assert_eq!(distro_info.version, release_info.version_id); } else if cfg!(target_os = "macos") { - // We mock the macOS version - let distro_info = linehaul.distro.unwrap(); - assert_eq!(distro_info.id, None); - assert_eq!(distro_info.name.unwrap(), "macOS"); - assert_eq!(distro_info.version, Some("14.4".to_string())); - assert_eq!(distro_info.libc, None); + // We mock the macOS distro + assert_json_snapshot!(&linehaul.distro, @r###" + { + "name": "macOS", + "version": "14.4", + "id": null, + "libc": null + }"### + ); } Ok(()) diff --git a/crates/uv-configuration/Cargo.toml b/crates/uv-configuration/Cargo.toml new file mode 100644 index 000000000..8a176161e --- /dev/null +++ b/crates/uv-configuration/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "uv-configuration" +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 } + +[lints] +workspace = true + +[dependencies] +pep508_rs = { workspace = true } +uv-normalize = { workspace = true } + +anyhow = { workspace = true } +clap = { workspace = true, features = ["derive"], optional = true } +itertools = { workspace = true } +rustc-hash = { workspace = true } +serde = { workspace = true, optional = true } +serde_json = { workspace = true, optional = true } + +[features] +default = [] +serde = ["dep:serde", "dep:serde_json"] diff --git a/crates/uv-types/src/build_options.rs b/crates/uv-configuration/src/build_options.rs similarity index 87% rename from crates/uv-types/src/build_options.rs rename to crates/uv-configuration/src/build_options.rs index 81b646576..14c8677a7 100644 --- a/crates/uv-types/src/build_options.rs +++ b/crates/uv-configuration/src/build_options.rs @@ -1,24 +1,9 @@ use std::fmt::{Display, Formatter}; use pep508_rs::PackageName; -use uv_interpreter::PythonEnvironment; use crate::{PackageNameSpecifier, PackageNameSpecifiers}; -/// Whether to enforce build isolation when building source distributions. -#[derive(Debug, Copy, Clone)] -pub enum BuildIsolation<'a> { - Isolated, - Shared(&'a PythonEnvironment), -} - -impl<'a> BuildIsolation<'a> { - /// Returns `true` if build isolation is enforced. - pub fn is_isolated(&self) -> bool { - matches!(self, Self::Isolated) - } -} - /// The strategy to use when building source distributions that lack a `pyproject.toml`. #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] pub enum SetupPyStrategy { @@ -211,6 +196,29 @@ impl NoBuild { } } +#[derive(Debug, Default, Clone, Hash, Eq, PartialEq)] +#[cfg_attr(feature = "clap", derive(clap::ValueEnum))] +pub enum IndexStrategy { + /// Only use results from the first index that returns a match for a given package name. + /// + /// While this differs from pip's behavior, it's the default index strategy as it's the most + /// secure. + #[default] + FirstMatch, + /// Search for every package name across all indexes, exhausting the versions from the first + /// index before moving on to the next. + /// + /// In this strategy, we look for every package across all indexes. When resolving, we attempt + /// to use versions from the indexes in order, such that we exhaust all available versions from + /// the first index before moving on to the next. Further, if a version is found to be + /// incompatible in the first index, we do not reconsider that version in subsequent indexes, + /// even if the secondary index might contain compatible versions (e.g., variants of the same + /// versions with different ABI tags or Python version constraints). + /// + /// See: https://peps.python.org/pep-0708/ + UnsafeAnyMatch, +} + #[cfg(test)] mod tests { use std::str::FromStr; diff --git a/crates/uv-types/src/config_settings.rs b/crates/uv-configuration/src/config_settings.rs similarity index 99% rename from crates/uv-types/src/config_settings.rs rename to crates/uv-configuration/src/config_settings.rs index 7b0729aeb..c7cefc64a 100644 --- a/crates/uv-types/src/config_settings.rs +++ b/crates/uv-configuration/src/config_settings.rs @@ -40,6 +40,7 @@ enum ConfigSettingValue { /// /// See: #[derive(Debug, Default, Clone)] +#[cfg_attr(not(feature = "serde"), allow(dead_code))] pub struct ConfigSettings(BTreeMap); impl FromIterator for ConfigSettings { diff --git a/crates/uv-types/src/constraints.rs b/crates/uv-configuration/src/constraints.rs similarity index 100% rename from crates/uv-types/src/constraints.rs rename to crates/uv-configuration/src/constraints.rs diff --git a/crates/uv-configuration/src/lib.rs b/crates/uv-configuration/src/lib.rs new file mode 100644 index 000000000..665568f2b --- /dev/null +++ b/crates/uv-configuration/src/lib.rs @@ -0,0 +1,13 @@ +pub use build_options::*; +pub use config_settings::*; +pub use constraints::*; +pub use name_specifiers::*; +pub use overrides::*; +pub use package_options::*; + +mod build_options; +mod config_settings; +mod constraints; +mod name_specifiers; +mod overrides; +mod package_options; diff --git a/crates/uv-types/src/name_specifiers.rs b/crates/uv-configuration/src/name_specifiers.rs similarity index 100% rename from crates/uv-types/src/name_specifiers.rs rename to crates/uv-configuration/src/name_specifiers.rs diff --git a/crates/uv-types/src/overrides.rs b/crates/uv-configuration/src/overrides.rs similarity index 100% rename from crates/uv-types/src/overrides.rs rename to crates/uv-configuration/src/overrides.rs diff --git a/crates/uv-types/src/package_options.rs b/crates/uv-configuration/src/package_options.rs similarity index 100% rename from crates/uv-types/src/package_options.rs rename to crates/uv-configuration/src/package_options.rs diff --git a/crates/uv-dev/Cargo.toml b/crates/uv-dev/Cargo.toml index 7ad14c089..7f9d323c7 100644 --- a/crates/uv-dev/Cargo.toml +++ b/crates/uv-dev/Cargo.toml @@ -23,11 +23,14 @@ pep508_rs = { workspace = true } uv-build = { workspace = true } uv-cache = { workspace = true, features = ["clap"] } uv-client = { workspace = true } +uv-configuration = { workspace = true } uv-dispatch = { workspace = true } +uv-fs = { workspace = true } uv-installer = { workspace = true } uv-interpreter = { workspace = true } uv-normalize = { workspace = true } uv-resolver = { workspace = true } +uv-toolchain = { workspace = true } uv-types = { workspace = true } # Any dependencies that are exclusively used in `uv-dev` should be listed as non-workspace diff --git a/crates/uv-dev/src/build.rs b/crates/uv-dev/src/build.rs index 0a49a0883..ae6095086 100644 --- a/crates/uv-dev/src/build.rs +++ b/crates/uv-dev/src/build.rs @@ -9,14 +9,12 @@ use distribution_types::IndexLocations; use rustc_hash::FxHashMap; use uv_build::{SourceBuild, SourceBuildContext}; use uv_cache::{Cache, CacheArgs}; -use uv_client::{FlatIndex, RegistryClientBuilder}; +use uv_client::RegistryClientBuilder; +use uv_configuration::{BuildKind, ConfigSettings, NoBinary, NoBuild, SetupPyStrategy}; use uv_dispatch::BuildDispatch; use uv_interpreter::PythonEnvironment; -use uv_resolver::InMemoryIndex; -use uv_types::NoBinary; -use uv_types::{ - BuildContext, BuildIsolation, BuildKind, ConfigSettings, InFlight, NoBuild, SetupPyStrategy, -}; +use uv_resolver::{FlatIndex, InMemoryIndex}; +use uv_types::{BuildContext, BuildIsolation, InFlight}; #[derive(Parser)] pub(crate) struct BuildArgs { @@ -93,5 +91,5 @@ pub(crate) async fn build(args: BuildArgs) -> Result { FxHashMap::default(), ) .await?; - Ok(wheel_dir.join(builder.build(&wheel_dir).await?)) + Ok(wheel_dir.join(builder.build_wheel(&wheel_dir).await?)) } diff --git a/crates/uv-dev/src/fetch_python.rs b/crates/uv-dev/src/fetch_python.rs new file mode 100644 index 000000000..cdbdfcea0 --- /dev/null +++ b/crates/uv-dev/src/fetch_python.rs @@ -0,0 +1,142 @@ +use anyhow::Result; +use clap::Parser; +use fs_err as fs; +#[cfg(unix)] +use fs_err::tokio::symlink; +use futures::StreamExt; +#[cfg(unix)] +use itertools::Itertools; +use std::str::FromStr; +#[cfg(unix)] +use std::{collections::HashMap, path::PathBuf}; +use tokio::time::Instant; +use tracing::{info, info_span, Instrument}; + +use uv_fs::Simplified; +use uv_toolchain::{ + DownloadResult, Error, PythonDownload, PythonDownloadRequest, TOOLCHAIN_DIRECTORY, +}; + +#[derive(Parser, Debug)] +pub(crate) struct FetchPythonArgs { + versions: Vec, +} + +pub(crate) async fn fetch_python(args: FetchPythonArgs) -> Result<()> { + let start = Instant::now(); + + let bootstrap_dir = &*TOOLCHAIN_DIRECTORY; + + fs_err::create_dir_all(bootstrap_dir)?; + + let versions = if args.versions.is_empty() { + info!("Reading versions from file..."); + read_versions_file().await? + } else { + args.versions + }; + + let requests = versions + .iter() + .map(|version| { + PythonDownloadRequest::from_str(version).and_then(PythonDownloadRequest::fill) + }) + .collect::, Error>>()?; + + let downloads = requests + .iter() + .map(|request| match PythonDownload::from_request(request) { + Some(download) => download, + None => panic!("No download found for request {request:?}"), + }) + .collect::>(); + + let client = uv_client::BaseClientBuilder::new().build(); + + info!("Fetching requested versions..."); + let mut tasks = futures::stream::iter(downloads.iter()) + .map(|download| { + async { + let result = download.fetch(&client, bootstrap_dir).await; + (download.python_version(), result) + } + .instrument(info_span!("download", key = %download)) + }) + .buffered(4); + + let mut results = Vec::new(); + let mut downloaded = 0; + while let Some(task) = tasks.next().await { + let (version, result) = task; + let path = match result? { + DownloadResult::AlreadyAvailable(path) => { + info!("Found existing download for v{}", version); + path + } + DownloadResult::Fetched(path) => { + info!("Downloaded v{} to {}", version, path.user_display()); + downloaded += 1; + path + } + }; + results.push((version, path)); + } + + if downloaded > 0 { + let s = if downloaded == 1 { "" } else { "s" }; + info!( + "Fetched {} in {}s", + format!("{} version{}", downloaded, s), + start.elapsed().as_secs() + ); + } else { + info!("All versions downloaded already."); + }; + + // Order matters here, as we overwrite previous links + info!("Installing to `{}`...", bootstrap_dir.user_display()); + + // On Windows, linking the executable generally results in broken installations + // and each toolchain path will need to be added to the PATH separately in the + // desired order + #[cfg(unix)] + { + let mut links: HashMap = HashMap::new(); + for (version, path) in results { + // TODO(zanieb): This path should be a part of the download metadata + let executable = path.join("install").join("bin").join("python3"); + for target in [ + bootstrap_dir.join(format!("python{}", version.python_full_version())), + bootstrap_dir.join(format!("python{}.{}", version.major(), version.minor())), + bootstrap_dir.join(format!("python{}", version.major())), + bootstrap_dir.join("python"), + ] { + // Attempt to remove it, we'll fail on link if we couldn't remove it for some reason + // but if it's missing we don't want to error + let _ = fs::remove_file(&target); + symlink(&executable, &target).await?; + links.insert(target, executable.clone()); + } + } + for (target, executable) in links.iter().sorted() { + info!( + "Linked `{}` to `{}`", + target.user_display(), + executable.user_display() + ); + } + }; + + info!("Installed {} versions", requests.len()); + + Ok(()) +} + +async fn read_versions_file() -> Result> { + let lines: Vec = fs::tokio::read_to_string(".python-versions") + .await? + .lines() + .map(ToString::to_string) + .collect(); + Ok(lines) +} diff --git a/crates/uv-dev/src/main.rs b/crates/uv-dev/src/main.rs index dc5865c9f..ce83a8167 100644 --- a/crates/uv-dev/src/main.rs +++ b/crates/uv-dev/src/main.rs @@ -21,6 +21,7 @@ use resolve_many::ResolveManyArgs; use crate::build::{build, BuildArgs}; use crate::clear_compile::ClearCompileArgs; use crate::compile::CompileArgs; +use crate::fetch_python::FetchPythonArgs; use crate::render_benchmarks::RenderBenchmarksArgs; use crate::resolve_cli::ResolveCliArgs; use crate::wheel_metadata::WheelMetadataArgs; @@ -44,6 +45,7 @@ static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; mod build; mod clear_compile; mod compile; +mod fetch_python; mod render_benchmarks; mod resolve_cli; mod resolve_many; @@ -72,6 +74,8 @@ enum Cli { Compile(CompileArgs), /// Remove all `.pyc` in the tree. ClearCompile(ClearCompileArgs), + /// Fetch Python versions for testing + FetchPython(FetchPythonArgs), } #[instrument] // Anchor span to check for overhead @@ -92,6 +96,7 @@ async fn run() -> Result<()> { Cli::RenderBenchmarks(args) => render_benchmarks::render_benchmarks(&args)?, Cli::Compile(args) => compile::compile(args).await?, Cli::ClearCompile(args) => clear_compile::clear_compile(&args)?, + Cli::FetchPython(args) => fetch_python::fetch_python(args).await?, } Ok(()) } diff --git a/crates/uv-dev/src/resolve_cli.rs b/crates/uv-dev/src/resolve_cli.rs index bb320628c..b4aa0d992 100644 --- a/crates/uv-dev/src/resolve_cli.rs +++ b/crates/uv-dev/src/resolve_cli.rs @@ -12,12 +12,13 @@ use petgraph::dot::{Config as DotConfig, Dot}; use distribution_types::{FlatIndexLocation, IndexLocations, IndexUrl, Resolution}; use pep508_rs::Requirement; use uv_cache::{Cache, CacheArgs}; -use uv_client::{FlatIndex, FlatIndexClient, RegistryClientBuilder}; +use uv_client::{FlatIndexClient, RegistryClientBuilder}; +use uv_configuration::{ConfigSettings, NoBinary, NoBuild, SetupPyStrategy}; use uv_dispatch::BuildDispatch; use uv_installer::SitePackages; use uv_interpreter::PythonEnvironment; -use uv_resolver::{InMemoryIndex, Manifest, Options, Resolver}; -use uv_types::{BuildIsolation, ConfigSettings, InFlight, NoBinary, NoBuild, SetupPyStrategy}; +use uv_resolver::{FlatIndex, InMemoryIndex, Manifest, Options, Resolver}; +use uv_types::{BuildIsolation, HashStrategy, InFlight}; #[derive(ValueEnum, Default, Clone)] pub(crate) enum ResolveCliFormat { @@ -56,14 +57,6 @@ pub(crate) async fn resolve_cli(args: ResolveCliArgs) -> Result<()> { let venv = PythonEnvironment::from_virtualenv(&cache)?; let index_locations = IndexLocations::new(args.index_url, args.extra_index_url, args.find_links, false); - let client = RegistryClientBuilder::new(cache.clone()) - .index_urls(index_locations.index_urls()) - .build(); - let flat_index = { - let client = FlatIndexClient::new(&client, &cache); - let entries = client.fetch(index_locations.flat_index()).await?; - FlatIndex::from_entries(entries, venv.interpreter().tags()?) - }; let index = InMemoryIndex::default(); let in_flight = InFlight::default(); let no_build = if args.no_build { @@ -71,6 +64,20 @@ pub(crate) async fn resolve_cli(args: ResolveCliArgs) -> Result<()> { } else { NoBuild::None }; + let client = RegistryClientBuilder::new(cache.clone()) + .index_urls(index_locations.index_urls()) + .build(); + let flat_index = { + let client = FlatIndexClient::new(&client, &cache); + let entries = client.fetch(index_locations.flat_index()).await?; + FlatIndex::from_entries( + entries, + venv.interpreter().tags()?, + &HashStrategy::None, + &no_build, + &NoBinary::None, + ) + }; let config_settings = ConfigSettings::default(); let build_dispatch = BuildDispatch::new( @@ -101,6 +108,7 @@ pub(crate) async fn resolve_cli(args: ResolveCliArgs) -> Result<()> { &client, &flat_index, &index, + &HashStrategy::None, &build_dispatch, &site_packages, )?; diff --git a/crates/uv-dev/src/resolve_many.rs b/crates/uv-dev/src/resolve_many.rs index f5608b46a..761fcbed6 100644 --- a/crates/uv-dev/src/resolve_many.rs +++ b/crates/uv-dev/src/resolve_many.rs @@ -14,13 +14,13 @@ use distribution_types::IndexLocations; use pep440_rs::{Version, VersionSpecifier, VersionSpecifiers}; use pep508_rs::{Requirement, VersionOrUrl}; use uv_cache::{Cache, CacheArgs}; -use uv_client::{FlatIndex, OwnedArchive, RegistryClient, RegistryClientBuilder}; +use uv_client::{OwnedArchive, RegistryClient, RegistryClientBuilder}; +use uv_configuration::{ConfigSettings, NoBinary, NoBuild, SetupPyStrategy}; use uv_dispatch::BuildDispatch; use uv_interpreter::PythonEnvironment; use uv_normalize::PackageName; -use uv_resolver::InMemoryIndex; -use uv_types::NoBinary; -use uv_types::{BuildContext, BuildIsolation, ConfigSettings, InFlight, NoBuild, SetupPyStrategy}; +use uv_resolver::{FlatIndex, InMemoryIndex}; +use uv_types::{BuildContext, BuildIsolation, InFlight}; #[derive(Parser)] pub(crate) struct ResolveManyArgs { @@ -47,10 +47,17 @@ async fn find_latest_version( client: &RegistryClient, package_name: &PackageName, ) -> Option { - let (_, raw_simple_metadata) = client.simple(package_name).await.ok()?; - let simple_metadata = OwnedArchive::deserialize(&raw_simple_metadata); - let version = simple_metadata.into_iter().next()?.version; - Some(version) + client + .simple(package_name) + .await + .ok() + .into_iter() + .flatten() + .filter_map(|(_index, raw_simple_metadata)| { + let simple_metadata = OwnedArchive::deserialize(&raw_simple_metadata); + Some(simple_metadata.into_iter().next()?.version) + }) + .max() } pub(crate) async fn resolve_many(args: ResolveManyArgs) -> Result<()> { diff --git a/crates/uv-dispatch/Cargo.toml b/crates/uv-dispatch/Cargo.toml index 727e11668..7cd8476d4 100644 --- a/crates/uv-dispatch/Cargo.toml +++ b/crates/uv-dispatch/Cargo.toml @@ -21,9 +21,9 @@ uv-cache = { workspace = true } uv-client = { workspace = true } uv-installer = { workspace = true } uv-interpreter = { workspace = true } -uv-requirements = { workspace = true } uv-resolver = { workspace = true } uv-types = { workspace = true } +uv-configuration = { workspace = true } anyhow = { workspace = true } futures = { workspace = true } diff --git a/crates/uv-dispatch/src/lib.rs b/crates/uv-dispatch/src/lib.rs index 7a73c81d4..b204b9a1f 100644 --- a/crates/uv-dispatch/src/lib.rs +++ b/crates/uv-dispatch/src/lib.rs @@ -3,8 +3,8 @@ //! implementing [`BuildContext`]. use std::ffi::OsStr; +use std::ffi::OsString; use std::path::Path; -use std::{ffi::OsString, future::Future}; use anyhow::{bail, Context, Result}; use futures::FutureExt; @@ -16,14 +16,12 @@ use distribution_types::{IndexLocations, Name, Resolution, SourceDist}; use pep508_rs::Requirement; use uv_build::{SourceBuild, SourceBuildContext}; use uv_cache::Cache; -use uv_client::{FlatIndex, RegistryClient}; +use uv_client::RegistryClient; +use uv_configuration::{BuildKind, ConfigSettings, NoBinary, NoBuild, Reinstall, SetupPyStrategy}; use uv_installer::{Downloader, Installer, Plan, Planner, SitePackages}; use uv_interpreter::{Interpreter, PythonEnvironment}; -use uv_resolver::{InMemoryIndex, Manifest, Options, Resolver}; -use uv_types::{ - BuildContext, BuildIsolation, BuildKind, ConfigSettings, EmptyInstalledPackages, InFlight, - NoBinary, NoBuild, Reinstall, SetupPyStrategy, -}; +use uv_resolver::{FlatIndex, InMemoryIndex, Manifest, Options, Resolver}; +use uv_types::{BuildContext, BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight}; /// The main implementation of [`BuildContext`], used by the CLI, see [`BuildContext`] /// documentation. @@ -145,6 +143,7 @@ impl<'a> BuildContext for BuildDispatch<'a> { self.client, self.flat_index, self.index, + &HashStrategy::None, self, &EmptyInstalledPackages, )?; @@ -157,7 +156,6 @@ impl<'a> BuildContext for BuildDispatch<'a> { Ok(Resolution::from(graph)) } - #[allow(clippy::manual_async_fn)] // TODO(konstin): rustc 1.75 gets into a type inference cycle with async fn #[instrument( skip(self, resolution, venv), fields( @@ -165,119 +163,118 @@ impl<'a> BuildContext for BuildDispatch<'a> { venv = ?venv.root() ) )] - fn install<'data>( + async fn install<'data>( &'data self, resolution: &'data Resolution, venv: &'data PythonEnvironment, - ) -> impl Future> + Send + 'data { - async move { - debug!( - "Installing in {} in {}", + ) -> Result<()> { + debug!( + "Installing in {} in {}", + resolution + .distributions() + .map(ToString::to_string) + .join(", "), + venv.root().display(), + ); + + // Determine the current environment markers. + let tags = self.interpreter.tags()?; + + // Determine the set of installed packages. + let site_packages = SitePackages::from_executable(venv)?; + + let Plan { + cached, + remote, + installed: _, + reinstalls, + extraneous: _, + } = Planner::with_requirements(&resolution.requirements()).build( + site_packages, + &Reinstall::None, + &NoBinary::None, + &HashStrategy::None, + self.index_locations, + self.cache(), + venv, + tags, + )?; + + // Nothing to do. + if remote.is_empty() && cached.is_empty() && reinstalls.is_empty() { + debug!("No build requirements to install for build"); + return Ok(()); + } + + // Resolve any registry-based requirements. + let remote = remote + .iter() + .map(|dist| { resolution - .distributions() - .map(ToString::to_string) - .join(", "), - venv.root().display(), + .get_remote(&dist.name) + .cloned() + .expect("Resolution should contain all packages") + }) + .collect::>(); + + // Download any missing distributions. + let wheels = if remote.is_empty() { + vec![] + } else { + // TODO(konstin): Check that there is no endless recursion. + let downloader = + Downloader::new(self.cache, tags, &HashStrategy::None, self.client, self); + debug!( + "Downloading and building requirement{} for build: {}", + if remote.len() == 1 { "" } else { "s" }, + remote.iter().map(ToString::to_string).join(", ") ); - // Determine the current environment markers. - let tags = self.interpreter.tags()?; + downloader + .download(remote, self.in_flight) + .await + .context("Failed to download and build distributions")? + }; - // Determine the set of installed packages. - let site_packages = SitePackages::from_executable(venv)?; - - let Plan { - cached, - remote, - installed: _, - reinstalls, - extraneous: _, - } = Planner::with_requirements(&resolution.requirements()).build( - site_packages, - &Reinstall::None, - &NoBinary::None, - self.index_locations, - self.cache(), - venv, - tags, - )?; - - // Nothing to do. - if remote.is_empty() && cached.is_empty() && reinstalls.is_empty() { - debug!("No build requirements to install for build"); - return Ok(()); - } - - // Resolve any registry-based requirements. - let remote = remote - .iter() - .map(|dist| { - resolution - .get_remote(&dist.name) - .cloned() - .expect("Resolution should contain all packages") - }) - .collect::>(); - - // Download any missing distributions. - let wheels = if remote.is_empty() { - vec![] - } else { - // TODO(konstin): Check that there is no endless recursion. - let downloader = Downloader::new(self.cache, tags, self.client, self); - debug!( - "Downloading and building requirement{} for build: {}", - if remote.len() == 1 { "" } else { "s" }, - remote.iter().map(ToString::to_string).join(", ") - ); - - downloader - .download(remote, self.in_flight) + // Remove any unnecessary packages. + if !reinstalls.is_empty() { + for dist_info in &reinstalls { + let summary = uv_installer::uninstall(dist_info) .await - .context("Failed to download and build distributions")? - }; - - // Remove any unnecessary packages. - if !reinstalls.is_empty() { - for dist_info in &reinstalls { - let summary = uv_installer::uninstall(dist_info) - .await - .context("Failed to uninstall build dependencies")?; - debug!( - "Uninstalled {} ({} file{}, {} director{})", - dist_info.name(), - summary.file_count, - if summary.file_count == 1 { "" } else { "s" }, - summary.dir_count, - if summary.dir_count == 1 { "y" } else { "ies" }, - ); - } - } - - // Install the resolved distributions. - let wheels = wheels.into_iter().chain(cached).collect::>(); - if !wheels.is_empty() { + .context("Failed to uninstall build dependencies")?; debug!( - "Installing build requirement{}: {}", - if wheels.len() == 1 { "" } else { "s" }, - wheels.iter().map(ToString::to_string).join(", ") + "Uninstalled {} ({} file{}, {} director{})", + dist_info.name(), + summary.file_count, + if summary.file_count == 1 { "" } else { "s" }, + summary.dir_count, + if summary.dir_count == 1 { "y" } else { "ies" }, ); - Installer::new(venv) - .install(&wheels) - .context("Failed to install build dependencies")?; } - - Ok(()) } + + // Install the resolved distributions. + let wheels = wheels.into_iter().chain(cached).collect::>(); + if !wheels.is_empty() { + debug!( + "Installing build requirement{}: {}", + if wheels.len() == 1 { "" } else { "s" }, + wheels.iter().map(ToString::to_string).join(", ") + ); + Installer::new(venv) + .install(&wheels) + .context("Failed to install build dependencies")?; + } + + Ok(()) } - #[allow(clippy::manual_async_fn)] // TODO(konstin): rustc 1.75 gets into a type inference cycle with async fn - #[instrument(skip_all, fields(package_id = package_id, subdirectory = ?subdirectory))] + #[instrument(skip_all, fields(version_id = version_id, subdirectory = ?subdirectory))] async fn setup_build<'data>( &'data self, source: &'data Path, subdirectory: Option<&'data Path>, - package_id: &'data str, + version_id: &'data str, dist: Option<&'data SourceDist>, build_kind: BuildKind, ) -> Result { @@ -307,7 +304,7 @@ impl<'a> BuildContext for BuildDispatch<'a> { self.interpreter, self, self.source_build_context.clone(), - package_id.to_string(), + version_id.to_string(), self.setup_py, self.config_settings.clone(), self.build_isolation, diff --git a/crates/uv-distribution/Cargo.toml b/crates/uv-distribution/Cargo.toml index 42b8c9e12..a4814ff6d 100644 --- a/crates/uv-distribution/Cargo.toml +++ b/crates/uv-distribution/Cargo.toml @@ -28,10 +28,12 @@ uv-fs = { workspace = true, features = ["tokio"] } uv-git = { workspace = true, features = ["vendored-openssl"] } uv-normalize = { workspace = true } uv-types = { workspace = true } +uv-configuration = { workspace = true } anyhow = { workspace = true } fs-err = { workspace = true } futures = { workspace = true } +md-5 = { workspace = true } nanoid = { workspace = true } once_cell = { workspace = true } reqwest = { workspace = true } @@ -39,6 +41,7 @@ reqwest-middleware = { workspace = true } rmp-serde = { workspace = true } rustc-hash = { workspace = true } serde = { workspace = true, features = ["derive"] } +sha2 = { workspace = true } tempfile = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } @@ -46,4 +49,3 @@ tokio-util = { workspace = true, features = ["compat"] } tracing = { workspace = true } url = { workspace = true } zip = { workspace = true } - diff --git a/crates/uv-distribution/src/archive.rs b/crates/uv-distribution/src/archive.rs new file mode 100644 index 000000000..3d02d9e85 --- /dev/null +++ b/crates/uv-distribution/src/archive.rs @@ -0,0 +1,25 @@ +use distribution_types::Hashed; +use pypi_types::HashDigest; +use uv_cache::ArchiveId; + +/// An archive (unzipped wheel) that exists in the local cache. +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct Archive { + /// The unique ID of the entry in the wheel's archive bucket. + pub id: ArchiveId, + /// The computed hashes of the archive. + pub hashes: Vec, +} + +impl Archive { + /// Create a new [`Archive`] with the given ID and hashes. + pub(crate) fn new(id: ArchiveId, hashes: Vec) -> Self { + Self { id, hashes } + } +} + +impl Hashed for Archive { + fn hashes(&self) -> &[HashDigest] { + &self.hashes + } +} diff --git a/crates/uv-distribution/src/distribution_database.rs b/crates/uv-distribution/src/distribution_database.rs index e4b1cec23..67bc80cb5 100644 --- a/crates/uv-distribution/src/distribution_database.rs +++ b/crates/uv-distribution/src/distribution_database.rs @@ -1,8 +1,9 @@ use std::io; -use std::path::{Path, PathBuf}; +use std::path::Path; use std::sync::Arc; use futures::{FutureExt, TryStreamExt}; +use tempfile::TempDir; use tokio::io::AsyncSeekExt; use tokio_util::compat::FuturesAsyncReadCompatExt; use tracing::{info_span, instrument, warn, Instrument}; @@ -10,17 +11,23 @@ use url::Url; use distribution_filename::WheelFilename; use distribution_types::{ - BuildableSource, BuiltDist, Dist, FileLocation, IndexLocations, LocalEditable, Name, SourceDist, + BuildableSource, BuiltDist, Dist, FileLocation, HashPolicy, Hashed, IndexLocations, + LocalEditable, Name, SourceDist, }; use platform_tags::Tags; -use pypi_types::Metadata23; -use uv_cache::{ArchiveTarget, ArchiveTimestamp, CacheBucket, CacheEntry, WheelCache}; -use uv_client::{CacheControl, CachedClientError, Connectivity, RegistryClient}; -use uv_types::{BuildContext, NoBinary, NoBuild}; +use pypi_types::{HashDigest, Metadata23}; +use uv_cache::{ArchiveId, ArchiveTimestamp, CacheBucket, CacheEntry, Timestamp, WheelCache}; +use uv_client::{ + CacheControl, CachedClientError, Connectivity, DataWithCachePolicy, RegistryClient, +}; +use uv_configuration::{NoBinary, NoBuild}; +use uv_extract::hash::Hasher; +use uv_fs::write_atomic; +use uv_types::BuildContext; -use crate::download::{BuiltWheel, UnzippedWheel}; +use crate::archive::Archive; use crate::locks::Locks; -use crate::{DiskWheel, Error, LocalWheel, Reporter, SourceDistributionBuilder}; +use crate::{ArchiveMetadata, Error, LocalWheel, Reporter, SourceDistributionBuilder}; /// A cached high-level interface to convert distributions (a requirement resolved to a location) /// to a wheel or wheel metadata. @@ -77,28 +84,38 @@ impl<'a, Context: BuildContext + Send + Sync> DistributionDatabase<'a, Context> /// Either fetch the wheel or fetch and build the source distribution /// - /// If `no_remote_wheel` is set, the wheel will be built from a source distribution - /// even if compatible pre-built wheels are available. + /// Returns a wheel that's compliant with the given platform tags. + /// + /// 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_or_build_wheel(&self, dist: &Dist, tags: &Tags) -> Result { + pub async fn get_or_build_wheel( + &self, + dist: &Dist, + tags: &Tags, + hashes: HashPolicy<'_>, + ) -> Result { match dist { - Dist::Built(built) => self.get_wheel(built).await, - Dist::Source(source) => self.build_wheel(source, tags).await, + Dist::Built(built) => self.get_wheel(built, hashes).await, + Dist::Source(source) => self.build_wheel(source, tags, hashes).await, } } /// Either fetch the only wheel metadata (directly from the index or with range requests) or /// fetch and build the source distribution. /// - /// Returns the [`Metadata23`], along with a "precise" URL for the source distribution, if - /// possible. 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. + /// 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_or_build_wheel_metadata(&self, dist: &Dist) -> Result { + pub async fn get_or_build_wheel_metadata( + &self, + dist: &Dist, + hashes: HashPolicy<'_>, + ) -> Result { match dist { - Dist::Built(built) => self.get_wheel_metadata(built).await, + Dist::Built(built) => self.get_wheel_metadata(built, hashes).await, Dist::Source(source) => { - self.build_wheel_metadata(&BuildableSource::Dist(source)) + self.build_wheel_metadata(&BuildableSource::Dist(source), hashes) .await } } @@ -110,22 +127,35 @@ impl<'a, Context: BuildContext + Send + Sync> DistributionDatabase<'a, Context> editable: &LocalEditable, editable_wheel_dir: &Path, ) -> Result<(LocalWheel, Metadata23), Error> { + // Build the wheel. let (dist, disk_filename, filename, metadata) = self .builder .build_editable(editable, editable_wheel_dir) .await?; - let built_wheel = BuiltWheel { + // Unzip into the editable wheel directory. + let path = editable_wheel_dir.join(&disk_filename); + let target = editable_wheel_dir.join(cache_key::digest(&editable.path)); + let id = self.unzip_wheel(&path, &target).await?; + let wheel = LocalWheel { dist, filename, - path: editable_wheel_dir.join(disk_filename), - target: editable_wheel_dir.join(cache_key::digest(&editable.path)), + archive: self.build_context.cache().archive(&id), + hashes: vec![], }; - Ok((LocalWheel::Built(built_wheel), metadata)) + + Ok((wheel, metadata)) } /// Fetch a wheel from the cache or download it from the index. - async fn get_wheel(&self, dist: &BuiltDist) -> Result { + /// + /// While hashes will be generated in all cases, hash-checking is _not_ enforced and should + /// instead be enforced by the caller. + async fn get_wheel( + &self, + dist: &BuiltDist, + hashes: HashPolicy<'_>, + ) -> Result { let no_binary = match self.build_context.no_binary() { NoBinary::None => false, NoBinary::All => true, @@ -145,41 +175,14 @@ impl<'a, Context: BuildContext + Send + Sync> DistributionDatabase<'a, Context> Url::parse(url).map_err(|err| Error::Url(url.clone(), err))? } FileLocation::Path(path) => { - let url = Url::from_file_path(path).expect("path is absolute"); let cache_entry = self.build_context.cache().entry( CacheBucket::Wheels, - WheelCache::Url(&url).wheel_dir(wheel.name().as_ref()), + WheelCache::Index(&wheel.index).wheel_dir(wheel.name().as_ref()), wheel.filename.stem(), ); - - // If the file is already unzipped, and the unzipped directory is fresh, - // return it. - match cache_entry.path().canonicalize() { - Ok(archive) => { - if ArchiveTimestamp::up_to_date_with( - path, - ArchiveTarget::Cache(&archive), - ) - .map_err(Error::CacheRead)? - { - return Ok(LocalWheel::Unzipped(UnzippedWheel { - dist: Dist::Built(dist.clone()), - archive, - filename: wheel.filename.clone(), - })); - } - } - Err(err) if err.kind() == io::ErrorKind::NotFound => {} - Err(err) => return Err(Error::CacheRead(err)), - } - - // Otherwise, unzip the file. - return Ok(LocalWheel::Disk(DiskWheel { - dist: Dist::Built(dist.clone()), - path: path.clone(), - target: cache_entry.into_path_buf(), - filename: wheel.filename.clone(), - })); + return self + .load_wheel(path, &wheel.filename, cache_entry, dist, hashes) + .await; } }; @@ -192,14 +195,15 @@ impl<'a, Context: BuildContext + Send + Sync> DistributionDatabase<'a, Context> // Download and unzip. match self - .stream_wheel(url.clone(), &wheel.filename, &wheel_entry, dist) + .stream_wheel(url.clone(), &wheel.filename, &wheel_entry, dist, hashes) .await { - Ok(archive) => Ok(LocalWheel::Unzipped(UnzippedWheel { + Ok(archive) => Ok(LocalWheel { dist: Dist::Built(dist.clone()), - archive, + archive: self.build_context.cache().archive(&archive.id), + hashes: archive.hashes, filename: wheel.filename.clone(), - })), + }), Err(Error::Extract(err)) if err.is_http_streaming_unsupported() => { warn!( "Streaming unsupported for {dist}; downloading wheel to disk ({err})" @@ -208,13 +212,14 @@ impl<'a, Context: BuildContext + Send + Sync> DistributionDatabase<'a, Context> // If the request failed because streaming is unsupported, download the // wheel directly. let archive = self - .download_wheel(url, &wheel.filename, &wheel_entry, dist) + .download_wheel(url, &wheel.filename, &wheel_entry, dist, hashes) .await?; - Ok(LocalWheel::Unzipped(UnzippedWheel { + Ok(LocalWheel { dist: Dist::Built(dist.clone()), - archive, + archive: self.build_context.cache().archive(&archive.id), + hashes: archive.hashes, filename: wheel.filename.clone(), - })) + }) } Err(err) => Err(err), } @@ -230,14 +235,21 @@ impl<'a, Context: BuildContext + Send + Sync> DistributionDatabase<'a, Context> // Download and unzip. match self - .stream_wheel(wheel.url.raw().clone(), &wheel.filename, &wheel_entry, dist) + .stream_wheel( + wheel.url.raw().clone(), + &wheel.filename, + &wheel_entry, + dist, + hashes, + ) .await { - Ok(archive) => Ok(LocalWheel::Unzipped(UnzippedWheel { + Ok(archive) => Ok(LocalWheel { dist: Dist::Built(dist.clone()), - archive, + archive: self.build_context.cache().archive(&archive.id), + hashes: archive.hashes, filename: wheel.filename.clone(), - })), + }), Err(Error::Client(err)) if err.is_http_streaming_unsupported() => { warn!( "Streaming unsupported for {dist}; downloading wheel to disk ({err})" @@ -251,13 +263,15 @@ impl<'a, Context: BuildContext + Send + Sync> DistributionDatabase<'a, Context> &wheel.filename, &wheel_entry, dist, + hashes, ) .await?; - Ok(LocalWheel::Unzipped(UnzippedWheel { + Ok(LocalWheel { dist: Dist::Built(dist.clone()), - archive, + archive: self.build_context.cache().archive(&archive.id), + hashes: archive.hashes, filename: wheel.filename.clone(), - })) + }) } Err(err) => Err(err), } @@ -270,90 +284,107 @@ impl<'a, Context: BuildContext + Send + Sync> DistributionDatabase<'a, Context> wheel.filename.stem(), ); - // If the file is already unzipped, and the unzipped directory is fresh, - // return it. - match cache_entry.path().canonicalize() { - Ok(archive) => { - if ArchiveTimestamp::up_to_date_with( - &wheel.path, - ArchiveTarget::Cache(&archive), - ) - .map_err(Error::CacheRead)? - { - return Ok(LocalWheel::Unzipped(UnzippedWheel { - dist: Dist::Built(dist.clone()), - archive, - filename: wheel.filename.clone(), - })); - } - } - Err(err) if err.kind() == io::ErrorKind::NotFound => {} - Err(err) => return Err(Error::CacheRead(err)), - } - - Ok(LocalWheel::Disk(DiskWheel { - dist: Dist::Built(dist.clone()), - path: wheel.path.clone(), - target: cache_entry.into_path_buf(), - filename: wheel.filename.clone(), - })) + self.load_wheel(&wheel.path, &wheel.filename, cache_entry, dist, hashes) + .await } } } /// Convert a source distribution into a wheel, fetching it from the cache or building it if /// necessary. - async fn build_wheel(&self, dist: &SourceDist, tags: &Tags) -> Result { + /// + /// The returned wheel is guaranteed to come from a distribution with a matching hash, and + /// no build processes will be executed for distributions with mismatched hashes. + async fn build_wheel( + &self, + dist: &SourceDist, + tags: &Tags, + hashes: HashPolicy<'_>, + ) -> Result { let lock = self.locks.acquire(&Dist::Source(dist.clone())).await; let _guard = lock.lock().await; let built_wheel = self .builder - .download_and_build(&BuildableSource::Dist(dist), tags) + .download_and_build(&BuildableSource::Dist(dist), tags, hashes) .boxed() .await?; // If the wheel was unzipped previously, respect it. Source distributions are - // cached under a unique build ID, so unzipped directories are never stale. + // cached under a unique revision ID, so unzipped directories are never stale. match built_wheel.target.canonicalize() { - Ok(archive) => Ok(LocalWheel::Unzipped(UnzippedWheel { - dist: Dist::Source(dist.clone()), - archive, - filename: built_wheel.filename, - })), - Err(err) if err.kind() == io::ErrorKind::NotFound => { - Ok(LocalWheel::Built(BuiltWheel { + Ok(archive) => { + return Ok(LocalWheel { dist: Dist::Source(dist.clone()), - path: built_wheel.path, - target: built_wheel.target, + archive, filename: built_wheel.filename, - })) + hashes: built_wheel.hashes, + }); } - Err(err) => Err(Error::CacheRead(err)), + Err(err) if err.kind() == io::ErrorKind::NotFound => {} + Err(err) => return Err(Error::CacheRead(err)), } + + // Otherwise, unzip the wheel. + let id = self + .unzip_wheel(&built_wheel.path, &built_wheel.target) + .await?; + + Ok(LocalWheel { + dist: Dist::Source(dist.clone()), + archive: self.build_context.cache().archive(&id), + hashes: built_wheel.hashes, + filename: built_wheel.filename, + }) } /// Fetch the wheel metadata from the index, or from the cache if possible. - pub async fn get_wheel_metadata(&self, dist: &BuiltDist) -> Result { + /// + /// While hashes will be generated in some cases, hash-checking is _not_ enforced and should + /// instead be enforced by the caller. + pub async fn get_wheel_metadata( + &self, + dist: &BuiltDist, + hashes: HashPolicy<'_>, + ) -> Result { + // If hash generation is enabled, and the distribution isn't hosted on an index, 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. + // TODO(charlie): Request the hashes via a separate method, to reduce the coupling in this API. + if hashes.is_generate() && matches!(dist, BuiltDist::DirectUrl(_) | BuiltDist::Path(_)) { + let wheel = self.get_wheel(dist, hashes).await?; + let metadata = wheel.metadata()?; + let hashes = wheel.hashes; + return Ok(ArchiveMetadata { metadata, hashes }); + } + match self.client.wheel_metadata(dist).boxed().await { - Ok(metadata) => Ok(metadata), + Ok(metadata) => Ok(ArchiveMetadata::from(metadata)), Err(err) if err.is_http_streaming_unsupported() => { warn!("Streaming unsupported when fetching metadata for {dist}; downloading wheel directly ({err})"); // If the request failed due to an error that could be resolved by // downloading the wheel directly, try that. - let wheel = self.get_wheel(dist).await?; - Ok(wheel.metadata()?) + let wheel = self.get_wheel(dist, hashes).await?; + let metadata = wheel.metadata()?; + let hashes = wheel.hashes; + Ok(ArchiveMetadata { metadata, hashes }) } Err(err) => Err(err.into()), } } /// Build the wheel metadata for a source distribution, or fetch it from the cache if possible. + /// + /// The returned metadata is guaranteed to come from a distribution with a matching hash, and + /// no build processes will be executed for distributions with mismatched hashes. pub async fn build_wheel_metadata( &self, source: &BuildableSource<'_>, - ) -> Result { + hashes: HashPolicy<'_>, + ) -> Result { let no_build = match self.build_context.no_build() { NoBuild::All => true, NoBuild::None => false, @@ -372,7 +403,7 @@ impl<'a, Context: BuildContext + Send + Sync> DistributionDatabase<'a, Context> let metadata = self .builder - .download_and_build_metadata(source) + .download_and_build_metadata(source, hashes) .boxed() .await?; Ok(metadata) @@ -385,7 +416,8 @@ impl<'a, Context: BuildContext + Send + Sync> DistributionDatabase<'a, Context> filename: &WheelFilename, wheel_entry: &CacheEntry, dist: &BuiltDist, - ) -> Result { + hashes: HashPolicy<'_>, + ) -> Result { // Create an entry for the HTTP cache. let http_entry = wheel_entry.with_file(format!("{}.http", filename.stem())); @@ -396,35 +428,39 @@ impl<'a, Context: BuildContext + Send + Sync> DistributionDatabase<'a, Context> .map_err(|err| self.handle_response_errors(err)) .into_async_read(); + // Create a hasher for each hash algorithm. + let algorithms = hashes.algorithms(); + let mut hashers = algorithms.into_iter().map(Hasher::from).collect::>(); + let mut hasher = uv_extract::hash::HashReader::new(reader.compat(), &mut hashers); + // Download and unzip the wheel to a temporary directory. let temp_dir = tempfile::tempdir_in(self.build_context.cache().root()) .map_err(Error::CacheWrite)?; - uv_extract::stream::unzip(reader.compat(), temp_dir.path()).await?; + uv_extract::stream::unzip(&mut hasher, temp_dir.path()).await?; + + // If necessary, exhaust the reader to compute the hash. + if !hashes.is_none() { + hasher.finish().await.map_err(Error::HashExhaustion)?; + } // Persist the temporary directory to the directory store. - let archive = self + let id = self .build_context .cache() .persist(temp_dir.into_path(), wheel_entry.path()) .await .map_err(Error::CacheRead)?; - Ok(archive) + + Ok(Archive::new( + id, + hashers.into_iter().map(HashDigest::from).collect(), + )) } .instrument(info_span!("wheel", wheel = %dist)) }; - let req = self - .client - .uncached_client() - .get(url) - .header( - // `reqwest` defaults to accepting compressed responses. - // Specify identity encoding to get consistent .whl downloading - // behavior from servers. ref: https://github.com/pypa/pip/pull/1688 - "accept-encoding", - reqwest::header::HeaderValue::from_static("identity"), - ) - .build()?; + // Fetch the archive from the cache, or download it if necessary. + let req = self.request(url.clone())?; let cache_control = match self.client.connectivity() { Connectivity::Online => CacheControl::from( self.build_context @@ -434,7 +470,6 @@ impl<'a, Context: BuildContext + Send + Sync> DistributionDatabase<'a, Context> ), Connectivity::Offline => CacheControl::AllowStale, }; - let archive = self .client .cached_client() @@ -445,6 +480,20 @@ impl<'a, Context: BuildContext + Send + Sync> DistributionDatabase<'a, Context> CachedClientError::Client(err) => Error::Client(err), })?; + // If the archive is missing the required hashes, force a refresh. + let archive = if archive.has_digests(hashes) { + archive + } else { + self.client + .cached_client() + .skip_cache(self.request(url)?, &http_entry, download) + .await + .map_err(|err| match err { + CachedClientError::Callback(err) => err, + CachedClientError::Client(err) => Error::Client(err), + })? + }; + Ok(archive) } @@ -455,7 +504,8 @@ impl<'a, Context: BuildContext + Send + Sync> DistributionDatabase<'a, Context> filename: &WheelFilename, wheel_entry: &CacheEntry, dist: &BuiltDist, - ) -> Result { + hashes: HashPolicy<'_>, + ) -> Result { // Create an entry for the HTTP cache. let http_entry = wheel_entry.with_file(format!("{}.http", filename.stem())); @@ -481,33 +531,48 @@ impl<'a, Context: BuildContext + Send + Sync> DistributionDatabase<'a, Context> file.seek(io::SeekFrom::Start(0)) .await .map_err(Error::CacheWrite)?; - let reader = tokio::io::BufReader::new(file); - uv_extract::seek::unzip(reader, temp_dir.path()).await?; + + // If no hashes are required, parallelize the unzip operation. + let hashes = if hashes.is_none() { + let file = file.into_std().await; + tokio::task::spawn_blocking({ + let target = temp_dir.path().to_owned(); + move || -> Result<(), uv_extract::Error> { + // Unzip the wheel into a temporary directory. + uv_extract::unzip(file, &target)?; + Ok(()) + } + }) + .await??; + + vec![] + } else { + // Create a hasher for each hash algorithm. + let algorithms = hashes.algorithms(); + let mut hashers = algorithms.into_iter().map(Hasher::from).collect::>(); + let mut hasher = uv_extract::hash::HashReader::new(file, &mut hashers); + uv_extract::stream::unzip(&mut hasher, temp_dir.path()).await?; + + // If necessary, exhaust the reader to compute the hash. + hasher.finish().await.map_err(Error::HashExhaustion)?; + + hashers.into_iter().map(HashDigest::from).collect() + }; // Persist the temporary directory to the directory store. - let archive = self + let id = self .build_context .cache() .persist(temp_dir.into_path(), wheel_entry.path()) .await .map_err(Error::CacheRead)?; - Ok(archive) + + Ok(Archive::new(id, hashes)) } .instrument(info_span!("wheel", wheel = %dist)) }; - let req = self - .client - .uncached_client() - .get(url) - .header( - // `reqwest` defaults to accepting compressed responses. - // Specify identity encoding to get consistent .whl downloading - // behavior from servers. ref: https://github.com/pypa/pip/pull/1688 - "accept-encoding", - reqwest::header::HeaderValue::from_static("identity"), - ) - .build()?; + let req = self.request(url.clone())?; let cache_control = match self.client.connectivity() { Connectivity::Online => CacheControl::from( self.build_context @@ -517,7 +582,6 @@ impl<'a, Context: BuildContext + Send + Sync> DistributionDatabase<'a, Context> ), Connectivity::Offline => CacheControl::AllowStale, }; - let archive = self .client .cached_client() @@ -528,11 +592,225 @@ impl<'a, Context: BuildContext + Send + Sync> DistributionDatabase<'a, Context> CachedClientError::Client(err) => Error::Client(err), })?; + // If the archive is missing the required hashes, force a refresh. + let archive = if archive.has_digests(hashes) { + archive + } else { + self.client + .cached_client() + .skip_cache(self.request(url)?, &http_entry, download) + .await + .map_err(|err| match err { + CachedClientError::Callback(err) => err, + CachedClientError::Client(err) => Error::Client(err), + })? + }; + Ok(archive) } + /// Load a wheel from a local path. + async fn load_wheel( + &self, + path: &Path, + filename: &WheelFilename, + wheel_entry: CacheEntry, + dist: &BuiltDist, + hashes: HashPolicy<'_>, + ) -> Result { + // Determine the last-modified time of the wheel. + let modified = ArchiveTimestamp::from_file(path).map_err(Error::CacheRead)?; + + // Attempt to read the archive pointer from the cache. + let pointer_entry = wheel_entry.with_file(format!("{}.rev", filename.stem())); + let pointer = LocalArchivePointer::read_from(&pointer_entry)?; + + // Extract the archive from the pointer. + let archive = pointer + .filter(|pointer| pointer.is_up_to_date(modified)) + .map(LocalArchivePointer::into_archive) + .filter(|archive| archive.has_digests(hashes)); + + // If the file is already unzipped, and the cache is up-to-date, return it. + if let Some(archive) = archive { + Ok(LocalWheel { + dist: Dist::Built(dist.clone()), + archive: self.build_context.cache().archive(&archive.id), + hashes: archive.hashes, + filename: filename.clone(), + }) + } else if hashes.is_none() { + // Otherwise, unzip the wheel. + let archive = Archive::new(self.unzip_wheel(path, wheel_entry.path()).await?, vec![]); + + // Write the archive pointer to the cache. + let pointer = LocalArchivePointer { + timestamp: modified.timestamp(), + archive: archive.clone(), + }; + pointer.write_to(&pointer_entry).await?; + + Ok(LocalWheel { + dist: Dist::Built(dist.clone()), + archive: self.build_context.cache().archive(&archive.id), + hashes: archive.hashes, + filename: filename.clone(), + }) + } else { + // If necessary, compute the hashes of the wheel. + let file = fs_err::tokio::File::open(path) + .await + .map_err(Error::CacheRead)?; + let temp_dir = tempfile::tempdir_in(self.build_context.cache().root()) + .map_err(Error::CacheWrite)?; + + // Create a hasher for each hash algorithm. + let algorithms = hashes.algorithms(); + let mut hashers = algorithms.into_iter().map(Hasher::from).collect::>(); + let mut hasher = uv_extract::hash::HashReader::new(file, &mut hashers); + + // Unzip the wheel to a temporary directory. + uv_extract::stream::unzip(&mut hasher, temp_dir.path()).await?; + + // Exhaust the reader to compute the hash. + hasher.finish().await.map_err(Error::HashExhaustion)?; + + let hashes = hashers.into_iter().map(HashDigest::from).collect(); + + // Persist the temporary directory to the directory store. + let id = self + .build_context + .cache() + .persist(temp_dir.into_path(), wheel_entry.path()) + .await + .map_err(Error::CacheWrite)?; + + // Create an archive. + let archive = Archive::new(id, hashes); + + // Write the archive pointer to the cache. + let pointer = LocalArchivePointer { + timestamp: modified.timestamp(), + archive: archive.clone(), + }; + pointer.write_to(&pointer_entry).await?; + + Ok(LocalWheel { + dist: Dist::Built(dist.clone()), + archive: self.build_context.cache().archive(&archive.id), + hashes: archive.hashes, + filename: filename.clone(), + }) + } + } + + /// Unzip a wheel into the cache, returning the path to the unzipped directory. + async fn unzip_wheel(&self, path: &Path, target: &Path) -> Result { + let temp_dir = tokio::task::spawn_blocking({ + let path = path.to_owned(); + let root = self.build_context.cache().root().to_path_buf(); + move || -> Result { + // Unzip the wheel into a temporary directory. + let temp_dir = tempfile::tempdir_in(root)?; + uv_extract::unzip(fs_err::File::open(path)?, temp_dir.path())?; + Ok(temp_dir) + } + }) + .await??; + + // Persist the temporary directory to the directory store. + let id = self + .build_context + .cache() + .persist(temp_dir.into_path(), target) + .await + .map_err(Error::CacheWrite)?; + + Ok(id) + } + + /// Returns a GET [`reqwest::Request`] for the given URL. + fn request(&self, url: Url) -> Result { + self.client + .uncached_client() + .get(url) + .header( + // `reqwest` defaults to accepting compressed responses. + // Specify identity encoding to get consistent .whl downloading + // behavior from servers. ref: https://github.com/pypa/pip/pull/1688 + "accept-encoding", + reqwest::header::HeaderValue::from_static("identity"), + ) + .build() + } + /// Return the [`IndexLocations`] used by this resolver. pub fn index_locations(&self) -> &IndexLocations { self.build_context.index_locations() } } + +/// A pointer to an archive in the cache, fetched from an HTTP archive. +/// +/// Encoded with `MsgPack`, and represented on disk by a `.http` file. +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct HttpArchivePointer { + archive: Archive, +} + +impl HttpArchivePointer { + /// Read an [`HttpArchivePointer`] from the cache. + pub fn read_from(path: impl AsRef) -> Result, Error> { + match fs_err::File::open(path.as_ref()) { + Ok(file) => { + let data = DataWithCachePolicy::from_reader(file)?.data; + let archive = rmp_serde::from_slice::(&data)?; + Ok(Some(Self { archive })) + } + Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None), + Err(err) => Err(Error::CacheRead(err)), + } + } + + /// Return the [`Archive`] from the pointer. + pub fn into_archive(self) -> Archive { + self.archive + } +} + +/// A pointer to an archive in the cache, fetched from a local path. +/// +/// Encoded with `MsgPack`, and represented on disk by a `.rev` file. +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct LocalArchivePointer { + timestamp: Timestamp, + archive: Archive, +} + +impl LocalArchivePointer { + /// Read an [`LocalArchivePointer`] from the cache. + pub fn read_from(path: impl AsRef) -> Result, Error> { + match fs_err::read(path) { + Ok(cached) => Ok(Some(rmp_serde::from_slice::(&cached)?)), + Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None), + Err(err) => Err(Error::CacheRead(err)), + } + } + + /// Write an [`LocalArchivePointer`] to the cache. + pub async fn write_to(&self, entry: &CacheEntry) -> Result<(), Error> { + write_atomic(entry.path(), rmp_serde::to_vec(&self)?) + .await + .map_err(Error::CacheWrite) + } + + /// Returns `true` if the archive is up-to-date with the given modified timestamp. + pub fn is_up_to_date(&self, modified: ArchiveTimestamp) -> bool { + self.timestamp == modified.timestamp() + } + + /// Return the [`Archive`] from the pointer. + pub fn into_archive(self) -> Archive { + self.archive + } +} diff --git a/crates/uv-distribution/src/download.rs b/crates/uv-distribution/src/download.rs index 7ee3726b5..68db0b722 100644 --- a/crates/uv-distribution/src/download.rs +++ b/crates/uv-distribution/src/download.rs @@ -1,14 +1,14 @@ use std::path::{Path, PathBuf}; use distribution_filename::WheelFilename; -use distribution_types::{CachedDist, Dist}; -use pypi_types::Metadata23; +use distribution_types::{CachedDist, Dist, Hashed}; +use pypi_types::{HashDigest, Metadata23}; use crate::Error; -/// A wheel that's been unzipped while downloading +/// A locally available wheel. #[derive(Debug, Clone)] -pub struct UnzippedWheel { +pub struct LocalWheel { /// The remote distribution from which this wheel was downloaded. pub(crate) dist: Dist, /// The parsed filename. @@ -16,115 +16,42 @@ pub struct UnzippedWheel { /// The canonicalized path in the cache directory to which the wheel was downloaded. /// Typically, a directory within the archive bucket. pub(crate) archive: PathBuf, -} - -/// A downloaded wheel that's stored on-disk. -#[derive(Debug, Clone)] -pub struct DiskWheel { - /// The remote distribution from which this wheel was downloaded. - pub(crate) dist: Dist, - /// The parsed filename. - pub(crate) filename: WheelFilename, - /// The path to the downloaded wheel. - pub(crate) path: PathBuf, - /// The expected path to the downloaded wheel's entry in the cache. - /// Typically, a symlink within the wheels or built wheels bucket. - pub(crate) target: PathBuf, -} - -/// A wheel built from a source distribution that's stored on-disk. -#[derive(Debug, Clone)] -pub struct BuiltWheel { - /// The remote source distribution from which this wheel was built. - pub(crate) dist: Dist, - /// The parsed filename. - pub(crate) filename: WheelFilename, - /// The path to the built wheel. - pub(crate) path: PathBuf, - /// The expected path to the downloaded wheel's entry in the cache. - /// Typically, a symlink within the wheels or built wheels bucket. - pub(crate) target: PathBuf, -} - -/// A downloaded or built wheel. -#[derive(Debug, Clone)] -pub enum LocalWheel { - Unzipped(UnzippedWheel), - Disk(DiskWheel), - Built(BuiltWheel), + /// The computed hashes of the wheel. + pub(crate) hashes: Vec, } impl LocalWheel { /// Return the path to the downloaded wheel's entry in the cache. pub fn target(&self) -> &Path { - match self { - Self::Unzipped(wheel) => &wheel.archive, - Self::Disk(wheel) => &wheel.target, - Self::Built(wheel) => &wheel.target, - } + &self.archive } /// Return the [`Dist`] from which this wheel was downloaded. pub fn remote(&self) -> &Dist { - match self { - Self::Unzipped(wheel) => wheel.remote(), - Self::Disk(wheel) => wheel.remote(), - Self::Built(wheel) => wheel.remote(), - } + &self.dist } /// Return the [`WheelFilename`] of this wheel. pub fn filename(&self) -> &WheelFilename { - match self { - Self::Unzipped(wheel) => &wheel.filename, - Self::Disk(wheel) => &wheel.filename, - Self::Built(wheel) => &wheel.filename, - } - } - - /// Convert a [`LocalWheel`] into a [`CachedDist`]. - pub fn into_cached_dist(self, archive: PathBuf) -> CachedDist { - match self { - Self::Unzipped(wheel) => CachedDist::from_remote(wheel.dist, wheel.filename, archive), - Self::Disk(wheel) => CachedDist::from_remote(wheel.dist, wheel.filename, archive), - Self::Built(wheel) => CachedDist::from_remote(wheel.dist, wheel.filename, archive), - } + &self.filename } /// Read the [`Metadata23`] from a wheel. pub fn metadata(&self) -> Result { - match self { - Self::Unzipped(wheel) => read_flat_wheel_metadata(&wheel.filename, &wheel.archive), - Self::Disk(wheel) => read_built_wheel_metadata(&wheel.filename, &wheel.path), - Self::Built(wheel) => read_built_wheel_metadata(&wheel.filename, &wheel.path), - } + read_flat_wheel_metadata(&self.filename, &self.archive) } } -impl UnzippedWheel { - /// Return the [`Dist`] from which this wheel was downloaded. - pub fn remote(&self) -> &Dist { - &self.dist - } - - /// Convert an [`UnzippedWheel`] into a [`CachedDist`]. - pub fn into_cached_dist(self) -> CachedDist { - CachedDist::from_remote(self.dist, self.filename, self.archive) +impl Hashed for LocalWheel { + fn hashes(&self) -> &[HashDigest] { + &self.hashes } } -impl DiskWheel { - /// Return the [`Dist`] from which this wheel was downloaded. - pub fn remote(&self) -> &Dist { - &self.dist - } -} - -impl BuiltWheel { - /// Return the [`Dist`] from which this source distribution that this wheel was built from was - /// downloaded. - pub fn remote(&self) -> &Dist { - &self.dist +/// Convert a [`LocalWheel`] into a [`CachedDist`]. +impl From for CachedDist { + fn from(wheel: LocalWheel) -> CachedDist { + CachedDist::from_remote(wheel.dist, wheel.filename, wheel.hashes, wheel.archive) } } @@ -134,18 +61,6 @@ impl std::fmt::Display for LocalWheel { } } -/// Read the [`Metadata23`] from a built wheel. -fn read_built_wheel_metadata( - filename: &WheelFilename, - wheel: impl AsRef, -) -> Result { - let file = fs_err::File::open(wheel.as_ref()).map_err(Error::CacheRead)?; - let reader = std::io::BufReader::new(file); - let mut archive = zip::ZipArchive::new(reader)?; - let metadata = install_wheel_rs::metadata::read_archive_metadata(filename, &mut archive)?; - Ok(Metadata23::parse_metadata(&metadata)?) -} - /// Read the [`Metadata23`] from an unzipped wheel. fn read_flat_wheel_metadata( filename: &WheelFilename, diff --git a/crates/uv-distribution/src/error.rs b/crates/uv-distribution/src/error.rs index 95b313b68..271ee66b0 100644 --- a/crates/uv-distribution/src/error.rs +++ b/crates/uv-distribution/src/error.rs @@ -3,6 +3,8 @@ use tokio::task::JoinError; use zip::result::ZipError; use distribution_filename::WheelFilenameError; +use pep440_rs::Version; +use pypi_types::HashDigest; use uv_client::BetterReqwestError; use uv_normalize::PackageName; @@ -47,8 +49,10 @@ pub enum Error { given: PackageName, metadata: PackageName, }, + #[error("Package metadata version `{metadata}` does not match given version `{given}`")] + VersionMismatch { given: Version, metadata: Version }, #[error("Failed to parse metadata from built wheel")] - Metadata(#[from] pypi_types::Error), + Metadata(#[from] pypi_types::MetadataError), #[error("Failed to read `dist-info` metadata from built wheel")] DistInfo(#[from] install_wheel_rs::Error), #[error("Failed to read zip archive from built wheel")] @@ -62,11 +66,11 @@ pub enum Error { #[error("The source distribution is missing a `PKG-INFO` file")] MissingPkgInfo, #[error("The source distribution does not support static metadata in `PKG-INFO`")] - DynamicPkgInfo(#[source] pypi_types::Error), + DynamicPkgInfo(#[source] pypi_types::MetadataError), #[error("The source distribution is missing a `pyproject.toml` file")] MissingPyprojectToml, #[error("The source distribution does not support static metadata in `pyproject.toml`")] - DynamicPyprojectToml(#[source] pypi_types::Error), + DynamicPyprojectToml(#[source] pypi_types::MetadataError), #[error("Unsupported scheme in URL: {0}")] UnsupportedScheme(String), @@ -78,6 +82,40 @@ pub enum Error { /// Should not occur; only seen when another task panicked. #[error("The task executor is broken, did some other task panic?")] Join(#[from] JoinError), + + /// An I/O error that occurs while exhausting a reader to compute a hash. + #[error("Failed to hash distribution")] + HashExhaustion(#[source] std::io::Error), + + #[error("Hash mismatch for {distribution}\n\nExpected:\n{expected}\n\nComputed:\n{actual}")] + MismatchedHashes { + distribution: String, + expected: String, + actual: String, + }, + + #[error( + "Hash-checking is enabled, but no hashes were provided or computed for: {distribution}" + )] + MissingHashes { distribution: String }, + + #[error("Hash-checking is enabled, but no hashes were computed for: {distribution}\n\nExpected:\n{expected}")] + MissingActualHashes { + distribution: String, + expected: String, + }, + + #[error("Hash-checking is enabled, but no hashes were provided for: {distribution}\n\nComputed:\n{actual}")] + MissingExpectedHashes { + distribution: String, + actual: String, + }, + + #[error("Hash-checking is not supported for local directories: {0}")] + HashesNotSupportedSourceTree(String), + + #[error("Hash-checking is not supported for Git repositories: {0}")] + HashesNotSupportedGit(String), } impl From for Error { @@ -96,3 +134,59 @@ impl From for Error { } } } + +impl Error { + /// Construct a hash mismatch error. + pub fn hash_mismatch( + distribution: String, + expected: &[HashDigest], + actual: &[HashDigest], + ) -> Error { + match (expected.is_empty(), actual.is_empty()) { + (true, true) => Self::MissingHashes { distribution }, + (true, false) => { + let actual = actual + .iter() + .map(|hash| format!(" {hash}")) + .collect::>() + .join("\n"); + + Self::MissingExpectedHashes { + distribution, + actual, + } + } + (false, true) => { + let expected = expected + .iter() + .map(|hash| format!(" {hash}")) + .collect::>() + .join("\n"); + + Self::MissingActualHashes { + distribution, + expected, + } + } + (false, false) => { + let expected = expected + .iter() + .map(|hash| format!(" {hash}")) + .collect::>() + .join("\n"); + + let actual = actual + .iter() + .map(|hash| format!(" {hash}")) + .collect::>() + .join("\n"); + + Self::MismatchedHashes { + distribution, + expected, + actual, + } + } + } + } +} diff --git a/crates/uv-distribution/src/index/built_wheel_index.rs b/crates/uv-distribution/src/index/built_wheel_index.rs index 6dde63ebe..48777ba22 100644 --- a/crates/uv-distribution/src/index/built_wheel_index.rs +++ b/crates/uv-distribution/src/index/built_wheel_index.rs @@ -1,52 +1,72 @@ -use distribution_types::{git_reference, DirectUrlSourceDist, GitSourceDist, PathSourceDist}; +use distribution_types::{ + git_reference, DirectUrlSourceDist, GitSourceDist, Hashed, PathSourceDist, +}; use platform_tags::Tags; use uv_cache::{ArchiveTimestamp, Cache, CacheBucket, CacheShard, WheelCache}; use uv_fs::symlinks; +use uv_types::HashStrategy; use crate::index::cached_wheel::CachedWheel; -use crate::source::{read_http_manifest, read_timestamp_manifest, MANIFEST}; +use crate::source::{HttpRevisionPointer, LocalRevisionPointer, HTTP_REVISION, LOCAL_REVISION}; use crate::Error; /// A local index of built distributions for a specific source distribution. -pub struct BuiltWheelIndex; +#[derive(Debug)] +pub struct BuiltWheelIndex<'a> { + cache: &'a Cache, + tags: &'a Tags, + hasher: &'a HashStrategy, +} + +impl<'a> BuiltWheelIndex<'a> { + /// Initialize an index of built distributions. + pub fn new(cache: &'a Cache, tags: &'a Tags, hasher: &'a HashStrategy) -> Self { + Self { + cache, + tags, + hasher, + } + } -impl BuiltWheelIndex { /// Return the most compatible [`CachedWheel`] for a given source distribution at a direct URL. /// /// This method does not perform any freshness checks and assumes that the source distribution /// is already up-to-date. - pub fn url( - source_dist: &DirectUrlSourceDist, - cache: &Cache, - tags: &Tags, - ) -> Result, Error> { + pub fn url(&self, source_dist: &DirectUrlSourceDist) -> Result, Error> { // For direct URLs, cache directly under the hash of the URL itself. - let cache_shard = cache.shard( + let cache_shard = self.cache.shard( CacheBucket::BuiltWheels, WheelCache::Url(source_dist.url.raw()).root(), ); - // Read the manifest from the cache. There's no need to enforce freshness, since we - // enforce freshness on the entries. - let manifest_entry = cache_shard.entry(MANIFEST); - let Some(manifest) = read_http_manifest(&manifest_entry)? else { + // Read the revision from the cache. + let Some(pointer) = HttpRevisionPointer::read_from(cache_shard.entry(HTTP_REVISION))? + else { return Ok(None); }; - Ok(Self::find(&cache_shard.shard(manifest.id()), tags)) + // Enforce hash-checking by omitting any wheels that don't satisfy the required hashes. + let revision = pointer.into_revision(); + if !revision.satisfies(self.hasher.get(source_dist)) { + return Ok(None); + } + + Ok(self.find(&cache_shard.shard(revision.id()))) } /// Return the most compatible [`CachedWheel`] for a given source distribution at a local path. - pub fn path( - source_dist: &PathSourceDist, - cache: &Cache, - tags: &Tags, - ) -> Result, Error> { - let cache_shard = cache.shard( + pub fn path(&self, source_dist: &PathSourceDist) -> Result, Error> { + let cache_shard = self.cache.shard( CacheBucket::BuiltWheels, WheelCache::Path(&source_dist.url).root(), ); + // Read the revision from the cache. + let Some(pointer) = LocalRevisionPointer::read_from(cache_shard.entry(LOCAL_REVISION))? + else { + return Ok(None); + }; + // Determine the last-modified time of the source distribution. let Some(modified) = ArchiveTimestamp::from_path(&source_dist.path).map_err(Error::CacheRead)? @@ -54,28 +74,37 @@ impl BuiltWheelIndex { return Err(Error::DirWithoutEntrypoint); }; - // Read the manifest from the cache. There's no need to enforce freshness, since we - // enforce freshness on the entries. - let manifest_entry = cache_shard.entry(MANIFEST); - let Some(manifest) = read_timestamp_manifest(&manifest_entry, modified)? else { + // If the distribution is stale, omit it from the index. + if !pointer.is_up_to_date(modified) { return Ok(None); - }; + } - Ok(Self::find(&cache_shard.shard(manifest.id()), tags)) + // Enforce hash-checking by omitting any wheels that don't satisfy the required hashes. + let revision = pointer.into_revision(); + if !revision.satisfies(self.hasher.get(source_dist)) { + return Ok(None); + } + + Ok(self.find(&cache_shard.shard(revision.id()))) } /// Return the most compatible [`CachedWheel`] for a given source distribution at a git URL. - pub fn git(source_dist: &GitSourceDist, cache: &Cache, tags: &Tags) -> Option { + pub fn git(&self, source_dist: &GitSourceDist) -> Option { + // Enforce hash-checking, which isn't supported for Git distributions. + if self.hasher.get(source_dist).is_validate() { + return None; + } + let Ok(Some(git_sha)) = git_reference(&source_dist.url) else { return None; }; - let cache_shard = cache.shard( + let cache_shard = self.cache.shard( CacheBucket::BuiltWheels, WheelCache::Git(&source_dist.url, &git_sha.to_short_string()).root(), ); - Self::find(&cache_shard, tags) + self.find(&cache_shard) } /// Find the "best" distribution in the index for a given source distribution. @@ -94,16 +123,16 @@ impl BuiltWheelIndex { /// ``` /// /// The `shard` should be `built-wheels-v0/pypi/django-allauth-0.51.0.tar.gz`. - fn find(shard: &CacheShard, tags: &Tags) -> Option { + fn find(&self, shard: &CacheShard) -> Option { let mut candidate: Option = None; // Unzipped wheels are stored as symlinks into the archive directory. for subdir in symlinks(shard) { - match CachedWheel::from_path(&subdir) { + match CachedWheel::from_built_source(&subdir) { None => {} Some(dist_info) => { // Pick the wheel with the highest priority - let compatibility = dist_info.filename.compatibility(tags); + let compatibility = dist_info.filename.compatibility(self.tags); // Only consider wheels that are compatible with our tags. if !compatibility.is_compatible() { @@ -113,7 +142,7 @@ impl BuiltWheelIndex { if let Some(existing) = candidate.as_ref() { // Override if the wheel is newer, or "more" compatible. if dist_info.filename.version > existing.filename.version - || compatibility > existing.filename.compatibility(tags) + || compatibility > existing.filename.compatibility(self.tags) { candidate = Some(dist_info); } diff --git a/crates/uv-distribution/src/index/cached_wheel.rs b/crates/uv-distribution/src/index/cached_wheel.rs index a8e4172aa..5efbc817b 100644 --- a/crates/uv-distribution/src/index/cached_wheel.rs +++ b/crates/uv-distribution/src/index/cached_wheel.rs @@ -1,9 +1,12 @@ use std::path::Path; use distribution_filename::WheelFilename; -use distribution_types::{CachedDirectUrlDist, CachedRegistryDist}; +use distribution_types::{CachedDirectUrlDist, CachedRegistryDist, Hashed}; use pep508_rs::VerbatimUrl; -use uv_cache::CacheEntry; +use pypi_types::HashDigest; +use uv_cache::{Cache, CacheBucket, CacheEntry}; + +use crate::{Archive, HttpArchivePointer, LocalArchivePointer}; #[derive(Debug, Clone)] pub struct CachedWheel { @@ -11,16 +14,28 @@ pub struct CachedWheel { pub filename: WheelFilename, /// The [`CacheEntry`] for the wheel. pub entry: CacheEntry, + /// The [`HashDigest`]s for the wheel. + pub hashes: Vec, } impl CachedWheel { /// Try to parse a distribution from a cached directory name (like `typing-extensions-4.8.0-py3-none-any`). - pub fn from_path(path: &Path) -> Option { + pub fn from_built_source(path: impl AsRef) -> Option { + let path = path.as_ref(); + + // Determine the wheel filename. let filename = path.file_name()?.to_str()?; let filename = WheelFilename::from_stem(filename).ok()?; + + // Convert to a cached wheel. let archive = path.canonicalize().ok()?; let entry = CacheEntry::from_path(archive); - Some(Self { filename, entry }) + let hashes = Vec::new(); + Some(Self { + filename, + entry, + hashes, + }) } /// Convert a [`CachedWheel`] into a [`CachedRegistryDist`]. @@ -28,6 +43,7 @@ impl CachedWheel { CachedRegistryDist { filename: self.filename, path: self.entry.into_path_buf(), + hashes: self.hashes, } } @@ -38,6 +54,55 @@ impl CachedWheel { url, path: self.entry.into_path_buf(), editable: false, + hashes: self.hashes, } } + + /// Read a cached wheel from a `.http` pointer (e.g., `anyio-4.0.0-py3-none-any.http`). + pub fn from_http_pointer(path: impl AsRef, cache: &Cache) -> Option { + let path = path.as_ref(); + + // Determine the wheel filename. + let filename = path.file_name()?.to_str()?; + let filename = WheelFilename::from_stem(filename).ok()?; + + // Read the pointer. + let pointer = HttpArchivePointer::read_from(path).ok()??; + let Archive { id, hashes } = pointer.into_archive(); + + // Convert to a cached wheel. + let entry = cache.entry(CacheBucket::Archive, "", id); + Some(Self { + filename, + entry, + hashes, + }) + } + + /// Read a cached wheel from a `.rev` pointer (e.g., `anyio-4.0.0-py3-none-any.rev`). + pub fn from_local_pointer(path: impl AsRef, cache: &Cache) -> Option { + let path = path.as_ref(); + + // Determine the wheel filename. + let filename = path.file_name()?.to_str()?; + let filename = WheelFilename::from_stem(filename).ok()?; + + // Read the pointer. + let pointer = LocalArchivePointer::read_from(path).ok()??; + let Archive { id, hashes } = pointer.into_archive(); + + // Convert to a cached wheel. + let entry = cache.entry(CacheBucket::Archive, "", id); + Some(Self { + filename, + entry, + hashes, + }) + } +} + +impl Hashed for CachedWheel { + fn hashes(&self) -> &[HashDigest] { + &self.hashes + } } diff --git a/crates/uv-distribution/src/index/registry_wheel_index.rs b/crates/uv-distribution/src/index/registry_wheel_index.rs index 1aa5ab017..0149a0cfb 100644 --- a/crates/uv-distribution/src/index/registry_wheel_index.rs +++ b/crates/uv-distribution/src/index/registry_wheel_index.rs @@ -1,19 +1,19 @@ use std::collections::hash_map::Entry; use std::collections::BTreeMap; -use std::path::Path; use rustc_hash::FxHashMap; -use distribution_types::{CachedRegistryDist, FlatIndexLocation, IndexLocations, IndexUrl}; +use distribution_types::{CachedRegistryDist, FlatIndexLocation, Hashed, IndexLocations, IndexUrl}; use pep440_rs::Version; use pep508_rs::VerbatimUrl; use platform_tags::Tags; use uv_cache::{Cache, CacheBucket, WheelCache}; -use uv_fs::{directories, symlinks}; +use uv_fs::{directories, files, symlinks}; use uv_normalize::PackageName; +use uv_types::HashStrategy; use crate::index::cached_wheel::CachedWheel; -use crate::source::{read_http_manifest, MANIFEST}; +use crate::source::{HttpRevisionPointer, LocalRevisionPointer, HTTP_REVISION, LOCAL_REVISION}; /// A local index of distributions that originate from a registry, like `PyPI`. #[derive(Debug)] @@ -21,16 +21,23 @@ pub struct RegistryWheelIndex<'a> { cache: &'a Cache, tags: &'a Tags, index_locations: &'a IndexLocations, + hasher: &'a HashStrategy, index: FxHashMap<&'a PackageName, BTreeMap>, } impl<'a> RegistryWheelIndex<'a> { - /// Initialize an index of cached distributions from a directory. - pub fn new(cache: &'a Cache, tags: &'a Tags, index_locations: &'a IndexLocations) -> Self { + /// Initialize an index of registry distributions. + pub fn new( + cache: &'a Cache, + tags: &'a Tags, + index_locations: &'a IndexLocations, + hasher: &'a HashStrategy, + ) -> Self { Self { cache, tags, index_locations, + hasher, index: FxHashMap::default(), } } @@ -65,6 +72,7 @@ impl<'a> RegistryWheelIndex<'a> { self.cache, self.tags, self.index_locations, + self.hasher, )), }; versions @@ -76,14 +84,18 @@ impl<'a> RegistryWheelIndex<'a> { cache: &Cache, tags: &Tags, index_locations: &IndexLocations, + hasher: &HashStrategy, ) -> BTreeMap { let mut versions = BTreeMap::new(); - // Collect into owned `IndexUrl` + // Collect into owned `IndexUrl`. let flat_index_urls: Vec = index_locations .flat_index() .filter_map(|flat_index| match flat_index { - FlatIndexLocation::Path(_) => None, + FlatIndexLocation::Path(path) => { + let path = fs_err::canonicalize(path).ok()?; + Some(IndexUrl::Path(VerbatimUrl::from_path(path))) + } FlatIndexLocation::Url(url) => { Some(IndexUrl::Url(VerbatimUrl::unknown(url.clone()))) } @@ -97,7 +109,44 @@ impl<'a> RegistryWheelIndex<'a> { WheelCache::Index(index_url).wheel_dir(package.to_string()), ); - Self::add_directory(&wheel_dir, tags, &mut versions); + // For registry wheels, the cache structure is: `//.http` + // or `///.rev`. + for file in files(&wheel_dir) { + match index_url { + // Add files from remote registries. + IndexUrl::Pypi(_) | IndexUrl::Url(_) => { + if file + .extension() + .is_some_and(|ext| ext.eq_ignore_ascii_case("http")) + { + if let Some(wheel) = + CachedWheel::from_http_pointer(wheel_dir.join(file), cache) + { + // Enforce hash-checking based on the built distribution. + if wheel.satisfies(hasher.get_package(package)) { + Self::add_wheel(wheel, tags, &mut versions); + } + } + } + } + // Add files from local registries (e.g., `--find-links`). + IndexUrl::Path(_) => { + if file + .extension() + .is_some_and(|ext| ext.eq_ignore_ascii_case("rev")) + { + if let Some(wheel) = + CachedWheel::from_local_pointer(wheel_dir.join(file), cache) + { + // Enforce hash-checking based on the built distribution. + if wheel.satisfies(hasher.get_package(package)) { + Self::add_wheel(wheel, tags, &mut versions); + } + } + } + } + } + } // Index all the built wheels, created by downloading and building source distributions // from the registry. @@ -110,43 +159,62 @@ impl<'a> RegistryWheelIndex<'a> { for shard in directories(&cache_shard) { // Read the existing metadata from the cache, if it exists. let cache_shard = cache_shard.shard(shard); - let manifest_entry = cache_shard.entry(MANIFEST); - if let Ok(Some(manifest)) = read_http_manifest(&manifest_entry) { - Self::add_directory(cache_shard.join(manifest.id()), tags, &mut versions); + + // Read the revision from the cache. + let revision = match index_url { + // Add files from remote registries. + IndexUrl::Pypi(_) | IndexUrl::Url(_) => { + let revision_entry = cache_shard.entry(HTTP_REVISION); + if let Ok(Some(pointer)) = HttpRevisionPointer::read_from(revision_entry) { + Some(pointer.into_revision()) + } else { + None + } + } + // Add files from local registries (e.g., `--find-links`). + IndexUrl::Path(_) => { + let revision_entry = cache_shard.entry(LOCAL_REVISION); + if let Ok(Some(pointer)) = LocalRevisionPointer::read_from(revision_entry) { + Some(pointer.into_revision()) + } else { + None + } + } }; + + if let Some(revision) = revision { + // Enforce hash-checking based on the source distribution. + if revision.satisfies(hasher.get_package(package)) { + for wheel_dir in symlinks(cache_shard.join(revision.id())) { + if let Some(wheel) = CachedWheel::from_built_source(wheel_dir) { + Self::add_wheel(wheel, tags, &mut versions); + } + } + } + } } } versions } - /// Add the wheels in a given directory to the index. - /// - /// Each subdirectory in the given path is expected to be that of an unzipped wheel. - fn add_directory( - path: impl AsRef, + /// Add the [`CachedWheel`] to the index. + fn add_wheel( + wheel: CachedWheel, tags: &Tags, versions: &mut BTreeMap, ) { - // Unzipped wheels are stored as symlinks into the archive directory. - for wheel_dir in symlinks(path.as_ref()) { - match CachedWheel::from_path(&wheel_dir) { - None => {} - Some(dist_info) => { - let dist_info = dist_info.into_registry_dist(); + let dist_info = wheel.into_registry_dist(); - // Pick the wheel with the highest priority - let compatibility = dist_info.filename.compatibility(tags); - if let Some(existing) = versions.get_mut(&dist_info.filename.version) { - // Override if we have better compatibility - if compatibility > existing.filename.compatibility(tags) { - *existing = dist_info; - } - } else if compatibility.is_compatible() { - versions.insert(dist_info.filename.version.clone(), dist_info); - } - } + // Pick the wheel with the highest priority + let compatibility = dist_info.filename.compatibility(tags); + if let Some(existing) = versions.get_mut(&dist_info.filename.version) { + // Override if we have better compatibility + if compatibility > existing.filename.compatibility(tags) { + *existing = dist_info; } + } else if compatibility.is_compatible() { + versions.insert(dist_info.filename.version.clone(), dist_info); } } } diff --git a/crates/uv-distribution/src/lib.rs b/crates/uv-distribution/src/lib.rs index 5c04678c4..cddb2fea7 100644 --- a/crates/uv-distribution/src/lib.rs +++ b/crates/uv-distribution/src/lib.rs @@ -1,12 +1,14 @@ -pub use distribution_database::DistributionDatabase; -pub use download::{BuiltWheel, DiskWheel, LocalWheel}; +pub use archive::Archive; +pub use distribution_database::{DistributionDatabase, HttpArchivePointer, LocalArchivePointer}; +pub use download::LocalWheel; pub use error::Error; pub use git::{is_same_reference, to_precise}; pub use index::{BuiltWheelIndex, RegistryWheelIndex}; +use pypi_types::{HashDigest, Metadata23}; pub use reporter::Reporter; pub use source::SourceDistributionBuilder; -pub use unzip::Unzip; +mod archive; mod distribution_database; mod download; mod error; @@ -15,4 +17,21 @@ mod index; mod locks; mod reporter; mod source; -mod unzip; + +/// The metadata associated with an archive. +#[derive(Debug, Clone)] +pub struct ArchiveMetadata { + /// The [`Metadata23`] for the underlying distribution. + pub metadata: Metadata23, + /// The hashes of the source or built archive. + pub hashes: Vec, +} + +impl From for ArchiveMetadata { + fn from(metadata: Metadata23) -> Self { + Self { + metadata, + hashes: vec![], + } + } +} diff --git a/crates/uv-distribution/src/source/built_wheel_metadata.rs b/crates/uv-distribution/src/source/built_wheel_metadata.rs index a968b88d1..664e32f8f 100644 --- a/crates/uv-distribution/src/source/built_wheel_metadata.rs +++ b/crates/uv-distribution/src/source/built_wheel_metadata.rs @@ -2,23 +2,27 @@ use std::path::PathBuf; use std::str::FromStr; use distribution_filename::WheelFilename; +use distribution_types::Hashed; use platform_tags::Tags; +use pypi_types::HashDigest; use uv_cache::CacheShard; use uv_fs::files; /// The information about the wheel we either just built or got from the cache. #[derive(Debug, Clone)] -pub struct BuiltWheelMetadata { +pub(crate) struct BuiltWheelMetadata { /// The path to the built wheel. pub(crate) path: PathBuf, /// The expected path to the downloaded wheel's entry in the cache. pub(crate) target: PathBuf, /// The parsed filename. pub(crate) filename: WheelFilename, + /// The computed hashes of the source distribution from which the wheel was built. + pub(crate) hashes: Vec, } impl BuiltWheelMetadata { - /// Find a compatible wheel in the cache based on the given manifest. + /// Find a compatible wheel in the cache. pub(crate) fn find_in_cache(tags: &Tags, cache_shard: &CacheShard) -> Option { for directory in files(cache_shard) { if let Some(metadata) = Self::from_path(directory, cache_shard) { @@ -39,6 +43,20 @@ impl BuiltWheelMetadata { target: cache_shard.join(filename.stem()), path, filename, + hashes: vec![], }) } + + /// Set the computed hashes of the wheel. + #[must_use] + pub(crate) fn with_hashes(mut self, hashes: Vec) -> Self { + self.hashes = hashes; + self + } +} + +impl Hashed for BuiltWheelMetadata { + fn hashes(&self) -> &[HashDigest] { + &self.hashes + } } diff --git a/crates/uv-distribution/src/source/manifest.rs b/crates/uv-distribution/src/source/manifest.rs deleted file mode 100644 index ba1085986..000000000 --- a/crates/uv-distribution/src/source/manifest.rs +++ /dev/null @@ -1,17 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// The [`Manifest`] is a thin wrapper around a unique identifier for the source distribution. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub(crate) struct Manifest(String); - -impl Manifest { - /// Initialize a new [`Manifest`] with a random UUID. - pub(crate) fn new() -> Self { - Self(nanoid::nanoid!()) - } - - /// Return the unique ID of the manifest. - pub(crate) fn id(&self) -> &str { - &self.0 - } -} diff --git a/crates/uv-distribution/src/source/mod.rs b/crates/uv-distribution/src/source/mod.rs index 2ff2873e9..474211f4c 100644 --- a/crates/uv-distribution/src/source/mod.rs +++ b/crates/uv-distribution/src/source/mod.rs @@ -9,7 +9,6 @@ use anyhow::Result; use fs_err::tokio as fs; use futures::{FutureExt, TryStreamExt}; use reqwest::Response; -use tempfile::TempDir; use tokio_util::compat::FuturesAsyncReadCompatExt; use tracing::{debug, info_span, instrument, Instrument}; use url::Url; @@ -17,30 +16,32 @@ use zip::ZipArchive; use distribution_filename::WheelFilename; use distribution_types::{ - BuildableSource, DirectArchiveUrl, Dist, FileLocation, GitSourceUrl, LocalEditable, - PathSourceDist, PathSourceUrl, RemoteSource, SourceDist, SourceUrl, + BuildableSource, DirectArchiveUrl, Dist, FileLocation, GitSourceUrl, HashPolicy, Hashed, + LocalEditable, PathSourceDist, PathSourceUrl, RemoteSource, SourceDist, SourceUrl, }; use install_wheel_rs::metadata::read_archive_metadata; use platform_tags::Tags; -use pypi_types::Metadata23; +use pypi_types::{HashDigest, Metadata23}; use uv_cache::{ - ArchiveTimestamp, Cache, CacheBucket, CacheEntry, CacheShard, CachedByTimestamp, Freshness, + ArchiveTimestamp, CacheBucket, CacheEntry, CacheShard, CachedByTimestamp, Freshness, Timestamp, WheelCache, }; use uv_client::{ CacheControl, CachedClientError, Connectivity, DataWithCachePolicy, RegistryClient, }; +use uv_configuration::{BuildKind, NoBuild}; +use uv_extract::hash::Hasher; use uv_fs::write_atomic; -use uv_types::{BuildContext, BuildKind, NoBuild, SourceBuildTrait}; +use uv_types::{BuildContext, SourceBuildTrait}; use crate::error::Error; use crate::git::{fetch_git_archive, resolve_precise}; use crate::source::built_wheel_metadata::BuiltWheelMetadata; -use crate::source::manifest::Manifest; -use crate::Reporter; +use crate::source::revision::Revision; +use crate::{ArchiveMetadata, Reporter}; mod built_wheel_metadata; -mod manifest; +mod revision; /// Fetch and build a source distribution from a remote source, or from a local cache. pub struct SourceDistributionBuilder<'a, T: BuildContext> { @@ -49,8 +50,11 @@ pub struct SourceDistributionBuilder<'a, T: BuildContext> { reporter: Option>, } -/// The name of the file that contains the cached manifest, encoded via `MsgPack`. -pub(crate) const MANIFEST: &str = "manifest.msgpack"; +/// The name of the file that contains the revision ID for a remote distribution, encoded via `MsgPack`. +pub(crate) const HTTP_REVISION: &str = "revision.http"; + +/// The name of the file that contains the revision ID for a local distribution, encoded via `MsgPack`. +pub(crate) const LOCAL_REVISION: &str = "revision.rev"; /// The name of the file that contains the cached distribution metadata, encoded via `MsgPack`. pub(crate) const METADATA: &str = "metadata.msgpack"; @@ -75,41 +79,14 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { } /// Download and build a [`SourceDist`]. - pub async fn download_and_build( + pub(super) async fn download_and_build( &self, source: &BuildableSource<'_>, tags: &Tags, + hashes: HashPolicy<'_>, ) -> Result { let built_wheel_metadata = match &source { BuildableSource::Dist(SourceDist::Registry(dist)) => { - let url = match &dist.file.url { - FileLocation::RelativeUrl(base, url) => { - pypi_types::base_url_join_relative(base, url)? - } - FileLocation::AbsoluteUrl(url) => { - Url::parse(url).map_err(|err| Error::Url(url.clone(), err))? - } - FileLocation::Path(path) => { - let url = Url::from_file_path(path).expect("path is absolute"); - - // If necessary, extract the archive. - let extracted = extract_archive(path, self.build_context.cache()).await?; - - return self - .path( - source, - &PathSourceUrl { - url: &url, - path: Cow::Borrowed(path), - }, - extracted.path(), - tags, - ) - .boxed() - .await; - } - }; - // For registry source distributions, shard by package, then version, for // convenience in debugging. let cache_shard = self.build_context.cache().shard( @@ -119,93 +96,6 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .join(dist.filename.version.to_string()), ); - self.url(source, &dist.file.filename, &url, &cache_shard, None, tags) - .boxed() - .await? - } - BuildableSource::Dist(SourceDist::DirectUrl(dist)) => { - let filename = dist.filename().expect("Distribution must have a filename"); - let DirectArchiveUrl { url, subdirectory } = DirectArchiveUrl::from(dist.url.raw()); - - // For direct URLs, cache directly under the hash of the URL itself. - let cache_shard = self - .build_context - .cache() - .shard(CacheBucket::BuiltWheels, WheelCache::Url(&url).root()); - - self.url( - source, - &filename, - &url, - &cache_shard, - subdirectory.as_deref(), - tags, - ) - .boxed() - .await? - } - BuildableSource::Dist(SourceDist::Git(dist)) => { - self.git(source, &GitSourceUrl::from(dist), tags) - .boxed() - .await? - } - BuildableSource::Dist(SourceDist::Path(dist)) => { - // If necessary, extract the archive. - let extracted = extract_archive(&dist.path, self.build_context.cache()).await?; - - self.path(source, &PathSourceUrl::from(dist), extracted.path(), tags) - .boxed() - .await? - } - BuildableSource::Url(SourceUrl::Direct(resource)) => { - let filename = resource - .url - .filename() - .expect("Distribution must have a filename"); - let DirectArchiveUrl { url, subdirectory } = DirectArchiveUrl::from(resource.url); - - // For direct URLs, cache directly under the hash of the URL itself. - let cache_shard = self - .build_context - .cache() - .shard(CacheBucket::BuiltWheels, WheelCache::Url(&url).root()); - - self.url( - source, - &filename, - &url, - &cache_shard, - subdirectory.as_deref(), - tags, - ) - .boxed() - .await? - } - BuildableSource::Url(SourceUrl::Git(resource)) => { - self.git(source, resource, tags).boxed().await? - } - BuildableSource::Url(SourceUrl::Path(resource)) => { - // If necessary, extract the archive. - let extracted = extract_archive(&resource.path, self.build_context.cache()).await?; - - self.path(source, resource, extracted.path(), tags) - .boxed() - .await? - } - }; - - Ok(built_wheel_metadata) - } - - /// Download a [`SourceDist`] and determine its metadata. This typically involves building the - /// source distribution into a wheel; however, some build backends support determining the - /// metadata without building the source distribution. - pub async fn download_and_build_metadata( - &self, - source: &BuildableSource<'_>, - ) -> Result { - let metadata = match &source { - BuildableSource::Dist(SourceDist::Registry(dist)) => { let url = match &dist.file.url { FileLocation::RelativeUrl(base, url) => { pypi_types::base_url_join_relative(base, url)? @@ -215,24 +105,140 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { } FileLocation::Path(path) => { let url = Url::from_file_path(path).expect("path is absolute"); - - // If necessary, extract the archive. - let extracted = extract_archive(path, self.build_context.cache()).await?; - return self - .path_metadata( + .archive( source, &PathSourceUrl { url: &url, path: Cow::Borrowed(path), }, - extracted.path(), + &cache_shard, + tags, + hashes, ) .boxed() .await; } }; + self.url( + source, + &dist.file.filename, + &url, + &cache_shard, + None, + tags, + hashes, + ) + .boxed() + .await? + } + BuildableSource::Dist(SourceDist::DirectUrl(dist)) => { + let filename = dist.filename().expect("Distribution must have a filename"); + let DirectArchiveUrl { url, subdirectory } = DirectArchiveUrl::from(dist.url.raw()); + + // For direct URLs, cache directly under the hash of the URL itself. + let cache_shard = self + .build_context + .cache() + .shard(CacheBucket::BuiltWheels, WheelCache::Url(&url).root()); + + self.url( + source, + &filename, + &url, + &cache_shard, + subdirectory.as_deref(), + tags, + hashes, + ) + .boxed() + .await? + } + BuildableSource::Dist(SourceDist::Git(dist)) => { + self.git(source, &GitSourceUrl::from(dist), tags, hashes) + .boxed() + .await? + } + BuildableSource::Dist(SourceDist::Path(dist)) => { + if dist.path.is_dir() { + self.source_tree(source, &PathSourceUrl::from(dist), tags, hashes) + .boxed() + .await? + } else { + let cache_shard = self + .build_context + .cache() + .shard(CacheBucket::BuiltWheels, WheelCache::Path(&dist.url).root()); + self.archive( + source, + &PathSourceUrl::from(dist), + &cache_shard, + tags, + hashes, + ) + .boxed() + .await? + } + } + BuildableSource::Url(SourceUrl::Direct(resource)) => { + let filename = resource + .url + .filename() + .expect("Distribution must have a filename"); + let DirectArchiveUrl { url, subdirectory } = DirectArchiveUrl::from(resource.url); + + // For direct URLs, cache directly under the hash of the URL itself. + let cache_shard = self + .build_context + .cache() + .shard(CacheBucket::BuiltWheels, WheelCache::Url(&url).root()); + + self.url( + source, + &filename, + &url, + &cache_shard, + subdirectory.as_deref(), + tags, + hashes, + ) + .boxed() + .await? + } + BuildableSource::Url(SourceUrl::Git(resource)) => { + self.git(source, resource, tags, hashes).boxed().await? + } + BuildableSource::Url(SourceUrl::Path(resource)) => { + if resource.path.is_dir() { + self.source_tree(source, resource, tags, hashes) + .boxed() + .await? + } else { + let cache_shard = self.build_context.cache().shard( + CacheBucket::BuiltWheels, + WheelCache::Path(resource.url).root(), + ); + self.archive(source, resource, &cache_shard, tags, hashes) + .boxed() + .await? + } + } + }; + + Ok(built_wheel_metadata) + } + + /// Download a [`SourceDist`] and determine its metadata. This typically involves building the + /// source distribution into a wheel; however, some build backends support determining the + /// metadata without building the source distribution. + pub(super) async fn download_and_build_metadata( + &self, + source: &BuildableSource<'_>, + hashes: HashPolicy<'_>, + ) -> Result { + let metadata = match &source { + BuildableSource::Dist(SourceDist::Registry(dist)) => { // For registry source distributions, shard by package, then version. let cache_shard = self.build_context.cache().shard( CacheBucket::BuiltWheels, @@ -241,9 +247,40 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .join(dist.filename.version.to_string()), ); - self.url_metadata(source, &dist.file.filename, &url, &cache_shard, None) - .boxed() - .await? + let url = match &dist.file.url { + FileLocation::RelativeUrl(base, url) => { + pypi_types::base_url_join_relative(base, url)? + } + FileLocation::AbsoluteUrl(url) => { + Url::parse(url).map_err(|err| Error::Url(url.clone(), err))? + } + FileLocation::Path(path) => { + let url = Url::from_file_path(path).expect("path is absolute"); + return self + .archive_metadata( + source, + &PathSourceUrl { + url: &url, + path: Cow::Borrowed(path), + }, + &cache_shard, + hashes, + ) + .boxed() + .await; + } + }; + + self.url_metadata( + source, + &dist.file.filename, + &url, + &cache_shard, + None, + hashes, + ) + .boxed() + .await? } BuildableSource::Dist(SourceDist::DirectUrl(dist)) => { let filename = dist.filename().expect("Distribution must have a filename"); @@ -261,22 +298,30 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { &url, &cache_shard, subdirectory.as_deref(), + hashes, ) .boxed() .await? } BuildableSource::Dist(SourceDist::Git(dist)) => { - self.git_metadata(source, &GitSourceUrl::from(dist)) + self.git_metadata(source, &GitSourceUrl::from(dist), hashes) .boxed() .await? } BuildableSource::Dist(SourceDist::Path(dist)) => { - // If necessary, extract the archive. - let extracted = extract_archive(&dist.path, self.build_context.cache()).await?; - - self.path_metadata(source, &PathSourceUrl::from(dist), extracted.path()) - .boxed() - .await? + if dist.path.is_dir() { + self.source_tree_metadata(source, &PathSourceUrl::from(dist), hashes) + .boxed() + .await? + } else { + let cache_shard = self + .build_context + .cache() + .shard(CacheBucket::BuiltWheels, WheelCache::Path(&dist.url).root()); + self.archive_metadata(source, &PathSourceUrl::from(dist), &cache_shard, hashes) + .boxed() + .await? + } } BuildableSource::Url(SourceUrl::Direct(resource)) => { let filename = resource @@ -297,20 +342,28 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { &url, &cache_shard, subdirectory.as_deref(), + hashes, ) .boxed() .await? } BuildableSource::Url(SourceUrl::Git(resource)) => { - self.git_metadata(source, resource).boxed().await? + self.git_metadata(source, resource, hashes).boxed().await? } BuildableSource::Url(SourceUrl::Path(resource)) => { - // If necessary, extract the archive. - let extracted = extract_archive(&resource.path, self.build_context.cache()).await?; - - self.path_metadata(source, resource, extracted.path()) - .boxed() - .await? + if resource.path.is_dir() { + self.source_tree_metadata(source, resource, hashes) + .boxed() + .await? + } else { + let cache_shard = self.build_context.cache().shard( + CacheBucket::BuiltWheels, + WheelCache::Path(resource.url).root(), + ); + self.archive_metadata(source, resource, &cache_shard, hashes) + .boxed() + .await? + } } }; @@ -327,66 +380,29 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { cache_shard: &CacheShard, subdirectory: Option<&'data Path>, tags: &Tags, + hashes: HashPolicy<'_>, ) -> Result { - let cache_entry = cache_shard.entry(MANIFEST); - let cache_control = match self.client.connectivity() { - Connectivity::Online => CacheControl::from( - self.build_context - .cache() - .freshness(&cache_entry, source.name()) - .map_err(Error::CacheRead)?, - ), - Connectivity::Offline => CacheControl::AllowStale, - }; + // Fetch the revision for the source distribution. + let revision = self + .url_revision(source, filename, url, cache_shard, hashes) + .await?; - let download = |response| { - async { - // At this point, we're seeing a new or updated source distribution. Initialize a - // new manifest, to collect the source and built artifacts. - let manifest = Manifest::new(); + // Before running the build, check that the hashes match. + if !revision.satisfies(hashes) { + return Err(Error::hash_mismatch( + source.to_string(), + hashes.digests(), + revision.hashes(), + )); + } - // Download the source distribution. - debug!("Downloading source distribution: {source}"); - let source_dist_entry = cache_shard.shard(manifest.id()).entry(filename); - self.persist_url(response, source, filename, &source_dist_entry) - .await?; - - Ok(manifest) - } - .boxed() - .instrument(info_span!("download", source_dist = %source)) - }; - let req = self - .client - .uncached_client() - .get(url.clone()) - .header( - // `reqwest` defaults to accepting compressed responses. - // Specify identity encoding to get consistent .whl downloading - // behavior from servers. ref: https://github.com/pypa/pip/pull/1688 - "accept-encoding", - reqwest::header::HeaderValue::from_static("identity"), - ) - .build()?; - let manifest = self - .client - .cached_client() - .get_serde(req, &cache_entry, cache_control, download) - .await - .map_err(|err| match err { - CachedClientError::Callback(err) => err, - CachedClientError::Client(err) => Error::Client(err), - })?; - - // From here on, scope all operations to the current build. Within the manifest shard, - // there's no need to check for freshness, since entries have to be fresher than the - // manifest itself. There's also no need to lock, since we never replace entries within the - // shard. - let cache_shard = cache_shard.shard(manifest.id()); + // Scope all operations to the revision. Within the revision, there's no need to check for + // freshness, since entries have to be fresher than the revision itself. + let cache_shard = cache_shard.shard(revision.id()); // If the cache contains a compatible wheel, return it. if let Some(built_wheel) = BuiltWheelMetadata::find_in_cache(tags, &cache_shard) { - return Ok(built_wheel); + return Ok(built_wheel.with_hashes(revision.into_hashes())); } let task = self @@ -416,6 +432,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { path: cache_shard.join(&disk_filename), target: cache_shard.join(wheel_filename.stem()), filename: wheel_filename, + hashes: revision.into_hashes(), }) } @@ -431,68 +448,34 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { url: &'data Url, cache_shard: &CacheShard, subdirectory: Option<&'data Path>, - ) -> Result { - let cache_entry = cache_shard.entry(MANIFEST); - let cache_control = match self.client.connectivity() { - Connectivity::Online => CacheControl::from( - self.build_context - .cache() - .freshness(&cache_entry, source.name()) - .map_err(Error::CacheRead)?, - ), - Connectivity::Offline => CacheControl::AllowStale, - }; + hashes: HashPolicy<'_>, + ) -> Result { + // Fetch the revision for the source distribution. + let revision = self + .url_revision(source, filename, url, cache_shard, hashes) + .await?; - let download = |response| { - async { - // At this point, we're seeing a new or updated source distribution. Initialize a - // new manifest, to collect the source and built artifacts. - let manifest = Manifest::new(); + // Before running the build, check that the hashes match. + if !revision.satisfies(hashes) { + return Err(Error::hash_mismatch( + source.to_string(), + hashes.digests(), + revision.hashes(), + )); + } - // Download the source distribution. - debug!("Downloading source distribution: {source}"); - let source_dist_entry = cache_shard.shard(manifest.id()).entry(filename); - self.persist_url(response, source, filename, &source_dist_entry) - .await?; - - Ok(manifest) - } - .boxed() - .instrument(info_span!("download", source_dist = %source)) - }; - let req = self - .client - .uncached_client() - .get(url.clone()) - .header( - // `reqwest` defaults to accepting compressed responses. - // Specify identity encoding to get consistent .whl downloading - // behavior from servers. ref: https://github.com/pypa/pip/pull/1688 - "accept-encoding", - reqwest::header::HeaderValue::from_static("identity"), - ) - .build()?; - let manifest = self - .client - .cached_client() - .get_serde(req, &cache_entry, cache_control, download) - .await - .map_err(|err| match err { - CachedClientError::Callback(err) => err, - CachedClientError::Client(err) => Error::Client(err), - })?; - - // From here on, scope all operations to the current build. Within the manifest shard, - // there's no need to check for freshness, since entries have to be fresher than the - // manifest itself. There's also no need to lock, since we never replace entries within the - // shard. - let cache_shard = cache_shard.shard(manifest.id()); + // Scope all operations to the revision. Within the revision, there's no need to check for + // freshness, since entries have to be fresher than the revision itself. + let cache_shard = cache_shard.shard(revision.id()); // If the cache contains compatible metadata, return it. let metadata_entry = cache_shard.entry(METADATA); if let Some(metadata) = read_cached_metadata(&metadata_entry).await? { debug!("Using cached metadata for: {source}"); - return Ok(metadata); + return Ok(ArchiveMetadata { + metadata, + hashes: revision.into_hashes(), + }); } // Otherwise, we either need to build the metadata or the wheel. @@ -513,7 +496,10 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .await .map_err(Error::CacheWrite)?; - return Ok(metadata); + return Ok(ArchiveMetadata { + metadata, + hashes: revision.into_hashes(), + }); } let task = self @@ -538,50 +524,110 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { } } - Ok(metadata) + Ok(ArchiveMetadata { + metadata, + hashes: revision.into_hashes(), + }) } - /// Build a source distribution from a local path. - async fn path( + /// Return the [`Revision`] for a remote URL, refreshing it if necessary. + async fn url_revision( + &self, + source: &BuildableSource<'_>, + filename: &str, + url: &Url, + cache_shard: &CacheShard, + hashes: HashPolicy<'_>, + ) -> Result { + let cache_entry = cache_shard.entry(HTTP_REVISION); + let cache_control = match self.client.connectivity() { + Connectivity::Online => CacheControl::from( + self.build_context + .cache() + .freshness(&cache_entry, source.name()) + .map_err(Error::CacheRead)?, + ), + Connectivity::Offline => CacheControl::AllowStale, + }; + + let download = |response| { + async { + // At this point, we're seeing a new or updated source distribution. Initialize a + // new revision, to collect the source and built artifacts. + let revision = Revision::new(); + + // Download the source distribution. + debug!("Downloading source distribution: {source}"); + let entry = cache_shard.shard(revision.id()).entry(filename); + let hashes = self + .download_archive(response, source, filename, entry.path(), hashes) + .await?; + + Ok(revision.with_hashes(hashes)) + } + .boxed() + .instrument(info_span!("download", source_dist = %source)) + }; + let req = self.request(url.clone())?; + let revision = self + .client + .cached_client() + .get_serde(req, &cache_entry, cache_control, download) + .await + .map_err(|err| match err { + CachedClientError::Callback(err) => err, + CachedClientError::Client(err) => Error::Client(err), + })?; + + // If the archive is missing the required hashes, force a refresh. + if revision.has_digests(hashes) { + Ok(revision) + } else { + self.client + .cached_client() + .skip_cache(self.request(url.clone())?, &cache_entry, download) + .await + .map_err(|err| match err { + CachedClientError::Callback(err) => err, + CachedClientError::Client(err) => Error::Client(err), + }) + } + } + + /// Build a source distribution from a local archive (e.g., `.tar.gz` or `.zip`). + async fn archive( &self, source: &BuildableSource<'_>, resource: &PathSourceUrl<'_>, - source_root: &Path, + cache_shard: &CacheShard, tags: &Tags, + hashes: HashPolicy<'_>, ) -> Result { - let cache_shard = self.build_context.cache().shard( - CacheBucket::BuiltWheels, - WheelCache::Path(resource.url).root(), - ); + // Fetch the revision for the source distribution. + let revision = self + .archive_revision(source, resource, cache_shard, hashes) + .await?; - // Determine the last-modified time of the source distribution. - let Some(modified) = - ArchiveTimestamp::from_path(&resource.path).map_err(Error::CacheRead)? - else { - return Err(Error::DirWithoutEntrypoint); - }; + // Before running the build, check that the hashes match. + if !revision.satisfies(hashes) { + return Err(Error::hash_mismatch( + source.to_string(), + hashes.digests(), + revision.hashes(), + )); + } - // Read the existing metadata from the cache. - let manifest_entry = cache_shard.entry(MANIFEST); - let manifest_freshness = self - .build_context - .cache() - .freshness(&manifest_entry, source.name()) - .map_err(Error::CacheRead)?; - let manifest = - refresh_timestamp_manifest(&manifest_entry, manifest_freshness, modified).await?; - - // From here on, scope all operations to the current build. Within the manifest shard, - // there's no need to check for freshness, since entries have to be fresher than the - // manifest itself. There's also no need to lock, since we never replace entries within the - // shard. - let cache_shard = cache_shard.shard(manifest.id()); + // Scope all operations to the revision. Within the revision, there's no need to check for + // freshness, since entries have to be fresher than the revision itself. + let cache_shard = cache_shard.shard(revision.id()); // If the cache contains a compatible wheel, return it. if let Some(built_wheel) = BuiltWheelMetadata::find_in_cache(tags, &cache_shard) { return Ok(built_wheel); } + let source_entry = cache_shard.entry("source"); + // Otherwise, we need to build a wheel. let task = self .reporter @@ -589,7 +635,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .map(|reporter| reporter.on_build_start(source)); let (disk_filename, filename, metadata) = self - .build_distribution(source, source_root, None, &cache_shard) + .build_distribution(source, source_entry.path(), None, &cache_shard) .await?; if let Some(task) = task { @@ -608,64 +654,54 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { path: cache_shard.join(&disk_filename), target: cache_shard.join(filename.stem()), filename, + hashes: revision.into_hashes(), }) } - /// Build the source distribution's metadata from a local path. + /// Build the source distribution's metadata from a local archive (e.g., `.tar.gz` or `.zip`). /// /// If the build backend supports `prepare_metadata_for_build_wheel`, this method will avoid /// building the wheel. - async fn path_metadata( + async fn archive_metadata( &self, source: &BuildableSource<'_>, resource: &PathSourceUrl<'_>, - source_root: &Path, - ) -> Result { - let cache_shard = self.build_context.cache().shard( - CacheBucket::BuiltWheels, - WheelCache::Path(resource.url).root(), - ); + cache_shard: &CacheShard, + hashes: HashPolicy<'_>, + ) -> Result { + // Fetch the revision for the source distribution. + let revision = self + .archive_revision(source, resource, cache_shard, hashes) + .await?; - // Determine the last-modified time of the source distribution. - let Some(modified) = - ArchiveTimestamp::from_path(&resource.path).map_err(Error::CacheRead)? - else { - return Err(Error::DirWithoutEntrypoint); - }; + // Before running the build, check that the hashes match. + if !revision.satisfies(hashes) { + return Err(Error::hash_mismatch( + source.to_string(), + hashes.digests(), + revision.hashes(), + )); + } - // Read the existing metadata from the cache, to clear stale entries. - let manifest_entry = cache_shard.entry(MANIFEST); - let manifest_freshness = self - .build_context - .cache() - .freshness(&manifest_entry, source.name()) - .map_err(Error::CacheRead)?; - let manifest = - refresh_timestamp_manifest(&manifest_entry, manifest_freshness, modified).await?; - - // From here on, scope all operations to the current build. Within the manifest shard, - // there's no need to check for freshness, since entries have to be fresher than the - // manifest itself. There's also no need to lock, since we never replace entries within the - // shard. - let cache_shard = cache_shard.shard(manifest.id()); + // Scope all operations to the revision. Within the revision, there's no need to check for + // freshness, since entries have to be fresher than the revision itself. + let cache_shard = cache_shard.shard(revision.id()); // If the cache contains compatible metadata, return it. let metadata_entry = cache_shard.entry(METADATA); - if self - .build_context - .cache() - .freshness(&metadata_entry, source.name()) - .is_ok_and(Freshness::is_fresh) - { - if let Some(metadata) = read_cached_metadata(&metadata_entry).await? { - debug!("Using cached metadata for: {source}"); - return Ok(metadata); - } + if let Some(metadata) = read_cached_metadata(&metadata_entry).await? { + debug!("Using cached metadata for: {source}"); + return Ok(ArchiveMetadata { + metadata, + hashes: revision.into_hashes(), + }); } + let source_entry = cache_shard.entry("source"); + // If the backend supports `prepare_metadata_for_build_wheel`, use it. if let Some(metadata) = self - .build_metadata(source, source_root, None) + .build_metadata(source, source_entry.path(), None) .boxed() .await? { @@ -678,7 +714,10 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .await .map_err(Error::CacheWrite)?; - return Ok(metadata); + return Ok(ArchiveMetadata { + metadata, + hashes: revision.into_hashes(), + }); } // Otherwise, we need to build a wheel. @@ -688,7 +727,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .map(|reporter| reporter.on_build_start(source)); let (_disk_filename, _filename, metadata) = self - .build_distribution(source, source_root, None, &cache_shard) + .build_distribution(source, source_entry.path(), None, &cache_shard) .await?; if let Some(task) = task { @@ -703,7 +742,243 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .await .map_err(Error::CacheWrite)?; - Ok(metadata) + Ok(ArchiveMetadata { + metadata, + hashes: revision.into_hashes(), + }) + } + + /// Return the [`Revision`] for a local archive, refreshing it if necessary. + async fn archive_revision( + &self, + source: &BuildableSource<'_>, + resource: &PathSourceUrl<'_>, + cache_shard: &CacheShard, + hashes: HashPolicy<'_>, + ) -> Result { + // Determine the last-modified time of the source distribution. + let modified = ArchiveTimestamp::from_file(&resource.path).map_err(Error::CacheRead)?; + + // Read the existing metadata from the cache. + let revision_entry = cache_shard.entry(LOCAL_REVISION); + + // If the revision already exists, return it. There's no need to check for freshness, since + // we use an exact timestamp. + if let Some(pointer) = LocalRevisionPointer::read_from(&revision_entry)? { + if pointer.is_up_to_date(modified) { + let revision = pointer.into_revision(); + if revision.has_digests(hashes) { + return Ok(revision); + } + } + } + + // Otherwise, we need to create a new revision. + let revision = Revision::new(); + + // Unzip the archive to a temporary directory. + debug!("Unpacking source distribution: {source}"); + let entry = cache_shard.shard(revision.id()).entry("source"); + let hashes = self + .persist_archive(&resource.path, entry.path(), hashes) + .await?; + let revision = revision.with_hashes(hashes); + + // Persist the revision. + write_atomic( + revision_entry.path(), + rmp_serde::to_vec(&CachedByTimestamp { + timestamp: modified.timestamp(), + data: revision.clone(), + })?, + ) + .await + .map_err(Error::CacheWrite)?; + + Ok(revision) + } + + /// Build a source distribution from a local source tree (i.e., directory). + async fn source_tree( + &self, + source: &BuildableSource<'_>, + resource: &PathSourceUrl<'_>, + tags: &Tags, + hashes: HashPolicy<'_>, + ) -> Result { + // Before running the build, check that the hashes match. + if hashes.is_validate() { + return Err(Error::HashesNotSupportedSourceTree(source.to_string())); + } + + let cache_shard = self.build_context.cache().shard( + CacheBucket::BuiltWheels, + WheelCache::Path(resource.url).root(), + ); + + // Fetch the revision for the source distribution. + let revision = self + .source_tree_revision(source, resource, &cache_shard) + .await?; + + // Scope all operations to the revision. Within the revision, there's no need to check for + // freshness, since entries have to be fresher than the revision itself. + let cache_shard = cache_shard.shard(revision.id()); + + // If the cache contains a compatible wheel, return it. + if let Some(built_wheel) = BuiltWheelMetadata::find_in_cache(tags, &cache_shard) { + return Ok(built_wheel); + } + + // Otherwise, we need to build a wheel. + let task = self + .reporter + .as_ref() + .map(|reporter| reporter.on_build_start(source)); + + let (disk_filename, filename, metadata) = self + .build_distribution(source, &resource.path, None, &cache_shard) + .await?; + + if let Some(task) = task { + if let Some(reporter) = self.reporter.as_ref() { + reporter.on_build_complete(source, task); + } + } + + // Store the metadata. + let metadata_entry = cache_shard.entry(METADATA); + write_atomic(metadata_entry.path(), rmp_serde::to_vec(&metadata)?) + .await + .map_err(Error::CacheWrite)?; + + Ok(BuiltWheelMetadata { + path: cache_shard.join(&disk_filename), + target: cache_shard.join(filename.stem()), + filename, + hashes: vec![], + }) + } + + /// Build the source distribution's metadata from a local source tree (i.e., a directory). + /// + /// If the build backend supports `prepare_metadata_for_build_wheel`, this method will avoid + /// building the wheel. + async fn source_tree_metadata( + &self, + source: &BuildableSource<'_>, + resource: &PathSourceUrl<'_>, + hashes: HashPolicy<'_>, + ) -> Result { + // Before running the build, check that the hashes match. + if hashes.is_validate() { + return Err(Error::HashesNotSupportedSourceTree(source.to_string())); + } + + let cache_shard = self.build_context.cache().shard( + CacheBucket::BuiltWheels, + WheelCache::Path(resource.url).root(), + ); + + // Fetch the revision for the source distribution. + let revision = self + .source_tree_revision(source, resource, &cache_shard) + .await?; + + // Scope all operations to the revision. Within the revision, there's no need to check for + // freshness, since entries have to be fresher than the revision itself. + let cache_shard = cache_shard.shard(revision.id()); + + // If the cache contains compatible metadata, return it. + let metadata_entry = cache_shard.entry(METADATA); + if let Some(metadata) = read_cached_metadata(&metadata_entry).await? { + debug!("Using cached metadata for: {source}"); + return Ok(ArchiveMetadata::from(metadata)); + } + + // If the backend supports `prepare_metadata_for_build_wheel`, use it. + if let Some(metadata) = self + .build_metadata(source, &resource.path, None) + .boxed() + .await? + { + // Store the metadata. + let cache_entry = cache_shard.entry(METADATA); + fs::create_dir_all(cache_entry.dir()) + .await + .map_err(Error::CacheWrite)?; + write_atomic(cache_entry.path(), rmp_serde::to_vec(&metadata)?) + .await + .map_err(Error::CacheWrite)?; + + return Ok(ArchiveMetadata::from(metadata)); + } + + // Otherwise, we need to build a wheel. + let task = self + .reporter + .as_ref() + .map(|reporter| reporter.on_build_start(source)); + + let (_disk_filename, _filename, metadata) = self + .build_distribution(source, &resource.path, None, &cache_shard) + .await?; + + if let Some(task) = task { + if let Some(reporter) = self.reporter.as_ref() { + reporter.on_build_complete(source, task); + } + } + + // Store the metadata. + let metadata_entry = cache_shard.entry(METADATA); + write_atomic(metadata_entry.path(), rmp_serde::to_vec(&metadata)?) + .await + .map_err(Error::CacheWrite)?; + + Ok(ArchiveMetadata::from(metadata)) + } + + /// Return the [`Revision`] for a local source tree, refreshing it if necessary. + async fn source_tree_revision( + &self, + source: &BuildableSource<'_>, + resource: &PathSourceUrl<'_>, + cache_shard: &CacheShard, + ) -> Result { + // Determine the last-modified time of the source distribution. + let Some(modified) = + ArchiveTimestamp::from_path(&resource.path).map_err(Error::CacheRead)? + else { + return Err(Error::DirWithoutEntrypoint); + }; + + // Read the existing metadata from the cache. + let entry = cache_shard.entry(LOCAL_REVISION); + let freshness = self + .build_context + .cache() + .freshness(&entry, source.name()) + .map_err(Error::CacheRead)?; + + // If the revision is fresh, return it. + if freshness.is_fresh() { + if let Some(pointer) = LocalRevisionPointer::read_from(&entry)? { + if pointer.timestamp == modified.timestamp() { + return Ok(pointer.into_revision()); + } + } + } + + // Otherwise, we need to create a new revision. + let revision = Revision::new(); + let pointer = LocalRevisionPointer { + timestamp: modified.timestamp(), + revision: revision.clone(), + }; + pointer.write_to(&entry).await?; + + Ok(revision) } /// Build a source distribution from a Git repository. @@ -712,7 +987,13 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { source: &BuildableSource<'_>, resource: &GitSourceUrl<'_>, tags: &Tags, + hashes: HashPolicy<'_>, ) -> Result { + // Before running the build, check that the hashes match. + if hashes.is_validate() { + return Err(Error::HashesNotSupportedGit(source.to_string())); + } + // Resolve to a precise Git SHA. let url = if let Some(url) = resolve_precise( resource.url, @@ -766,6 +1047,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { path: cache_shard.join(&disk_filename), target: cache_shard.join(filename.stem()), filename, + hashes: vec![], }) } @@ -777,7 +1059,13 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { &self, source: &BuildableSource<'_>, resource: &GitSourceUrl<'_>, - ) -> Result { + hashes: HashPolicy<'_>, + ) -> Result { + // Before running the build, check that the hashes match. + if hashes.is_validate() { + return Err(Error::HashesNotSupportedGit(source.to_string())); + } + // Resolve to a precise Git SHA. let url = if let Some(url) = resolve_precise( resource.url, @@ -811,7 +1099,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { { if let Some(metadata) = read_cached_metadata(&metadata_entry).await? { debug!("Using cached metadata for: {source}"); - return Ok(metadata); + return Ok(ArchiveMetadata::from(metadata)); } } @@ -830,7 +1118,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .await .map_err(Error::CacheWrite)?; - return Ok(metadata); + return Ok(ArchiveMetadata::from(metadata)); } // Otherwise, we need to build a wheel. @@ -855,25 +1143,18 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .await .map_err(Error::CacheWrite)?; - Ok(metadata) + Ok(ArchiveMetadata::from(metadata)) } /// Download and unzip a source distribution into the cache from an HTTP response. - async fn persist_url<'data>( + async fn download_archive( &self, response: Response, source: &BuildableSource<'_>, filename: &str, - cache_entry: &'data CacheEntry, - ) -> Result<&'data Path, Error> { - let cache_path = cache_entry.path(); - if cache_path.is_dir() { - debug!("Distribution is already cached: {source}"); - return Ok(cache_path); - } - - // Download and unzip the source distribution into a temporary directory. - let span = info_span!("download_source_dist", filename = filename, source_dist = %source); + target: &Path, + hashes: HashPolicy<'_>, + ) -> Result, Error> { let temp_dir = tempfile::tempdir_in(self.build_context.cache().bucket(CacheBucket::BuiltWheels)) .map_err(Error::CacheWrite)?; @@ -881,9 +1162,24 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .bytes_stream() .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err)) .into_async_read(); - uv_extract::stream::archive(reader.compat(), filename, temp_dir.path()).await?; + + // Create a hasher for each hash algorithm. + let algorithms = hashes.algorithms(); + let mut hashers = algorithms.into_iter().map(Hasher::from).collect::>(); + let mut hasher = uv_extract::hash::HashReader::new(reader.compat(), &mut hashers); + + // Download and unzip the source distribution into a temporary directory. + let span = info_span!("download_source_dist", filename = filename, source_dist = %source); + uv_extract::stream::archive(&mut hasher, filename, temp_dir.path()).await?; drop(span); + // If necessary, exhaust the reader to compute the hash. + if !hashes.is_none() { + hasher.finish().await.map_err(Error::HashExhaustion)?; + } + + let hashes = hashers.into_iter().map(HashDigest::from).collect(); + // Extract the top-level directory. let extracted = match uv_extract::strip_component(temp_dir.path()) { Ok(top_level) => top_level, @@ -892,20 +1188,69 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { }; // Persist it to the cache. - fs_err::tokio::create_dir_all(cache_path.parent().expect("Cache entry to have parent")) + fs_err::tokio::create_dir_all(target.parent().expect("Cache entry to have parent")) .await .map_err(Error::CacheWrite)?; - fs_err::tokio::rename(extracted, &cache_path) + fs_err::tokio::rename(extracted, target) .await .map_err(Error::CacheWrite)?; - Ok(cache_path) + Ok(hashes) + } + + /// Extract a local archive, and store it at the given [`CacheEntry`]. + async fn persist_archive( + &self, + path: &Path, + target: &Path, + hashes: HashPolicy<'_>, + ) -> Result, Error> { + debug!("Unpacking for build: {}", path.display()); + + let temp_dir = + tempfile::tempdir_in(self.build_context.cache().bucket(CacheBucket::BuiltWheels)) + .map_err(Error::CacheWrite)?; + let reader = fs_err::tokio::File::open(&path) + .await + .map_err(Error::CacheRead)?; + + // Create a hasher for each hash algorithm. + let algorithms = hashes.algorithms(); + let mut hashers = algorithms.into_iter().map(Hasher::from).collect::>(); + let mut hasher = uv_extract::hash::HashReader::new(reader, &mut hashers); + + // Unzip the archive into a temporary directory. + uv_extract::stream::archive(&mut hasher, path, &temp_dir.path()).await?; + + // If necessary, exhaust the reader to compute the hash. + if !hashes.is_none() { + hasher.finish().await.map_err(Error::HashExhaustion)?; + } + + let hashes = hashers.into_iter().map(HashDigest::from).collect(); + + // Extract the top-level directory from the archive. + let extracted = match uv_extract::strip_component(temp_dir.path()) { + Ok(top_level) => top_level, + Err(uv_extract::Error::NonSingularArchive(_)) => temp_dir.path().to_path_buf(), + Err(err) => return Err(err.into()), + }; + + // Persist it to the cache. + fs_err::tokio::create_dir_all(target.parent().expect("Cache entry to have parent")) + .await + .map_err(Error::CacheWrite)?; + fs_err::tokio::rename(extracted, &target) + .await + .map_err(Error::CacheWrite)?; + + Ok(hashes) } /// Build a source distribution, storing the built wheel in the cache. /// /// Returns the un-normalized disk filename, the parsed, normalized filename and the metadata - #[instrument(skip_all, fields(dist))] + #[instrument(skip_all, fields(dist = %source))] async fn build_distribution( &self, source: &BuildableSource<'_>, @@ -951,21 +1296,14 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { let metadata = read_wheel_metadata(&filename, cache_shard.join(&disk_filename))?; // Validate the metadata. - if let Some(name) = source.name() { - if metadata.name != *name { - return Err(Error::NameMismatch { - metadata: metadata.name, - given: name.clone(), - }); - } - } + validate(source, &metadata)?; debug!("Finished building: {source}"); Ok((disk_filename, filename, metadata)) } /// Build the metadata for a source distribution. - #[instrument(skip_all, fields(dist))] + #[instrument(skip_all, fields(dist = %source))] async fn build_metadata( &self, source: &BuildableSource<'_>, @@ -980,14 +1318,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { debug!("Found static `PKG-INFO` for: {source}"); // Validate the metadata. - if let Some(name) = source.name() { - if metadata.name != *name { - return Err(Error::NameMismatch { - metadata: metadata.name, - given: name.clone(), - }); - } - } + validate(source, &metadata)?; return Ok(Some(metadata)); } @@ -1003,14 +1334,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { debug!("Found static `pyproject.toml` for: {source}"); // Validate the metadata. - if let Some(name) = source.name() { - if metadata.name != *name { - return Err(Error::NameMismatch { - metadata: metadata.name, - given: name.clone(), - }); - } - } + validate(source, &metadata)?; return Ok(Some(metadata)); } @@ -1050,14 +1374,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { let metadata = Metadata23::parse_metadata(&content)?; // Validate the metadata. - if let Some(name) = source.name() { - if metadata.name != *name { - return Err(Error::NameMismatch { - metadata: metadata.name, - given: name.clone(), - }); - } - } + validate(source, &metadata)?; Ok(Some(metadata)) } @@ -1103,35 +1420,120 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { debug!("Finished building (editable): {dist}"); Ok((dist, disk_filename, filename, metadata)) } + + /// Returns a GET [`reqwest::Request`] for the given URL. + fn request(&self, url: Url) -> Result { + self.client + .uncached_client() + .get(url) + .header( + // `reqwest` defaults to accepting compressed responses. + // Specify identity encoding to get consistent .whl downloading + // behavior from servers. ref: https://github.com/pypa/pip/pull/1688 + "accept-encoding", + reqwest::header::HeaderValue::from_static("identity"), + ) + .build() + } } -#[derive(Debug)] -enum ExtractedSource { - /// The source distribution was passed in as a directory, and so doesn't need to be extracted. - Directory(PathBuf), - /// The source distribution was passed in as an archive, and was extracted into a temporary - /// directory. - /// - /// The extracted archive and temporary directory will be deleted when the `ExtractedSource` is - /// dropped. - #[allow(dead_code)] - Archive(PathBuf, TempDir), -} - -impl ExtractedSource { - /// Return the [`Path`] to the extracted source root. - fn path(&self) -> &Path { - match self { - ExtractedSource::Directory(path) => path, - ExtractedSource::Archive(path, _) => path, +/// Validate that the source distribution matches the built metadata. +fn validate(source: &BuildableSource<'_>, metadata: &Metadata23) -> Result<(), Error> { + if let Some(name) = source.name() { + if metadata.name != *name { + return Err(Error::NameMismatch { + metadata: metadata.name.clone(), + given: name.clone(), + }); } } + + if let Some(version) = source.version() { + if metadata.version != *version { + return Err(Error::VersionMismatch { + metadata: metadata.version.clone(), + given: version.clone(), + }); + } + } + + Ok(()) +} + +/// A pointer to a source distribution revision in the cache, fetched from an HTTP archive. +/// +/// Encoded with `MsgPack`, and represented on disk by a `.http` file. +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub(crate) struct HttpRevisionPointer { + revision: Revision, +} + +impl HttpRevisionPointer { + /// Read an [`HttpRevisionPointer`] from the cache. + pub(crate) fn read_from(path: impl AsRef) -> Result, Error> { + match fs_err::File::open(path.as_ref()) { + Ok(file) => { + let data = DataWithCachePolicy::from_reader(file)?.data; + let revision = rmp_serde::from_slice::(&data)?; + Ok(Some(Self { revision })) + } + Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None), + Err(err) => Err(Error::CacheRead(err)), + } + } + + /// Return the [`Revision`] from the pointer. + pub(crate) fn into_revision(self) -> Revision { + self.revision + } +} + +/// A pointer to a source distribution revision in the cache, fetched from a local path. +/// +/// Encoded with `MsgPack`, and represented on disk by a `.rev` file. +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub(crate) struct LocalRevisionPointer { + timestamp: Timestamp, + revision: Revision, +} + +impl LocalRevisionPointer { + /// Read an [`LocalRevisionPointer`] from the cache. + pub(crate) fn read_from(path: impl AsRef) -> Result, Error> { + match fs_err::read(path) { + Ok(cached) => Ok(Some(rmp_serde::from_slice::( + &cached, + )?)), + Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None), + Err(err) => Err(Error::CacheRead(err)), + } + } + + /// Write an [`LocalRevisionPointer`] to the cache. + async fn write_to(&self, entry: &CacheEntry) -> Result<(), Error> { + fs::create_dir_all(&entry.dir()) + .await + .map_err(Error::CacheWrite)?; + write_atomic(entry.path(), rmp_serde::to_vec(&self)?) + .await + .map_err(Error::CacheWrite) + } + + /// Returns `true` if the revision is up-to-date with the given modified timestamp. + pub(crate) fn is_up_to_date(&self, modified: ArchiveTimestamp) -> bool { + self.timestamp == modified.timestamp() + } + + /// Return the [`Revision`] from the pointer. + pub(crate) fn into_revision(self) -> Revision { + self.revision + } } /// Read the [`Metadata23`] from a source distribution's `PKG-INFO` file, if it uses Metadata 2.2 /// or later _and_ none of the required fields (`Requires-Python`, `Requires-Dist`, and /// `Provides-Extra`) are marked as dynamic. -pub(crate) async fn read_pkg_info( +async fn read_pkg_info( source_tree: &Path, subdirectory: Option<&Path>, ) -> Result { @@ -1156,7 +1558,7 @@ pub(crate) async fn read_pkg_info( /// Read the [`Metadata23`] from a source distribution's `pyproject.tom` file, if it defines static /// metadata consistent with PEP 621. -pub(crate) async fn read_pyproject_toml( +async fn read_pyproject_toml( source_tree: &Path, subdirectory: Option<&Path>, ) -> Result { @@ -1180,75 +1582,8 @@ pub(crate) async fn read_pyproject_toml( Ok(metadata) } -/// Read an existing HTTP-cached [`Manifest`], if it exists. -pub(crate) fn read_http_manifest(cache_entry: &CacheEntry) -> Result, Error> { - match fs_err::File::open(cache_entry.path()) { - Ok(file) => { - let data = DataWithCachePolicy::from_reader(file)?.data; - Ok(Some(rmp_serde::from_slice::(&data)?)) - } - Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None), - Err(err) => Err(Error::CacheRead(err)), - } -} - -/// Read an existing timestamped [`Manifest`], if it exists and is up-to-date. -/// -/// If the cache entry is stale, a new entry will be created. -pub(crate) fn read_timestamp_manifest( - cache_entry: &CacheEntry, - modified: ArchiveTimestamp, -) -> Result, Error> { - // If the cache entry is up-to-date, return it. - match fs_err::read(cache_entry.path()) { - Ok(cached) => { - let cached = rmp_serde::from_slice::>(&cached)?; - if cached.timestamp == modified.timestamp() { - return Ok(Some(cached.data)); - } - } - Err(err) if err.kind() == std::io::ErrorKind::NotFound => {} - Err(err) => return Err(Error::CacheRead(err)), - } - Ok(None) -} - -/// Read an existing timestamped [`Manifest`], if it exists and is up-to-date. -/// -/// If the cache entry is stale, a new entry will be created. -pub(crate) async fn refresh_timestamp_manifest( - cache_entry: &CacheEntry, - freshness: Freshness, - modified: ArchiveTimestamp, -) -> Result { - // If we know the exact modification time, we don't need to force a revalidate. - if matches!(modified, ArchiveTimestamp::Exact(_)) || freshness.is_fresh() { - if let Some(manifest) = read_timestamp_manifest(cache_entry, modified)? { - return Ok(manifest); - } - } - - // Otherwise, create a new manifest. - let manifest = Manifest::new(); - fs::create_dir_all(&cache_entry.dir()) - .await - .map_err(Error::CacheWrite)?; - write_atomic( - cache_entry.path(), - rmp_serde::to_vec(&CachedByTimestamp { - timestamp: modified.timestamp(), - data: manifest.clone(), - })?, - ) - .await - .map_err(Error::CacheWrite)?; - Ok(manifest) -} - /// Read an existing cached [`Metadata23`], if it exists. -pub(crate) async fn read_cached_metadata( - cache_entry: &CacheEntry, -) -> Result, Error> { +async fn read_cached_metadata(cache_entry: &CacheEntry) -> Result, Error> { match fs::read(&cache_entry.path()).await { Ok(cached) => Ok(Some(rmp_serde::from_slice::(&cached)?)), Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None), @@ -1267,42 +1602,3 @@ fn read_wheel_metadata( let dist_info = read_archive_metadata(filename, &mut archive)?; Ok(Metadata23::parse_metadata(&dist_info)?) } - -/// Extract a local source distribution, if it's stored as a `.tar.gz` or `.zip` archive. -/// -/// TODO(charlie): Consider storing the extracted source in the cache, to avoid re-extracting -/// on every invocation. -async fn extract_archive(path: &Path, cache: &Cache) -> Result { - let metadata = match fs::metadata(&path).await { - Ok(metadata) => metadata, - Err(err) if err.kind() == std::io::ErrorKind::NotFound => { - return Err(Error::NotFound(path.to_path_buf())); - } - Err(err) => return Err(Error::CacheRead(err)), - }; - - if metadata.is_dir() { - Ok(ExtractedSource::Directory(path.to_path_buf())) - } else { - debug!("Unpacking for build: {}", path.display()); - - let temp_dir = tempfile::tempdir_in(cache.bucket(CacheBucket::BuiltWheels)) - .map_err(Error::CacheWrite)?; - - // Unzip the archive into the temporary directory. - let reader = fs_err::tokio::File::open(&path) - .await - .map_err(Error::CacheRead)?; - uv_extract::seek::archive(tokio::io::BufReader::new(reader), path, &temp_dir.path()) - .await?; - - // Extract the top-level directory from the archive. - let extracted = match uv_extract::strip_component(temp_dir.path()) { - Ok(top_level) => top_level, - Err(uv_extract::Error::NonSingularArchive(_)) => temp_dir.path().to_path_buf(), - Err(err) => return Err(err.into()), - }; - - Ok(ExtractedSource::Archive(extracted, temp_dir)) - } -} diff --git a/crates/uv-distribution/src/source/revision.rs b/crates/uv-distribution/src/source/revision.rs new file mode 100644 index 000000000..64cbc127b --- /dev/null +++ b/crates/uv-distribution/src/source/revision.rs @@ -0,0 +1,72 @@ +use distribution_types::Hashed; +use serde::{Deserialize, Serialize}; +use std::path::Path; + +use pypi_types::HashDigest; + +/// The [`Revision`] is a thin wrapper around a unique identifier for the source distribution. +/// +/// A revision represents a unique version of a source distribution, at a level more granular than +/// (e.g.) the version number of the distribution itself. For example, a source distribution hosted +/// at a URL or a local file path may have multiple revisions, each representing a unique state of +/// the distribution, despite the reported version number remaining the same. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct Revision { + id: RevisionId, + hashes: Vec, +} + +impl Revision { + /// Initialize a new [`Revision`] with a random UUID. + pub(crate) fn new() -> Self { + Self { + id: RevisionId::new(), + hashes: vec![], + } + } + + /// Return the unique ID of the manifest. + pub(crate) fn id(&self) -> &RevisionId { + &self.id + } + + /// Return the computed hashes of the archive. + pub(crate) fn hashes(&self) -> &[HashDigest] { + &self.hashes + } + + /// Return the computed hashes of the archive. + pub(crate) fn into_hashes(self) -> Vec { + self.hashes + } + + /// Set the computed hashes of the archive. + #[must_use] + pub(crate) fn with_hashes(mut self, hashes: Vec) -> Self { + self.hashes = hashes; + self + } +} + +impl Hashed for Revision { + fn hashes(&self) -> &[HashDigest] { + &self.hashes + } +} + +/// A unique identifier for a revision of a source distribution. +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub(crate) struct RevisionId(String); + +impl RevisionId { + /// Generate a new unique identifier for an archive. + fn new() -> Self { + Self(nanoid::nanoid!()) + } +} + +impl AsRef for RevisionId { + fn as_ref(&self) -> &Path { + self.0.as_ref() + } +} diff --git a/crates/uv-distribution/src/unzip.rs b/crates/uv-distribution/src/unzip.rs deleted file mode 100644 index 92f5b4867..000000000 --- a/crates/uv-distribution/src/unzip.rs +++ /dev/null @@ -1,36 +0,0 @@ -use std::path::Path; - -use tracing::instrument; - -use uv_extract::Error; - -use crate::download::BuiltWheel; -use crate::{DiskWheel, LocalWheel}; - -pub trait Unzip { - /// Unzip a wheel into the target directory. - fn unzip(&self, target: &Path) -> Result<(), Error>; -} - -impl Unzip for DiskWheel { - fn unzip(&self, target: &Path) -> Result<(), Error> { - uv_extract::unzip(fs_err::File::open(&self.path)?, target) - } -} - -impl Unzip for BuiltWheel { - fn unzip(&self, target: &Path) -> Result<(), Error> { - uv_extract::unzip(fs_err::File::open(&self.path)?, target) - } -} - -impl Unzip for LocalWheel { - #[instrument(skip_all, fields(filename=self.filename().to_string()))] - fn unzip(&self, target: &Path) -> Result<(), Error> { - match self { - Self::Unzipped(_) => Ok(()), - Self::Disk(wheel) => wheel.unzip(target), - Self::Built(wheel) => wheel.unzip(target), - } - } -} diff --git a/crates/uv-extract/Cargo.toml b/crates/uv-extract/Cargo.toml index ca0704c4c..0c3dbb8fb 100644 --- a/crates/uv-extract/Cargo.toml +++ b/crates/uv-extract/Cargo.toml @@ -13,12 +13,16 @@ license = { workspace = true } workspace = true [dependencies] -async-compression = { workspace = true, features = ["gzip"] } +pypi-types = { workspace = true } + +async-compression = { workspace = true, features = ["gzip", "zstd"] } async_zip = { workspace = true, features = ["tokio"] } fs-err = { workspace = true, features = ["tokio"] } futures = { workspace = true } +md-5.workspace = true rayon = { workspace = true } rustc-hash = { workspace = true } +sha2 = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, features = ["io-util"] } tokio-tar = { workspace = true } diff --git a/crates/uv-extract/src/hash.rs b/crates/uv-extract/src/hash.rs new file mode 100644 index 000000000..22072f7c8 --- /dev/null +++ b/crates/uv-extract/src/hash.rs @@ -0,0 +1,146 @@ +use std::pin::Pin; +use std::task::{Context, Poll}; + +use sha2::Digest; +use tokio::io::{AsyncReadExt, ReadBuf}; + +use pypi_types::{HashAlgorithm, HashDigest}; + +pub struct Sha256Reader<'a, R> { + reader: R, + hasher: &'a mut sha2::Sha256, +} + +impl<'a, R> Sha256Reader<'a, R> +where + R: tokio::io::AsyncRead + Unpin, +{ + pub fn new(reader: R, hasher: &'a mut sha2::Sha256) -> Self { + Sha256Reader { reader, hasher } + } +} + +impl<'a, R> tokio::io::AsyncRead for Sha256Reader<'a, R> +where + R: tokio::io::AsyncRead + Unpin, +{ + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + let reader = Pin::new(&mut self.reader); + match reader.poll_read(cx, buf) { + Poll::Ready(Ok(())) => { + self.hasher.update(buf.filled()); + Poll::Ready(Ok(())) + } + other => other, + } + } +} + +#[derive(Debug)] +pub enum Hasher { + Md5(md5::Md5), + Sha256(sha2::Sha256), + Sha384(sha2::Sha384), + Sha512(sha2::Sha512), +} + +impl Hasher { + pub fn update(&mut self, data: &[u8]) { + match self { + Hasher::Md5(hasher) => hasher.update(data), + Hasher::Sha256(hasher) => hasher.update(data), + Hasher::Sha384(hasher) => hasher.update(data), + Hasher::Sha512(hasher) => hasher.update(data), + } + } + + pub fn finalize(self) -> Vec { + match self { + Hasher::Md5(hasher) => hasher.finalize().to_vec(), + Hasher::Sha256(hasher) => hasher.finalize().to_vec(), + Hasher::Sha384(hasher) => hasher.finalize().to_vec(), + Hasher::Sha512(hasher) => hasher.finalize().to_vec(), + } + } +} + +impl From for Hasher { + fn from(algorithm: HashAlgorithm) -> Self { + match algorithm { + HashAlgorithm::Md5 => Hasher::Md5(md5::Md5::new()), + HashAlgorithm::Sha256 => Hasher::Sha256(sha2::Sha256::new()), + HashAlgorithm::Sha384 => Hasher::Sha384(sha2::Sha384::new()), + HashAlgorithm::Sha512 => Hasher::Sha512(sha2::Sha512::new()), + } + } +} + +impl From for HashDigest { + fn from(hasher: Hasher) -> Self { + match hasher { + Hasher::Md5(hasher) => HashDigest { + algorithm: HashAlgorithm::Md5, + digest: format!("{:x}", hasher.finalize()).into_boxed_str(), + }, + Hasher::Sha256(hasher) => HashDigest { + algorithm: HashAlgorithm::Sha256, + digest: format!("{:x}", hasher.finalize()).into_boxed_str(), + }, + Hasher::Sha384(hasher) => HashDigest { + algorithm: HashAlgorithm::Sha384, + digest: format!("{:x}", hasher.finalize()).into_boxed_str(), + }, + Hasher::Sha512(hasher) => HashDigest { + algorithm: HashAlgorithm::Sha512, + digest: format!("{:x}", hasher.finalize()).into_boxed_str(), + }, + } + } +} + +pub struct HashReader<'a, R> { + reader: R, + hashers: &'a mut [Hasher], +} + +impl<'a, R> HashReader<'a, R> +where + R: tokio::io::AsyncRead + Unpin, +{ + pub fn new(reader: R, hashers: &'a mut [Hasher]) -> Self { + HashReader { reader, hashers } + } + + /// Exhaust the underlying reader. + pub async fn finish(&mut self) -> Result<(), std::io::Error> { + while self.read(&mut vec![0; 8192]).await? > 0 {} + + Ok(()) + } +} + +impl<'a, R> tokio::io::AsyncRead for HashReader<'a, R> +where + R: tokio::io::AsyncRead + Unpin, +{ + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + let reader = Pin::new(&mut self.reader); + match reader.poll_read(cx, buf) { + Poll::Ready(Ok(())) => { + for hasher in self.hashers.iter_mut() { + hasher.update(buf.filled()); + } + Poll::Ready(Ok(())) + } + other => other, + } + } +} diff --git a/crates/uv-extract/src/lib.rs b/crates/uv-extract/src/lib.rs index 20d433071..192aaa8e0 100644 --- a/crates/uv-extract/src/lib.rs +++ b/crates/uv-extract/src/lib.rs @@ -2,6 +2,7 @@ pub use error::Error; pub use sync::*; mod error; +pub mod hash; pub mod seek; pub mod stream; mod sync; diff --git a/crates/uv-extract/src/seek.rs b/crates/uv-extract/src/seek.rs index c4adbbb6c..57499fc86 100644 --- a/crates/uv-extract/src/seek.rs +++ b/crates/uv-extract/src/seek.rs @@ -14,7 +14,7 @@ pub async fn unzip( target: impl AsRef, ) -> Result<(), Error> { let target = target.as_ref(); - let mut reader = reader.compat(); + let mut reader = futures::io::BufReader::new(reader.compat()); let mut zip = async_zip::base::read::seek::ZipFileReader::new(&mut reader).await?; let mut directories = FxHashSet::default(); @@ -81,7 +81,7 @@ pub async fn unzip( } /// Unzip a `.zip` or `.tar.gz` archive into the target directory, requiring `Seek`. -pub async fn archive( +pub async fn archive( reader: R, source: impl AsRef, target: impl AsRef, @@ -107,7 +107,22 @@ pub async fn archive( .is_some_and(|ext| ext.eq_ignore_ascii_case("tar")) }) { - crate::stream::untar(reader, target).await?; + crate::stream::untar_gz(reader, target).await?; + return Ok(()); + } + + // `.tar.zst` + if source + .as_ref() + .extension() + .is_some_and(|ext| ext.eq_ignore_ascii_case("zst")) + && source.as_ref().file_stem().is_some_and(|stem| { + Path::new(stem) + .extension() + .is_some_and(|ext| ext.eq_ignore_ascii_case("tar")) + }) + { + crate::stream::untar_zst(reader, target).await?; return Ok(()); } diff --git a/crates/uv-extract/src/stream.rs b/crates/uv-extract/src/stream.rs index 3754064a7..e73db2ae4 100644 --- a/crates/uv-extract/src/stream.rs +++ b/crates/uv-extract/src/stream.rs @@ -66,10 +66,7 @@ pub async fn unzip( use std::fs::Permissions; use std::os::unix::fs::PermissionsExt; - // To avoid lots of small reads to `reader` when parsing the central directory, wrap it in - // a buffer. - let mut buf = futures::io::BufReader::new(reader); - let mut directory = async_zip::base::read::cd::CentralDirectoryReader::new(&mut buf); + let mut directory = async_zip::base::read::cd::CentralDirectoryReader::new(&mut reader); while let Some(entry) = directory.next().await? { if entry.dir()? { continue; @@ -154,11 +151,30 @@ async fn untar_in>( /// Unzip a `.tar.gz` archive into the target directory, without requiring `Seek`. /// /// This is useful for unpacking files as they're being downloaded. -pub async fn untar( +pub async fn untar_gz( reader: R, target: impl AsRef, ) -> Result<(), Error> { + let reader = tokio::io::BufReader::new(reader); let decompressed_bytes = async_compression::tokio::bufread::GzipDecoder::new(reader); + + let mut archive = tokio_tar::ArchiveBuilder::new(decompressed_bytes) + .set_preserve_mtime(false) + .build(); + untar_in(&mut archive, target.as_ref()).await?; + Ok(()) +} + +/// Unzip a `.tar.zst` archive into the target directory, without requiring `Seek`. +/// +/// This is useful for unpacking files as they're being downloaded. +pub async fn untar_zst( + reader: R, + target: impl AsRef, +) -> Result<(), Error> { + let reader = tokio::io::BufReader::new(reader); + let decompressed_bytes = async_compression::tokio::bufread::ZstdDecoder::new(reader); + let mut archive = tokio_tar::ArchiveBuilder::new(decompressed_bytes) .set_preserve_mtime(false) .build(); @@ -166,7 +182,7 @@ pub async fn untar( } /// Unzip a `.zip` or `.tar.gz` archive into the target directory, without requiring `Seek`. -pub async fn archive( +pub async fn archive( reader: R, source: impl AsRef, target: impl AsRef, @@ -192,7 +208,22 @@ pub async fn archive( .is_some_and(|ext| ext.eq_ignore_ascii_case("tar")) }) { - untar(reader, target).await?; + untar_gz(reader, target).await?; + return Ok(()); + } + + // `.tar.zst` + if source + .as_ref() + .extension() + .is_some_and(|ext| ext.eq_ignore_ascii_case("zst")) + && source.as_ref().file_stem().is_some_and(|stem| { + Path::new(stem) + .extension() + .is_some_and(|ext| ext.eq_ignore_ascii_case("tar")) + }) + { + untar_zst(reader, target).await?; return Ok(()); } diff --git a/crates/uv-git/src/git.rs b/crates/uv-git/src/git.rs index faddeefa1..cd37c9fc2 100644 --- a/crates/uv-git/src/git.rs +++ b/crates/uv-git/src/git.rs @@ -25,20 +25,14 @@ const CHECKOUT_READY_LOCK: &str = ".ok"; /// A reference to commit or commit-ish. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum GitReference { - /// From a branch. - #[allow(unused)] - Branch(String), - /// From a tag. - #[allow(unused)] - Tag(String), /// From a reference that's ambiguously a branch or tag. BranchOrTag(String), + /// From a reference that's ambiguously a short commit, a branch, or a tag. + BranchOrTagOrCommit(String), + /// From a named reference, like `refs/pull/493/head`. + NamedRef(String), /// From a specific revision, using a full 40-character commit hash. FullCommit(String), - /// From a truncated revision. - ShortCommit(String), - /// From a named reference, like `refs/pull/493/head`. - Ref(String), /// The default branch of the repository, the reference named `HEAD`. DefaultBranch, } @@ -52,37 +46,28 @@ enum RefspecStrategy { } impl GitReference { + /// Creates a [`GitReference`] from a revision string. pub(crate) fn from_rev(rev: &str) -> Self { if rev.starts_with("refs/") { - Self::Ref(rev.to_owned()) + Self::NamedRef(rev.to_owned()) } else if looks_like_commit_hash(rev) { if rev.len() == 40 { Self::FullCommit(rev.to_owned()) } else { - Self::ShortCommit(rev.to_owned()) + Self::BranchOrTagOrCommit(rev.to_owned()) } } else { Self::BranchOrTag(rev.to_owned()) } } - pub fn precise(&self) -> Option<&str> { - match self { - Self::FullCommit(rev) => Some(rev), - Self::ShortCommit(rev) => Some(rev), - _ => None, - } - } - /// Converts the [`GitReference`] to a `str`. pub fn as_str(&self) -> Option<&str> { match self { - Self::Branch(rev) => Some(rev), - Self::Tag(rev) => Some(rev), Self::BranchOrTag(rev) => Some(rev), Self::FullCommit(rev) => Some(rev), - Self::ShortCommit(rev) => Some(rev), - Self::Ref(rev) => Some(rev), + Self::BranchOrTagOrCommit(rev) => Some(rev), + Self::NamedRef(rev) => Some(rev), Self::DefaultBranch => None, } } @@ -90,12 +75,10 @@ impl GitReference { /// Converts the [`GitReference`] to a `str` that can be used as a revision. pub(crate) fn as_rev(&self) -> &str { match self { - Self::Branch(rev) - | Self::Tag(rev) - | Self::BranchOrTag(rev) + Self::BranchOrTag(rev) | Self::FullCommit(rev) - | Self::ShortCommit(rev) - | Self::Ref(rev) => rev, + | Self::BranchOrTagOrCommit(rev) + | Self::NamedRef(rev) => rev, Self::DefaultBranch => "HEAD", } } @@ -103,12 +86,10 @@ impl GitReference { /// Returns the kind of this reference. pub(crate) fn kind_str(&self) -> &str { match self { - Self::Branch(_) => "branch", - Self::Tag(_) => "tag", Self::BranchOrTag(_) => "branch or tag", Self::FullCommit(_) => "commit", - Self::ShortCommit(_) => "short commit", - Self::Ref(_) => "ref", + Self::BranchOrTagOrCommit(_) => "branch, tag, or commit", + Self::NamedRef(_) => "ref", Self::DefaultBranch => "default branch", } } @@ -281,37 +262,18 @@ impl GitReference { pub(crate) fn resolve(&self, repo: &git2::Repository) -> Result { let refkind = self.kind_str(); let id = match self { - // Note that we resolve the named tag here in sync with where it's - // fetched into via `fetch` below. - Self::Tag(s) => (|| -> Result { - let refname = format!("refs/remotes/origin/tags/{s}"); - let id = repo.refname_to_id(&refname)?; - let obj = repo.find_object(id, None)?; - let obj = obj.peel(ObjectType::Commit)?; - Ok(obj.id()) - })() - .with_context(|| format!("failed to find {refkind} `{s}`"))?, - - // Resolve the remote name since that's all we're configuring in - // `fetch` below. - Self::Branch(s) => { - let name = format!("origin/{s}"); - let b = repo - .find_branch(&name, git2::BranchType::Remote) - .with_context(|| format!("failed to find {refkind} `{s}`"))?; - b.get() - .target() - .ok_or_else(|| anyhow::format_err!("{refkind} `{s}` did not have a target"))? - } - // Attempt to resolve the branch, then the tag. Self::BranchOrTag(s) => { let name = format!("origin/{s}"); + // Resolve the remote name since that's all we're configuring in + // `fetch` below. repo.find_branch(&name, git2::BranchType::Remote) .ok() .and_then(|b| b.get().target()) .or_else(|| { + // Note that we resolve the named tag here in sync with where it's + // fetched into via `fetch` below. let refname = format!("refs/remotes/origin/tags/{s}"); let id = repo.refname_to_id(&refname).ok()?; let obj = repo.find_object(id, None).ok()?; @@ -321,6 +283,35 @@ impl GitReference { .ok_or_else(|| anyhow::format_err!("failed to find {refkind} `{s}`"))? } + // Attempt to resolve the branch, then the tag, then the commit. + Self::BranchOrTagOrCommit(s) => { + let name = format!("origin/{s}"); + + // Resolve the remote name since that's all we're configuring in + // `fetch` below. + repo.find_branch(&name, git2::BranchType::Remote) + .ok() + .and_then(|b| b.get().target()) + .or_else(|| { + // Note that we resolve the named tag here in sync with where it's + // fetched into via `fetch` below. + let refname = format!("refs/remotes/origin/tags/{s}"); + let id = repo.refname_to_id(&refname).ok()?; + let obj = repo.find_object(id, None).ok()?; + let obj = obj.peel(ObjectType::Commit).ok()?; + Some(obj.id()) + }) + .or_else(|| { + // Resolve the commit. + let obj = repo.revparse_single(s).ok()?; + match obj.as_tag() { + Some(tag) => Some(tag.target_id()), + None => Some(obj.id()), + } + }) + .ok_or_else(|| anyhow::format_err!("failed to find {refkind} `{s}`"))? + } + // We'll be using the HEAD commit Self::DefaultBranch => { let head_id = repo.refname_to_id("refs/remotes/origin/HEAD")?; @@ -328,7 +319,7 @@ impl GitReference { head.peel(ObjectType::Commit)?.id() } - Self::FullCommit(s) | Self::ShortCommit(s) | Self::Ref(s) => { + Self::FullCommit(s) | Self::NamedRef(s) => { let obj = repo.revparse_single(s)?; match obj.as_tag() { Some(tag) => tag.target_id(), @@ -958,14 +949,6 @@ pub(crate) fn fetch( match reference { // For branches and tags we can fetch simply one reference and copy it // locally, no need to fetch other branches/tags. - GitReference::Branch(branch) => { - refspecs.push(format!("+refs/heads/{branch}:refs/remotes/origin/{branch}")); - } - - GitReference::Tag(tag) => { - refspecs.push(format!("+refs/tags/{tag}:refs/remotes/origin/tags/{tag}")); - } - GitReference::BranchOrTag(branch_or_tag) => { refspecs.push(format!( "+refs/heads/{branch_or_tag}:refs/remotes/origin/{branch_or_tag}" @@ -976,11 +959,31 @@ pub(crate) fn fetch( refspec_strategy = RefspecStrategy::First; } + // For ambiguous references, we can fetch the exact commit (if known); otherwise, + // we fetch all branches and tags. + GitReference::BranchOrTagOrCommit(branch_or_tag_or_commit) => { + // The `oid_to_fetch` is the exact commit we want to fetch. But it could be the exact + // commit of a branch or tag. We should only fetch it directly if it's the exact commit + // of a short commit hash. + if let Some(oid_to_fetch) = + oid_to_fetch.filter(|oid| is_short_hash_of(branch_or_tag_or_commit, *oid)) + { + refspecs.push(format!("+{oid_to_fetch}:refs/commit/{oid_to_fetch}")); + } else { + // We don't know what the rev will point to. To handle this + // situation we fetch all branches and tags, and then we pray + // it's somewhere in there. + refspecs.push(String::from("+refs/heads/*:refs/remotes/origin/*")); + refspecs.push(String::from("+HEAD:refs/remotes/origin/HEAD")); + tags = true; + } + } + GitReference::DefaultBranch => { refspecs.push(String::from("+HEAD:refs/remotes/origin/HEAD")); } - GitReference::Ref(rev) => { + GitReference::NamedRef(rev) => { refspecs.push(format!("+{rev}:{rev}")); } @@ -997,19 +1000,6 @@ pub(crate) fn fetch( refspecs.push(format!("+{rev}:refs/remotes/origin/HEAD")); } } - - GitReference::ShortCommit(_) => { - if let Some(oid_to_fetch) = oid_to_fetch { - refspecs.push(format!("+{oid_to_fetch}:refs/commit/{oid_to_fetch}")); - } else { - // We don't know what the rev will point to. To handle this - // situation we fetch all branches and tags, and then we pray - // it's somewhere in there. - refspecs.push(String::from("+refs/heads/*:refs/remotes/origin/*")); - refspecs.push(String::from("+HEAD:refs/remotes/origin/HEAD")); - tags = true; - } - } } debug!("Performing a Git fetch for: {remote_url}"); @@ -1022,8 +1012,12 @@ pub(crate) fn fetch( let mut errors = refspecs .iter() .map_while(|refspec| { - let fetch_result = - fetch_with_cli(repo, remote_url, &[refspec.clone()], tags); + let fetch_result = fetch_with_cli( + repo, + remote_url, + std::slice::from_ref(refspec), + tags, + ); // Stop after the first success and log failures match fetch_result { @@ -1335,40 +1329,33 @@ fn github_fast_path( let local_object = reference.resolve(repo).ok(); let github_branch_name = match reference { - GitReference::Branch(branch) => branch, - GitReference::Tag(tag) => tag, GitReference::BranchOrTag(branch_or_tag) => branch_or_tag, GitReference::DefaultBranch => "HEAD", - GitReference::Ref(rev) => rev, - GitReference::FullCommit(rev) | GitReference::ShortCommit(rev) => { - if looks_like_commit_hash(rev) { - // `revparse_single` (used by `resolve`) is the only way to turn - // short hash -> long hash, but it also parses other things, - // like branch and tag names, which might coincidentally be - // valid hex. - // - // We only return early if `rev` is a prefix of the object found - // by `revparse_single`. Don't bother talking to GitHub in that - // case, since commit hashes are permanent. If a commit with the - // requested hash is already present in the local clone, its - // contents must be the same as what is on the server for that - // hash. - // - // If `rev` is not found locally by `revparse_single`, we'll - // need GitHub to resolve it and get a hash. If `rev` is found - // but is not a short hash of the found object, it's probably a - // branch and we also need to get a hash from GitHub, in case - // the branch has moved. - if let Some(local_object) = local_object { - if is_short_hash_of(rev, local_object) { - return Ok(FastPathRev::UpToDate); - } + GitReference::NamedRef(rev) => rev, + GitReference::FullCommit(rev) | GitReference::BranchOrTagOrCommit(rev) => { + // `revparse_single` (used by `resolve`) is the only way to turn + // short hash -> long hash, but it also parses other things, + // like branch and tag names, which might coincidentally be + // valid hex. + // + // We only return early if `rev` is a prefix of the object found + // by `revparse_single`. Don't bother talking to GitHub in that + // case, since commit hashes are permanent. If a commit with the + // requested hash is already present in the local clone, its + // contents must be the same as what is on the server for that + // hash. + // + // If `rev` is not found locally by `revparse_single`, we'll + // need GitHub to resolve it and get a hash. If `rev` is found + // but is not a short hash of the found object, it's probably a + // branch and we also need to get a hash from GitHub, in case + // the branch has moved. + if let Some(local_object) = local_object { + if is_short_hash_of(rev, local_object) { + return Ok(FastPathRev::UpToDate); } - rev - } else { - debug!("can't use github fast path with `rev = \"{}\"`", rev); - return Ok(FastPathRev::Indeterminate); } + rev } }; diff --git a/crates/uv-git/src/lib.rs b/crates/uv-git/src/lib.rs index 084571dd2..7198fd071 100644 --- a/crates/uv-git/src/lib.rs +++ b/crates/uv-git/src/lib.rs @@ -95,12 +95,10 @@ impl From for Url { } else { // Otherwise, add the branch or tag name. match git.reference { - GitReference::Branch(rev) - | GitReference::Tag(rev) - | GitReference::BranchOrTag(rev) - | GitReference::Ref(rev) + GitReference::BranchOrTag(rev) + | GitReference::NamedRef(rev) | GitReference::FullCommit(rev) - | GitReference::ShortCommit(rev) => { + | GitReference::BranchOrTagOrCommit(rev) => { url.set_path(&format!("{}@{}", url.path(), rev)); } GitReference::DefaultBranch => {} diff --git a/crates/uv-git/src/source.rs b/crates/uv-git/src/source.rs index f1061331a..64483d038 100644 --- a/crates/uv-git/src/source.rs +++ b/crates/uv-git/src/source.rs @@ -49,7 +49,7 @@ impl GitSource { } /// Fetch the underlying Git repository at the given revision. - #[instrument(skip(self))] + #[instrument(skip(self), fields(repository = %self.git.repository, rev = ?self.git.precise))] pub fn fetch(self) -> Result { // The path to the repo, within the Git database. let ident = digest(&RepositoryUrl::new(&self.git.repository)); diff --git a/crates/uv-installer/Cargo.toml b/crates/uv-installer/Cargo.toml index efb269cc2..789fba48d 100644 --- a/crates/uv-installer/Cargo.toml +++ b/crates/uv-installer/Cargo.toml @@ -22,6 +22,7 @@ pypi-types = { workspace = true } requirements-txt = { workspace = true } uv-cache = { workspace = true } uv-client = { workspace = true } +uv-configuration = { workspace = true } uv-distribution = { workspace = true } uv-extract = { workspace = true } uv-fs = { workspace = true } @@ -35,6 +36,7 @@ async-channel = { workspace = true } fs-err = { workspace = true } futures = { workspace = true } rayon = { workspace = true } +rmp-serde = { workspace = true } rustc-hash = { workspace = true } serde = { workspace = true } tempfile = { workspace = true } diff --git a/crates/uv-installer/src/downloader.rs b/crates/uv-installer/src/downloader.rs index 2f8f86091..e83d0bec8 100644 --- a/crates/uv-installer/src/downloader.rs +++ b/crates/uv-installer/src/downloader.rs @@ -3,19 +3,19 @@ use std::path::Path; use std::sync::Arc; use futures::{FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt}; -use tempfile::TempDir; use tokio::task::JoinError; use tracing::instrument; use url::Url; use distribution_types::{ - BuildableSource, CachedDist, Dist, Identifier, LocalEditable, RemoteSource, + BuildableSource, CachedDist, Dist, Hashed, Identifier, LocalEditable, LocalEditables, + RemoteSource, }; use platform_tags::Tags; use uv_cache::Cache; use uv_client::RegistryClient; -use uv_distribution::{DistributionDatabase, LocalWheel, Unzip}; -use uv_types::{BuildContext, InFlight}; +use uv_distribution::{DistributionDatabase, LocalWheel}; +use uv_types::{BuildContext, HashStrategy, InFlight}; use crate::editable::BuiltEditable; @@ -40,6 +40,7 @@ pub enum Error { pub struct Downloader<'a, Context: BuildContext + Send + Sync> { tags: &'a Tags, cache: &'a Cache, + hashes: &'a HashStrategy, database: DistributionDatabase<'a, Context>, reporter: Option>, } @@ -48,12 +49,14 @@ impl<'a, Context: BuildContext + Send + Sync> Downloader<'a, Context> { pub fn new( cache: &'a Cache, tags: &'a Tags, + hashes: &'a HashStrategy, client: &'a RegistryClient, build_context: &'a Context, ) -> Self { Self { tags, cache, + hashes, database: DistributionDatabase::new(client, build_context), reporter: None, } @@ -66,6 +69,7 @@ impl<'a, Context: BuildContext + Send + Sync> Downloader<'a, Context> { Self { tags: self.tags, cache: self.cache, + hashes: self.hashes, database: self.database.with_reporter(Facade::from(reporter.clone())), reporter: Some(reporter.clone()), } @@ -117,7 +121,7 @@ impl<'a, Context: BuildContext + Send + Sync> Downloader<'a, Context> { #[instrument(skip_all)] pub async fn build_editables( &self, - editables: Vec, + editables: LocalEditables, editable_wheel_dir: &Path, ) -> Result, Error> { // Build editables in parallel @@ -133,7 +137,7 @@ impl<'a, Context: BuildContext + Send + Sync> Downloader<'a, Context> { .build_wheel_editable(&editable, editable_wheel_dir) .await .map_err(Error::Editable)?; - let cached_dist = self.unzip_wheel(local_wheel).await?; + let cached_dist = CachedDist::from(local_wheel); if let Some(task_id) = task_id { if let Some(reporter) = &self.reporter { reporter.on_editable_build_complete(&editable, task_id); @@ -166,13 +170,28 @@ impl<'a, Context: BuildContext + Send + Sync> Downloader<'a, Context> { pub async fn get_wheel(&self, dist: Dist, in_flight: &InFlight) -> Result { let id = dist.distribution_id(); if in_flight.downloads.register(id.clone()) { - let download: LocalWheel = self + let policy = self.hashes.get(&dist); + let result = self .database - .get_or_build_wheel(&dist, self.tags) + .get_or_build_wheel(&dist, self.tags, policy) .boxed() .map_err(|err| Error::Fetch(dist.clone(), err)) - .await?; - let result = self.unzip_wheel(download).await; + .await + .and_then(|wheel: LocalWheel| { + if wheel.satisfies(policy) { + Ok(wheel) + } else { + Err(Error::Fetch( + dist.clone(), + uv_distribution::Error::hash_mismatch( + dist.to_string(), + policy.digests(), + wheel.hashes(), + ), + )) + } + }) + .map(CachedDist::from); match result { Ok(cached) => { in_flight.downloads.done(id, Ok(cached.clone())); @@ -196,37 +215,6 @@ impl<'a, Context: BuildContext + Send + Sync> Downloader<'a, Context> { } } } - - /// Unzip a locally-available wheel into the cache. - async fn unzip_wheel(&self, download: LocalWheel) -> Result { - // Just an optimization: Avoid spawning a blocking task if there is no work to be done. - if let LocalWheel::Unzipped(download) = download { - return Ok(download.into_cached_dist()); - } - - // Unzip the wheel. - let temp_dir = tokio::task::spawn_blocking({ - let download = download.clone(); - let cache = self.cache.clone(); - move || -> Result { - // Unzip the wheel into a temporary directory. - let temp_dir = tempfile::tempdir_in(cache.root())?; - download.unzip(temp_dir.path())?; - Ok(temp_dir) - } - }) - .await? - .map_err(|err| Error::Unzip(download.remote().clone(), err))?; - - // Persist the temporary directory to the directory store. - let archive = self - .cache - .persist(temp_dir.into_path(), download.target()) - .map_err(Error::CacheWrite) - .await?; - - Ok(download.into_cached_dist(archive)) - } } pub trait Reporter: Send + Sync { diff --git a/crates/uv-installer/src/plan.rs b/crates/uv-installer/src/plan.rs index d6ef6668c..21970eafb 100644 --- a/crates/uv-installer/src/plan.rs +++ b/crates/uv-installer/src/plan.rs @@ -1,11 +1,11 @@ use std::collections::hash_map::Entry; use std::hash::BuildHasherDefault; -use std::io; use anyhow::{bail, Result}; use rustc_hash::FxHashMap; use tracing::{debug, warn}; +use distribution_types::Hashed; use distribution_types::{ BuiltDist, CachedDirectUrlDist, CachedDist, Dist, IndexLocations, InstalledDist, InstalledMetadata, InstalledVersion, Name, SourceDist, @@ -13,10 +13,13 @@ use distribution_types::{ use pep508_rs::{Requirement, VersionOrUrl}; use platform_tags::Tags; use uv_cache::{ArchiveTarget, ArchiveTimestamp, Cache, CacheBucket, WheelCache}; -use uv_distribution::{BuiltWheelIndex, RegistryWheelIndex}; +use uv_configuration::{NoBinary, Reinstall}; +use uv_distribution::{ + BuiltWheelIndex, HttpArchivePointer, LocalArchivePointer, RegistryWheelIndex, +}; use uv_fs::Simplified; use uv_interpreter::PythonEnvironment; -use uv_types::{NoBinary, Reinstall}; +use uv_types::HashStrategy; use crate::{ResolvedEditable, SitePackages}; @@ -53,19 +56,25 @@ impl<'a> Planner<'a> { /// plan will respect cache entries created after the current time (as per the [`Refresh`] /// policy). Otherwise, entries will be ignored. The downstream distribution database may still /// read those entries from the cache after revalidating them. + /// + /// The install plan will also respect the required hashes, such that it will never return a + /// cached distribution that does not match the required hash. Like pip, though, it _will_ + /// return an _installed_ distribution that does not match the required hash. #[allow(clippy::too_many_arguments)] pub fn build( self, mut site_packages: SitePackages<'_>, reinstall: &Reinstall, no_binary: &NoBinary, + hasher: &HashStrategy, index_locations: &IndexLocations, cache: &Cache, venv: &PythonEnvironment, tags: &Tags, ) -> Result { // Index all the already-downloaded wheels in the cache. - let mut registry_index = RegistryWheelIndex::new(cache, tags, index_locations); + let mut registry_index = RegistryWheelIndex::new(cache, tags, index_locations, hasher); + let built_index = BuiltWheelIndex::new(cache, tags, hasher); let mut cached = vec![]; let mut remote = vec![]; @@ -205,16 +214,9 @@ impl<'a> Planner<'a> { } } Some(VersionOrUrl::VersionSpecifier(specifier)) => { - if let Some(distribution) = - registry_index - .get(&requirement.name) - .find_map(|(version, distribution)| { - if specifier.contains(version) { - Some(distribution) - } else { - None - } - }) + if let Some((_version, distribution)) = registry_index + .get(&requirement.name) + .find(|(version, _)| specifier.contains(version)) { debug!("Requirement already cached: {distribution}"); cached.push(CachedDist::Registry(distribution.clone())); @@ -251,24 +253,23 @@ impl<'a> Planner<'a> { CacheBucket::Wheels, WheelCache::Url(&wheel.url).wheel_dir(wheel.name().as_ref()), ) - .entry(wheel.filename.stem()); + .entry(format!("{}.http", wheel.filename.stem())); - match cache_entry.path().canonicalize() { - Ok(archive) => { + // Read the HTTP pointer. + if let Some(pointer) = HttpArchivePointer::read_from(&cache_entry)? { + let archive = pointer.into_archive(); + if archive.satisfies(hasher.get(&wheel)) { let cached_dist = CachedDirectUrlDist::from_url( wheel.filename, wheel.url, - archive, + archive.hashes, + cache.archive(&archive.id), ); debug!("URL wheel requirement already cached: {cached_dist}"); cached.push(CachedDist::Url(cached_dist)); continue; } - Err(err) if err.kind() == io::ErrorKind::NotFound => { - // The cache entry doesn't exist, so it's not fresh. - } - Err(err) => return Err(err.into()), } } Dist::Built(BuiltDist::Path(wheel)) => { @@ -293,37 +294,33 @@ impl<'a> Planner<'a> { CacheBucket::Wheels, WheelCache::Url(&wheel.url).wheel_dir(wheel.name().as_ref()), ) - .entry(wheel.filename.stem()); + .entry(format!("{}.rev", wheel.filename.stem())); - match cache_entry.path().canonicalize() { - Ok(archive) => { - if ArchiveTimestamp::up_to_date_with( - &wheel.path, - ArchiveTarget::Cache(&archive), - )? { + if let Some(pointer) = LocalArchivePointer::read_from(&cache_entry)? { + let timestamp = ArchiveTimestamp::from_file(&wheel.path)?; + if pointer.is_up_to_date(timestamp) { + let archive = pointer.into_archive(); + if archive.satisfies(hasher.get(&wheel)) { let cached_dist = CachedDirectUrlDist::from_url( wheel.filename, wheel.url, - archive, + archive.hashes, + cache.archive(&archive.id), ); debug!( - "URL wheel requirement already cached: {cached_dist}" + "Path wheel requirement already cached: {cached_dist}" ); cached.push(CachedDist::Url(cached_dist)); continue; } } - Err(err) if err.kind() == io::ErrorKind::NotFound => { - // The cache entry doesn't exist, so it's not fresh. - } - Err(err) => return Err(err.into()), } } Dist::Source(SourceDist::DirectUrl(sdist)) => { // Find the most-compatible wheel from the cache, since we don't know // the filename in advance. - if let Some(wheel) = BuiltWheelIndex::url(&sdist, cache, tags)? { + if let Some(wheel) = built_index.url(&sdist)? { let cached_dist = wheel.into_url_dist(url.clone()); debug!("URL source requirement already cached: {cached_dist}"); cached.push(CachedDist::Url(cached_dist)); @@ -333,7 +330,7 @@ impl<'a> Planner<'a> { Dist::Source(SourceDist::Path(sdist)) => { // Find the most-compatible wheel from the cache, since we don't know // the filename in advance. - if let Some(wheel) = BuiltWheelIndex::path(&sdist, cache, tags)? { + if let Some(wheel) = built_index.path(&sdist)? { let cached_dist = wheel.into_url_dist(url.clone()); debug!("Path source requirement already cached: {cached_dist}"); cached.push(CachedDist::Url(cached_dist)); @@ -343,7 +340,7 @@ impl<'a> Planner<'a> { Dist::Source(SourceDist::Git(sdist)) => { // Find the most-compatible wheel from the cache, since we don't know // the filename in advance. - if let Some(wheel) = BuiltWheelIndex::git(&sdist, cache, tags) { + if let Some(wheel) = built_index.git(&sdist) { let cached_dist = wheel.into_url_dist(url.clone()); debug!("Git source requirement already cached: {cached_dist}"); cached.push(CachedDist::Url(cached_dist)); diff --git a/crates/uv-installer/src/site_packages.rs b/crates/uv-installer/src/site_packages.rs index 26514e5d3..3db4424ee 100644 --- a/crates/uv-installer/src/site_packages.rs +++ b/crates/uv-installer/src/site_packages.rs @@ -10,7 +10,7 @@ use url::Url; use distribution_types::{InstalledDist, InstalledMetadata, InstalledVersion, Name}; use pep440_rs::{Version, VersionSpecifiers}; use pep508_rs::{Requirement, RequirementsTxtRequirement, VerbatimUrl}; -use requirements_txt::EditableRequirement; +use requirements_txt::{EditableRequirement, RequirementEntry}; use uv_cache::{ArchiveTarget, ArchiveTimestamp}; use uv_interpreter::PythonEnvironment; use uv_normalize::PackageName; @@ -299,19 +299,22 @@ impl<'a> SitePackages<'a> { /// Returns `true` if the installed packages satisfy the given requirements. pub fn satisfies( &self, - requirements: &[RequirementsTxtRequirement], + requirements: &[RequirementEntry], editables: &[EditableRequirement], constraints: &[Requirement], ) -> Result { - let mut stack = Vec::::with_capacity(requirements.len()); + let mut stack = Vec::::with_capacity(requirements.len()); let mut seen = FxHashSet::with_capacity_and_hasher(requirements.len(), BuildHasherDefault::default()); // Add the direct requirements to the queue. - for dependency in requirements { - if dependency.evaluate_markers(self.venv.interpreter().markers(), &[]) { - if seen.insert(dependency.clone()) { - stack.push(dependency.clone()); + for entry in requirements { + if entry + .requirement + .evaluate_markers(self.venv.interpreter().markers(), &[]) + { + if seen.insert(entry.clone()) { + stack.push(entry.clone()); } } } @@ -349,7 +352,10 @@ impl<'a> SitePackages<'a> { self.venv.interpreter().markers(), &requirement.extras, ) { - let dependency = RequirementsTxtRequirement::from(dependency); + let dependency = RequirementEntry { + requirement: RequirementsTxtRequirement::Pep508(dependency), + hashes: vec![], + }; if seen.insert(dependency.clone()) { stack.push(dependency); } @@ -364,8 +370,8 @@ impl<'a> SitePackages<'a> { } // Verify that all non-editable requirements are met. - while let Some(requirement) = stack.pop() { - let installed = match &requirement { + while let Some(entry) = stack.pop() { + let installed = match &entry.requirement { RequirementsTxtRequirement::Pep508(requirement) => { self.get_packages(&requirement.name) } @@ -380,7 +386,7 @@ impl<'a> SitePackages<'a> { } [distribution] => { // Validate that the installed version matches the requirement. - match requirement.version_or_url() { + match entry.requirement.version_or_url() { // Accept any installed version. None => {} @@ -466,9 +472,12 @@ impl<'a> SitePackages<'a> { for dependency in metadata.requires_dist { if dependency.evaluate_markers( self.venv.interpreter().markers(), - requirement.extras(), + entry.requirement.extras(), ) { - let dependency = RequirementsTxtRequirement::from(dependency); + let dependency = RequirementEntry { + requirement: RequirementsTxtRequirement::Pep508(dependency), + hashes: vec![], + }; if seen.insert(dependency.clone()) { stack.push(dependency); } diff --git a/crates/uv-interpreter/Cargo.toml b/crates/uv-interpreter/Cargo.toml index d8ae8fb75..eef17ce3b 100644 --- a/crates/uv-interpreter/Cargo.toml +++ b/crates/uv-interpreter/Cargo.toml @@ -21,6 +21,7 @@ platform-tags = { workspace = true } pypi-types = { workspace = true } uv-cache = { workspace = true } uv-fs = { workspace = true } +uv-toolchain = { workspace = true } configparser = { workspace = true } fs-err = { workspace = true, features = ["tokio"] } diff --git a/crates/uv-interpreter/src/find_python.rs b/crates/uv-interpreter/src/find_python.rs index b06472d21..2fc45bdec 100644 --- a/crates/uv-interpreter/src/find_python.rs +++ b/crates/uv-interpreter/src/find_python.rs @@ -7,10 +7,11 @@ use tracing::{debug, instrument}; use uv_cache::Cache; use uv_fs::normalize_path; +use uv_toolchain::PythonVersion; use crate::interpreter::InterpreterInfoError; use crate::python_environment::{detect_python_executable, detect_virtual_env}; -use crate::{Error, Interpreter, PythonVersion}; +use crate::{Error, Interpreter}; /// Find a Python of a specific version, a binary with a name or a path to a binary. /// @@ -464,7 +465,7 @@ fn find_version( let version_matches = |interpreter: &Interpreter| -> bool { if let Some(python_version) = python_version { // If a patch version was provided, check for an exact match - python_version.is_satisfied_by(interpreter) + interpreter.satisfies(python_version) } else { // The version always matches if one was not provided true diff --git a/crates/uv-interpreter/src/interpreter.rs b/crates/uv-interpreter/src/interpreter.rs index a5ff8e01a..ce14845f9 100644 --- a/crates/uv-interpreter/src/interpreter.rs +++ b/crates/uv-interpreter/src/interpreter.rs @@ -16,6 +16,7 @@ use platform_tags::{Tags, TagsError}; use pypi_types::Scheme; use uv_cache::{Cache, CacheBucket, CachedByTimestamp, Freshness, Timestamp}; use uv_fs::{write_atomic_sync, PythonExt, Simplified}; +use uv_toolchain::PythonVersion; use crate::Error; use crate::Virtualenv; @@ -314,6 +315,18 @@ impl Interpreter { }, } } + + /// Check if the interpreter matches the given Python version. + /// + /// If a patch version is present, we will require an exact match. + /// Otherwise, just the major and minor version numbers need to match. + pub fn satisfies(&self, version: &PythonVersion) -> bool { + if version.patch().is_some() { + version.version() == self.python_version() + } else { + (version.major(), version.minor()) == self.python_tuple() + } + } } /// The `EXTERNALLY-MANAGED` file in a Python installation. diff --git a/crates/uv-interpreter/src/lib.rs b/crates/uv-interpreter/src/lib.rs index 535888a78..1286ff436 100644 --- a/crates/uv-interpreter/src/lib.rs +++ b/crates/uv-interpreter/src/lib.rs @@ -19,14 +19,12 @@ pub use crate::find_python::{find_best_python, find_default_python, find_request pub use crate::interpreter::Interpreter; use crate::interpreter::InterpreterInfoError; pub use crate::python_environment::PythonEnvironment; -pub use crate::python_version::PythonVersion; pub use crate::virtualenv::Virtualenv; mod cfg; mod find_python; mod interpreter; mod python_environment; -mod python_version; mod virtualenv; #[derive(Debug, Error)] diff --git a/crates/uv-requirements/Cargo.toml b/crates/uv-requirements/Cargo.toml index c2befe770..658a89479 100644 --- a/crates/uv-requirements/Cargo.toml +++ b/crates/uv-requirements/Cargo.toml @@ -23,6 +23,7 @@ uv-normalize = { workspace = true } uv-resolver = { workspace = true, features = ["clap"] } uv-types = { workspace = true } uv-warnings = { workspace = true } +uv-configuration = { workspace = true } anyhow = { workspace = true } configparser = { workspace = true } diff --git a/crates/uv-requirements/src/confirm.rs b/crates/uv-requirements/src/confirm.rs index 3b111d331..7eb7d8b59 100644 --- a/crates/uv-requirements/src/confirm.rs +++ b/crates/uv-requirements/src/confirm.rs @@ -6,7 +6,8 @@ use console::{style, Key, Term}; /// This is a slimmed-down version of `dialoguer::Confirm`, with the post-confirmation report /// enabled. pub(crate) fn confirm(message: &str, term: &Term, default: bool) -> Result { - ctrlc::set_handler(move || { + // Set the Ctrl-C handler to exit the process. + let result = ctrlc::set_handler(move || { let term = Term::stderr(); term.show_cursor().ok(); term.flush().ok(); @@ -17,7 +18,16 @@ pub(crate) fn confirm(message: &str, term: &Term, default: bool) -> Result } else { 130 }); - })?; + }); + + match result { + Ok(()) => {} + Err(ctrlc::Error::MultipleHandlers) => { + // If multiple handlers were set, we assume that the existing handler is our + // confirmation handler, and continue. + } + Err(e) => return Err(e.into()), + } let prompt = format!( "{} {} {} {} {}", diff --git a/crates/uv-requirements/src/lookahead.rs b/crates/uv-requirements/src/lookahead.rs index 775603968..496b6506a 100644 --- a/crates/uv-requirements/src/lookahead.rs +++ b/crates/uv-requirements/src/lookahead.rs @@ -1,7 +1,6 @@ use std::collections::VecDeque; use anyhow::{Context, Result}; - use futures::stream::FuturesUnordered; use futures::StreamExt; use rustc_hash::FxHashSet; @@ -10,9 +9,10 @@ use distribution_types::{Dist, DistributionMetadata, LocalEditable}; use pep508_rs::{MarkerEnvironment, Requirement, VersionOrUrl}; use pypi_types::Metadata23; use uv_client::RegistryClient; +use uv_configuration::{Constraints, Overrides}; use uv_distribution::{DistributionDatabase, Reporter}; -use uv_resolver::InMemoryIndex; -use uv_types::{BuildContext, Constraints, Overrides, RequestedRequirements}; +use uv_resolver::{InMemoryIndex, MetadataResponse}; +use uv_types::{BuildContext, HashStrategy, RequestedRequirements}; /// A resolver for resolving lookahead requirements from direct URLs. /// @@ -39,6 +39,8 @@ pub struct LookaheadResolver<'a, Context: BuildContext + Send + Sync> { overrides: &'a Overrides, /// The editable requirements for the project. editables: &'a [(LocalEditable, Metadata23)], + /// The required hashes for the project. + hasher: &'a HashStrategy, /// The in-memory index for resolving dependencies. index: &'a InMemoryIndex, /// The database for fetching and building distributions. @@ -47,11 +49,13 @@ pub struct LookaheadResolver<'a, Context: BuildContext + Send + Sync> { impl<'a, Context: BuildContext + Send + Sync> LookaheadResolver<'a, Context> { /// Instantiate a new [`LookaheadResolver`] for a given set of requirements. + #[allow(clippy::too_many_arguments)] pub fn new( requirements: &'a [Requirement], constraints: &'a Constraints, overrides: &'a Overrides, editables: &'a [(LocalEditable, Metadata23)], + hasher: &'a HashStrategy, context: &'a Context, client: &'a RegistryClient, index: &'a InMemoryIndex, @@ -61,6 +65,7 @@ impl<'a, Context: BuildContext + Send + Sync> LookaheadResolver<'a, Context> { constraints, overrides, editables, + hasher, index, database: DistributionDatabase::new(client, context), } @@ -133,25 +138,37 @@ impl<'a, Context: BuildContext + Send + Sync> LookaheadResolver<'a, Context> { // Fetch the metadata for the distribution. let requires_dist = { - let id = dist.package_id(); - if let Some(metadata) = self.index.get_metadata(&id) { + let id = dist.version_id(); + if let Some(archive) = self + .index + .get_metadata(&id) + .as_deref() + .and_then(|response| { + if let MetadataResponse::Found(archive, ..) = response { + Some(archive) + } else { + None + } + }) + { // If the metadata is already in the index, return it. - metadata.requires_dist.clone() + archive.metadata.requires_dist.clone() } else { // Run the PEP 517 build process to extract metadata from the source distribution. - let metadata = self + let archive = self .database - .get_or_build_wheel_metadata(&dist) + .get_or_build_wheel_metadata(&dist, self.hasher.get(&dist)) .await .with_context(|| match &dist { Dist::Built(built) => format!("Failed to download: {built}"), Dist::Source(source) => format!("Failed to download and build: {source}"), })?; - let requires_dist = metadata.requires_dist.clone(); + let requires_dist = archive.metadata.requires_dist.clone(); // Insert the metadata into the index. - self.index.insert_metadata(id, metadata); + self.index + .insert_metadata(id, MetadataResponse::Found(archive)); requires_dist } diff --git a/crates/uv-requirements/src/source_tree.rs b/crates/uv-requirements/src/source_tree.rs index 580b44979..920c42993 100644 --- a/crates/uv-requirements/src/source_tree.rs +++ b/crates/uv-requirements/src/source_tree.rs @@ -1,30 +1,31 @@ use std::borrow::Cow; -use std::ops::Deref; use std::path::{Path, PathBuf}; use anyhow::{Context, Result}; use futures::{StreamExt, TryStreamExt}; use url::Url; -use distribution_types::{BuildableSource, PackageId, PathSourceUrl, SourceUrl}; +use distribution_types::{BuildableSource, HashPolicy, PathSourceUrl, SourceUrl, VersionId}; use pep508_rs::Requirement; use uv_client::RegistryClient; use uv_distribution::{DistributionDatabase, Reporter}; use uv_fs::Simplified; -use uv_resolver::InMemoryIndex; -use uv_types::BuildContext; +use uv_resolver::{InMemoryIndex, MetadataResponse}; +use uv_types::{BuildContext, HashStrategy}; use crate::ExtrasSpecification; /// A resolver for requirements specified via source trees. /// -/// Used, e.g., to determine the the input requirements when a user specifies a `pyproject.toml` +/// Used, e.g., to determine the input requirements when a user specifies a `pyproject.toml` /// file, which may require running PEP 517 build hooks to extract metadata. pub struct SourceTreeResolver<'a, Context: BuildContext + Send + Sync> { /// The requirements for the project. source_trees: Vec, /// The extras to include when resolving requirements. extras: &'a ExtrasSpecification<'a>, + /// The hash policy to enforce. + hasher: &'a HashStrategy, /// The in-memory index for resolving dependencies. index: &'a InMemoryIndex, /// The database for fetching and building distributions. @@ -36,6 +37,7 @@ impl<'a, Context: BuildContext + Send + Sync> SourceTreeResolver<'a, Context> { pub fn new( source_trees: Vec, extras: &'a ExtrasSpecification<'a>, + hasher: &'a HashStrategy, context: &'a Context, client: &'a RegistryClient, index: &'a InMemoryIndex, @@ -43,6 +45,7 @@ impl<'a, Context: BuildContext + Send + Sync> SourceTreeResolver<'a, Context> { Self { source_trees, extras, + hasher, index, database: DistributionDatabase::new(client, context), } @@ -84,21 +87,46 @@ impl<'a, Context: BuildContext + Send + Sync> SourceTreeResolver<'a, Context> { path: Cow::Owned(path), }); + // Determine the hash policy. Since we don't have a package name, we perform a + // manual match. + let hashes = match self.hasher { + HashStrategy::None => HashPolicy::None, + HashStrategy::Generate => HashPolicy::Generate, + HashStrategy::Validate { .. } => { + return Err(anyhow::anyhow!( + "Hash-checking is not supported for local directories: {}", + source_tree.user_display() + )); + } + }; + // Fetch the metadata for the distribution. let metadata = { - let id = PackageId::from_url(source.url()); - if let Some(metadata) = self.index.get_metadata(&id) { + let id = VersionId::from_url(source.url()); + if let Some(archive) = self + .index + .get_metadata(&id) + .as_deref() + .and_then(|response| { + if let MetadataResponse::Found(archive) = response { + Some(archive) + } else { + None + } + }) + { // If the metadata is already in the index, return it. - metadata.deref().clone() + archive.metadata.clone() } else { // Run the PEP 517 build process to extract metadata from the source distribution. let source = BuildableSource::Url(source); - let metadata = self.database.build_wheel_metadata(&source).await?; + let archive = self.database.build_wheel_metadata(&source, hashes).await?; // Insert the metadata into the index. - self.index.insert_metadata(id, metadata.clone()); + self.index + .insert_metadata(id, MetadataResponse::Found(archive.clone())); - metadata + archive.metadata } }; diff --git a/crates/uv-requirements/src/specification.rs b/crates/uv-requirements/src/specification.rs index eb73de073..63e1f64ed 100644 --- a/crates/uv-requirements/src/specification.rs +++ b/crates/uv-requirements/src/specification.rs @@ -7,11 +7,11 @@ use tracing::{instrument, Level}; use cache_key::CanonicalUrl; use distribution_types::{FlatIndexLocation, IndexUrl}; use pep508_rs::{Requirement, RequirementsTxtRequirement}; -use requirements_txt::{EditableRequirement, FindLink, RequirementsTxt}; +use requirements_txt::{EditableRequirement, FindLink, RequirementEntry, RequirementsTxt}; use uv_client::BaseClientBuilder; +use uv_configuration::{NoBinary, NoBuild}; use uv_fs::Simplified; use uv_normalize::{ExtraName, PackageName}; -use uv_types::{NoBinary, NoBuild}; use crate::pyproject::{Pep621Metadata, PyProjectToml}; use crate::{ExtrasSpecification, RequirementsSource}; @@ -21,11 +21,11 @@ pub struct RequirementsSpecification { /// The name of the project specifying requirements. pub project: Option, /// The requirements for the project. - pub requirements: Vec, + pub requirements: Vec, /// The constraints for the project. pub constraints: Vec, /// The overrides for the project. - pub overrides: Vec, + pub overrides: Vec, /// Package to install as editable installs pub editables: Vec, /// The source trees from which to extract requirements. @@ -60,7 +60,10 @@ impl RequirementsSpecification { .with_context(|| format!("Failed to parse `{name}`"))?; Self { project: None, - requirements: vec![requirement], + requirements: vec![RequirementEntry { + requirement, + hashes: vec![], + }], constraints: vec![], overrides: vec![], editables: vec![], @@ -98,11 +101,7 @@ impl RequirementsSpecification { RequirementsTxt::parse(path, std::env::current_dir()?, client_builder).await?; Self { project: None, - requirements: requirements_txt - .requirements - .into_iter() - .map(|entry| entry.requirement) - .collect(), + requirements: requirements_txt.requirements, constraints: requirements_txt.constraints, overrides: vec![], editables: requirements_txt.editables, @@ -151,7 +150,10 @@ impl RequirementsSpecification { requirements: project .requirements .into_iter() - .map(RequirementsTxtRequirement::Pep508) + .map(|requirement| RequirementEntry { + requirement: RequirementsTxtRequirement::Pep508(requirement), + hashes: vec![], + }) .collect(), constraints: vec![], overrides: vec![], @@ -261,11 +263,12 @@ impl RequirementsSpecification { spec.no_build.extend(source.no_build); } - // Read all constraints, treating _everything_ as a constraint. + // Read all constraints, treating both requirements _and_ constraints as constraints. + // Overrides are ignored, as are the hashes, as they are not relevant for constraints. for source in constraints { let source = Self::from_source(source, extras, client_builder).await?; - for requirement in source.requirements { - match requirement { + for entry in source.requirements { + match entry.requirement { RequirementsTxtRequirement::Pep508(requirement) => { spec.constraints.push(requirement); } @@ -277,7 +280,6 @@ impl RequirementsSpecification { } } spec.constraints.extend(source.constraints); - spec.constraints.extend(source.overrides); if let Some(index_url) = source.index_url { if let Some(existing) = spec.index_url { @@ -296,22 +298,11 @@ impl RequirementsSpecification { spec.no_build.extend(source.no_build); } - // Read all overrides, treating both requirements _and_ constraints as overrides. + // Read all overrides, treating both requirements _and_ overrides as overrides. + // Constraints are ignored. for source in overrides { let source = Self::from_source(source, extras, client_builder).await?; - for requirement in source.requirements { - match requirement { - RequirementsTxtRequirement::Pep508(requirement) => { - spec.overrides.push(requirement); - } - RequirementsTxtRequirement::Unnamed(requirement) => { - return Err(anyhow::anyhow!( - "Unnamed requirements are not allowed as overrides (found: `{requirement}`)" - )); - } - } - } - spec.overrides.extend(source.constraints); + spec.overrides.extend(source.requirements); spec.overrides.extend(source.overrides); if let Some(index_url) = source.index_url { diff --git a/crates/uv-requirements/src/unnamed.rs b/crates/uv-requirements/src/unnamed.rs index 905594686..d245b8166 100644 --- a/crates/uv-requirements/src/unnamed.rs +++ b/crates/uv-requirements/src/unnamed.rs @@ -10,23 +10,26 @@ use tracing::debug; use distribution_filename::{SourceDistFilename, WheelFilename}; use distribution_types::{ - BuildableSource, DirectSourceUrl, GitSourceUrl, PackageId, PathSourceUrl, RemoteSource, - SourceUrl, + BuildableSource, DirectSourceUrl, GitSourceUrl, PathSourceUrl, RemoteSource, SourceUrl, + VersionId, }; use pep508_rs::{ Requirement, RequirementsTxtRequirement, Scheme, UnnamedRequirement, VersionOrUrl, }; use pypi_types::Metadata10; +use requirements_txt::RequirementEntry; use uv_client::RegistryClient; use uv_distribution::{DistributionDatabase, Reporter}; use uv_normalize::PackageName; -use uv_resolver::InMemoryIndex; -use uv_types::BuildContext; +use uv_resolver::{InMemoryIndex, MetadataResponse}; +use uv_types::{BuildContext, HashStrategy}; /// Like [`RequirementsSpecification`], but with concrete names for all requirements. pub struct NamedRequirementsResolver<'a, Context: BuildContext + Send + Sync> { /// The requirements for the project. - requirements: Vec, + requirements: Vec, + /// Whether to check hashes for distributions. + hasher: &'a HashStrategy, /// The in-memory index for resolving dependencies. index: &'a InMemoryIndex, /// The database for fetching and building distributions. @@ -36,13 +39,15 @@ pub struct NamedRequirementsResolver<'a, Context: BuildContext + Send + Sync> { impl<'a, Context: BuildContext + Send + Sync> NamedRequirementsResolver<'a, Context> { /// Instantiate a new [`NamedRequirementsResolver`] for a given set of requirements. pub fn new( - requirements: Vec, + requirements: Vec, + hasher: &'a HashStrategy, context: &'a Context, client: &'a RegistryClient, index: &'a InMemoryIndex, ) -> Self { Self { requirements, + hasher, index, database: DistributionDatabase::new(client, context), } @@ -61,15 +66,16 @@ impl<'a, Context: BuildContext + Send + Sync> NamedRequirementsResolver<'a, Cont pub async fn resolve(self) -> Result> { let Self { requirements, + hasher, index, database, } = self; futures::stream::iter(requirements) - .map(|requirement| async { - match requirement { + .map(|entry| async { + match entry.requirement { RequirementsTxtRequirement::Pep508(requirement) => Ok(requirement), RequirementsTxtRequirement::Unnamed(requirement) => { - Self::resolve_requirement(requirement, index, &database).await + Self::resolve_requirement(requirement, hasher, index, &database).await } } }) @@ -81,6 +87,7 @@ impl<'a, Context: BuildContext + Send + Sync> NamedRequirementsResolver<'a, Cont /// Infer the package name for a given "unnamed" requirement. async fn resolve_requirement( requirement: UnnamedRequirement, + hasher: &HashStrategy, index: &InMemoryIndex, database: &DistributionDatabase<'a, Context>, ) -> Result { @@ -235,19 +242,26 @@ impl<'a, Context: BuildContext + Send + Sync> NamedRequirementsResolver<'a, Cont // Fetch the metadata for the distribution. let name = { - let id = PackageId::from_url(source.url()); - if let Some(metadata) = index.get_metadata(&id) { + let id = VersionId::from_url(source.url()); + if let Some(archive) = index.get_metadata(&id).as_deref().and_then(|response| { + if let MetadataResponse::Found(archive) = response { + Some(archive) + } else { + None + } + }) { // If the metadata is already in the index, return it. - metadata.name.clone() + archive.metadata.name.clone() } else { // Run the PEP 517 build process to extract metadata from the source distribution. + let hashes = hasher.get_url(source.url()); let source = BuildableSource::Url(source); - let metadata = database.build_wheel_metadata(&source).await?; + let archive = database.build_wheel_metadata(&source, hashes).await?; - let name = metadata.name.clone(); + let name = archive.metadata.name.clone(); // Insert the metadata into the index. - index.insert_metadata(id, metadata); + index.insert_metadata(id, MetadataResponse::Found(archive)); name } diff --git a/crates/uv-requirements/src/upgrade.rs b/crates/uv-requirements/src/upgrade.rs index 49a1cc1e3..8dc92c5c2 100644 --- a/crates/uv-requirements/src/upgrade.rs +++ b/crates/uv-requirements/src/upgrade.rs @@ -4,8 +4,8 @@ use anyhow::Result; use requirements_txt::RequirementsTxt; use uv_client::{BaseClientBuilder, Connectivity}; +use uv_configuration::Upgrade; use uv_resolver::{Preference, PreferenceError}; -use uv_types::Upgrade; /// Load the preferred requirements from an existing lockfile, applying the upgrade strategy. pub async fn read_lockfile( @@ -30,7 +30,6 @@ pub async fn read_lockfile( let preferences = requirements_txt .requirements .into_iter() - .filter(|entry| !entry.editable) .map(Preference::from_entry) .collect::, PreferenceError>>()?; diff --git a/crates/uv-resolver/Cargo.toml b/crates/uv-resolver/Cargo.toml index de2d97827..55269d8c6 100644 --- a/crates/uv-resolver/Cargo.toml +++ b/crates/uv-resolver/Cargo.toml @@ -16,6 +16,7 @@ workspace = true cache-key = { workspace = true } distribution-filename = { workspace = true, features = ["serde"] } distribution-types = { workspace = true } +install-wheel-rs = { workspace = true } once-map = { workspace = true } pep440_rs = { workspace = true } pep508_rs = { workspace = true } @@ -29,6 +30,7 @@ uv-interpreter = { workspace = true } uv-normalize = { workspace = true } uv-types = { workspace = true } uv-warnings = { workspace = true } +uv-configuration = { workspace = true } anstream = { workspace = true } anyhow = { workspace = true } @@ -46,6 +48,7 @@ petgraph = { workspace = true } pubgrub = { workspace = true } rkyv = { workspace = true } rustc-hash = { workspace = true } +textwrap = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, features = ["macros"] } tokio-stream = { workspace = true } diff --git a/crates/uv-resolver/src/candidate_selector.rs b/crates/uv-resolver/src/candidate_selector.rs index eff292bd9..31a6783e9 100644 --- a/crates/uv-resolver/src/candidate_selector.rs +++ b/crates/uv-resolver/src/candidate_selector.rs @@ -70,8 +70,32 @@ impl CandidateSelector { pub(crate) fn select<'a, InstalledPackages: InstalledPackagesProvider>( &'a self, package_name: &'a PackageName, - range: &'a Range, - version_map: &'a VersionMap, + range: &Range, + version_maps: &'a [VersionMap], + preferences: &'a Preferences, + installed_packages: &'a InstalledPackages, + exclusions: &'a Exclusions, + ) -> Option> { + if let Some(preferred) = Self::get_preferred( + package_name, + range, + version_maps, + preferences, + installed_packages, + exclusions, + ) { + return Some(preferred); + } + + self.select_no_preference(package_name, range, version_maps) + } + + /// Get a preferred version if one exists. This is the preference from a lockfile or a locally + /// installed version. + fn get_preferred<'a, InstalledPackages: InstalledPackagesProvider>( + package_name: &'a PackageName, + range: &Range, + version_maps: &'a [VersionMap], preferences: &'a Preferences, installed_packages: &'a InstalledPackages, exclusions: &'a Exclusions, @@ -107,7 +131,10 @@ impl CandidateSelector { } // Check for a remote distribution that matches the preferred version - if let Some(file) = version_map.get(version) { + if let Some(file) = version_maps + .iter() + .find_map(|version_map| version_map.get(version)) + { return Some(Candidate::new(package_name, version, file)); } } @@ -138,8 +165,12 @@ impl CandidateSelector { } } - // Determine the appropriate prerelease strategy for the current package. - let allow_prerelease = match &self.prerelease_strategy { + None + } + + /// Determine the appropriate prerelease strategy for the current package. + fn allow_prereleases(&self, package_name: &PackageName) -> AllowPreRelease { + match &self.prerelease_strategy { PreReleaseStrategy::Disallow => AllowPreRelease::No, PreReleaseStrategy::Allow => AllowPreRelease::Yes, PreReleaseStrategy::IfNecessary => AllowPreRelease::IfNecessary, @@ -157,40 +188,50 @@ impl CandidateSelector { AllowPreRelease::IfNecessary } } - }; + } + } + /// Select a [`Candidate`] without checking for version preference such as an existing + /// lockfile. + pub(crate) fn select_no_preference<'a>( + &'a self, + package_name: &'a PackageName, + range: &Range, + version_maps: &'a [VersionMap], + ) -> Option { tracing::trace!( - "selecting candidate for package {:?} with range {:?} with {} remote versions", + "selecting candidate for package {} with range {:?} with {} remote versions", package_name, range, - version_map.len() + version_maps.iter().map(VersionMap::len).sum::(), ); - match &self.resolution_strategy { - ResolutionStrategy::Highest => Self::select_candidate( - version_map.iter().rev(), - package_name, - range, - allow_prerelease, - ), - ResolutionStrategy::Lowest => { + let highest = self.use_highest_version(package_name); + let allow_prerelease = self.allow_prereleases(package_name); + + if highest { + version_maps.iter().find_map(|version_map| { + Self::select_candidate( + version_map.iter().rev(), + package_name, + range, + allow_prerelease, + ) + }) + } else { + version_maps.iter().find_map(|version_map| { Self::select_candidate(version_map.iter(), package_name, range, allow_prerelease) - } + }) + } + } + + /// By default, we select the latest version, but we also allow using the lowest version instead + /// to check the lower bounds. + pub(crate) fn use_highest_version(&self, package_name: &PackageName) -> bool { + match &self.resolution_strategy { + ResolutionStrategy::Highest => true, + ResolutionStrategy::Lowest => false, ResolutionStrategy::LowestDirect(direct_dependencies) => { - if direct_dependencies.contains(package_name) { - Self::select_candidate( - version_map.iter(), - package_name, - range, - allow_prerelease, - ) - } else { - Self::select_candidate( - version_map.iter().rev(), - package_name, - range, - allow_prerelease, - ) - } + !direct_dependencies.contains(package_name) } } } @@ -198,7 +239,7 @@ impl CandidateSelector { /// Select the first-matching [`Candidate`] from a set of candidate versions and files, /// preferring wheels over source distributions. fn select_candidate<'a>( - versions: impl Iterator)>, + versions: impl Iterator)> + ExactSizeIterator, package_name: &'a PackageName, range: &Range, allow_prerelease: AllowPreRelease, @@ -210,10 +251,8 @@ impl CandidateSelector { } let mut prerelease = None; - let mut steps = 0; - for (version, maybe_dist) in versions { - steps += 1; - + let versions_len = versions.len(); + for (step, (version, maybe_dist)) in versions.enumerate() { let candidate = if version.any_prerelease() { if range.contains(version) { match allow_prerelease { @@ -226,7 +265,7 @@ impl CandidateSelector { after {} steps: {:?} version", package_name, range, - steps, + step, version, ); // If pre-releases are allowed, treat them equivalently @@ -267,7 +306,7 @@ impl CandidateSelector { after {} steps: {:?} version", package_name, range, - steps, + step, version, ); Candidate::new(package_name, version, dist) @@ -299,7 +338,7 @@ impl CandidateSelector { after {} steps", package_name, range, - steps, + versions_len, ); match prerelease { None => None, diff --git a/crates/uv-resolver/src/error.rs b/crates/uv-resolver/src/error.rs index 6cf92bf4c..8391ad6aa 100644 --- a/crates/uv-resolver/src/error.rs +++ b/crates/uv-resolver/src/error.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; use std::fmt::Formatter; use std::ops::Deref; @@ -20,7 +20,7 @@ use crate::candidate_selector::CandidateSelector; use crate::dependency_provider::UvDependencyProvider; use crate::pubgrub::{PubGrubPackage, PubGrubPython, PubGrubReportFormatter}; use crate::python_requirement::PythonRequirement; -use crate::resolver::{UnavailablePackage, VersionsResponse}; +use crate::resolver::{IncompletePackage, UnavailablePackage, VersionsResponse}; #[derive(Debug, thiserror::Error)] pub enum ResolveError { @@ -93,6 +93,9 @@ pub enum ResolveError { #[error("Attempted to construct an invalid version specifier")] InvalidVersion(#[from] pep440_rs::VersionSpecifierBuildError), + #[error("In `--require-hashes` mode, all requirements must be pinned upfront with `==`, but found: {0}")] + UnhashedPackage(PackageName), + /// Something unexpected happened. #[error("{0}")] Failure(String), @@ -125,6 +128,7 @@ impl From> for ResolveError { python_requirement: None, index_locations: None, unavailable_packages: FxHashMap::default(), + incomplete_packages: FxHashMap::default(), }) } pubgrub::error::PubGrubError::SelfDependency { package, version } => { @@ -146,6 +150,7 @@ pub struct NoSolutionError { python_requirement: Option, index_locations: Option, unavailable_packages: FxHashMap, + incomplete_packages: FxHashMap>, } impl std::error::Error for NoSolutionError {} @@ -167,6 +172,7 @@ impl std::fmt::Display for NoSolutionError { &self.selector, &self.index_locations, &self.unavailable_packages, + &self.incomplete_packages, ) { write!(f, "\n\n{hint}")?; } @@ -209,14 +215,15 @@ impl NoSolutionError { // we represent the state of the resolver at the time of failure. if visited.contains(name) { if let Some(response) = package_versions.get(name) { - if let VersionsResponse::Found(ref version_map) = *response { - available_versions.insert( - package.clone(), - version_map - .iter() - .map(|(version, _)| version.clone()) - .collect(), - ); + if let VersionsResponse::Found(ref version_maps) = *response { + for version_map in version_maps { + available_versions + .entry(package.clone()) + .or_insert_with(BTreeSet::new) + .extend( + version_map.iter().map(|(version, _)| version.clone()), + ); + } } } } @@ -260,6 +267,30 @@ impl NoSolutionError { self } + /// Update the incomplete packages attached to the error. + #[must_use] + pub(crate) fn with_incomplete_packages( + mut self, + incomplete_packages: &DashMap>, + ) -> Self { + let mut new = FxHashMap::default(); + for package in self.derivation_tree.packages() { + if let PubGrubPackage::Package(name, ..) = package { + if let Some(entry) = incomplete_packages.get(name) { + let versions = entry.value(); + for entry in versions { + let (version, reason) = entry.pair(); + new.entry(name.clone()) + .or_insert_with(BTreeMap::default) + .insert(version.clone(), reason.clone()); + } + } + } + } + self.incomplete_packages = new; + self + } + /// Update the Python requirements attached to the error. #[must_use] pub(crate) fn with_python_requirement( diff --git a/crates/uv-resolver/src/exclusions.rs b/crates/uv-resolver/src/exclusions.rs index e7c6ade4d..34593af9d 100644 --- a/crates/uv-resolver/src/exclusions.rs +++ b/crates/uv-resolver/src/exclusions.rs @@ -1,6 +1,6 @@ use pep508_rs::PackageName; use rustc_hash::FxHashSet; -use uv_types::{Reinstall, Upgrade}; +use uv_configuration::{Reinstall, Upgrade}; /// Tracks locally installed packages that should not be selected during resolution. #[derive(Debug, Default, Clone)] diff --git a/crates/uv-resolver/src/flat_index.rs b/crates/uv-resolver/src/flat_index.rs new file mode 100644 index 000000000..f37577c85 --- /dev/null +++ b/crates/uv-resolver/src/flat_index.rs @@ -0,0 +1,231 @@ +use std::collections::btree_map::Entry; +use std::collections::BTreeMap; + +use rustc_hash::FxHashMap; +use tracing::instrument; + +use distribution_filename::{DistFilename, SourceDistFilename, WheelFilename}; +use distribution_types::{ + BuiltDist, Dist, File, Hash, HashPolicy, IncompatibleSource, IncompatibleWheel, IndexUrl, + PrioritizedDist, RegistryBuiltDist, RegistrySourceDist, SourceDist, SourceDistCompatibility, + WheelCompatibility, +}; +use pep440_rs::Version; +use platform_tags::{TagCompatibility, Tags}; +use pypi_types::HashDigest; +use uv_client::FlatIndexEntries; +use uv_configuration::{NoBinary, NoBuild}; +use uv_normalize::PackageName; +use uv_types::HashStrategy; + +/// A set of [`PrioritizedDist`] from a `--find-links` entry, indexed by [`PackageName`] +/// and [`Version`]. +#[derive(Debug, Clone, Default)] +pub struct FlatIndex { + /// The list of [`FlatDistributions`] from the `--find-links` entries, indexed by package name. + index: FxHashMap, + /// Whether any `--find-links` entries could not be resolved due to a lack of network + /// connectivity. + offline: bool, +} + +impl FlatIndex { + /// Collect all files from a `--find-links` target into a [`FlatIndex`]. + #[instrument(skip_all)] + pub fn from_entries( + entries: FlatIndexEntries, + tags: &Tags, + hasher: &HashStrategy, + no_build: &NoBuild, + no_binary: &NoBinary, + ) -> Self { + // Collect compatible distributions. + let mut index = FxHashMap::default(); + for (filename, file, url) in entries.entries { + let distributions = index.entry(filename.name().clone()).or_default(); + Self::add_file( + distributions, + file, + filename, + tags, + hasher, + no_build, + no_binary, + url, + ); + } + + // Collect offline entries. + let offline = entries.offline; + + Self { index, offline } + } + + #[allow(clippy::too_many_arguments)] + fn add_file( + distributions: &mut FlatDistributions, + file: File, + filename: DistFilename, + tags: &Tags, + hasher: &HashStrategy, + no_build: &NoBuild, + no_binary: &NoBinary, + index: IndexUrl, + ) { + // No `requires-python` here: for source distributions, we don't have that information; + // for wheels, we read it lazily only when selected. + match filename { + DistFilename::WheelFilename(filename) => { + let version = filename.version.clone(); + + let compatibility = + Self::wheel_compatibility(&filename, &file.hashes, tags, hasher, no_binary); + let dist = Dist::Built(BuiltDist::Registry(RegistryBuiltDist { + filename, + file: Box::new(file), + index, + })); + match distributions.0.entry(version) { + Entry::Occupied(mut entry) => { + entry.get_mut().insert_built(dist, vec![], compatibility); + } + Entry::Vacant(entry) => { + entry.insert(PrioritizedDist::from_built(dist, vec![], compatibility)); + } + } + } + DistFilename::SourceDistFilename(filename) => { + let compatibility = + Self::source_dist_compatibility(&filename, &file.hashes, hasher, no_build); + let dist = Dist::Source(SourceDist::Registry(RegistrySourceDist { + filename: filename.clone(), + file: Box::new(file), + index, + })); + match distributions.0.entry(filename.version) { + Entry::Occupied(mut entry) => { + entry.get_mut().insert_source(dist, vec![], compatibility); + } + Entry::Vacant(entry) => { + entry.insert(PrioritizedDist::from_source(dist, vec![], compatibility)); + } + } + } + } + } + + fn source_dist_compatibility( + filename: &SourceDistFilename, + hashes: &[HashDigest], + hasher: &HashStrategy, + no_build: &NoBuild, + ) -> SourceDistCompatibility { + // Check if source distributions are allowed for this package. + let no_build = match no_build { + NoBuild::None => false, + NoBuild::All => true, + NoBuild::Packages(packages) => packages.contains(&filename.name), + }; + + if no_build { + return SourceDistCompatibility::Incompatible(IncompatibleSource::NoBuild); + } + + // Check if hashes line up + let hash = if let HashPolicy::Validate(required) = hasher.get_package(&filename.name) { + if hashes.is_empty() { + Hash::Missing + } else if required.iter().any(|hash| hashes.contains(hash)) { + Hash::Matched + } else { + Hash::Mismatched + } + } else { + Hash::Matched + }; + + SourceDistCompatibility::Compatible(hash) + } + + fn wheel_compatibility( + filename: &WheelFilename, + hashes: &[HashDigest], + tags: &Tags, + hasher: &HashStrategy, + no_binary: &NoBinary, + ) -> WheelCompatibility { + // Check if binaries are allowed for this package. + let no_binary = match no_binary { + NoBinary::None => false, + NoBinary::All => true, + NoBinary::Packages(packages) => packages.contains(&filename.name), + }; + + if no_binary { + return WheelCompatibility::Incompatible(IncompatibleWheel::NoBinary); + } + + // Determine a compatibility for the wheel based on tags. + let priority = match filename.compatibility(tags) { + TagCompatibility::Incompatible(tag) => { + return WheelCompatibility::Incompatible(IncompatibleWheel::Tag(tag)) + } + TagCompatibility::Compatible(priority) => priority, + }; + + // Check if hashes line up + let hash = if let HashPolicy::Validate(required) = hasher.get_package(&filename.name) { + if hashes.is_empty() { + Hash::Missing + } else if required.iter().any(|hash| hashes.contains(hash)) { + Hash::Matched + } else { + Hash::Mismatched + } + } else { + Hash::Matched + }; + + WheelCompatibility::Compatible(hash, priority) + } + + /// Get the [`FlatDistributions`] for the given package name. + pub fn get(&self, package_name: &PackageName) -> Option<&FlatDistributions> { + self.index.get(package_name) + } + + /// Returns `true` if there are any offline `--find-links` entries. + pub fn offline(&self) -> bool { + self.offline + } +} + +/// A set of [`PrioritizedDist`] from a `--find-links` entry for a single package, indexed +/// by [`Version`]. +#[derive(Debug, Clone, Default)] +pub struct FlatDistributions(BTreeMap); + +impl FlatDistributions { + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } + + pub fn remove(&mut self, version: &Version) -> Option { + self.0.remove(version) + } +} + +impl IntoIterator for FlatDistributions { + type Item = (Version, PrioritizedDist); + type IntoIter = std::collections::btree_map::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl From for BTreeMap { + fn from(distributions: FlatDistributions) -> Self { + distributions.0 + } +} diff --git a/crates/uv-resolver/src/lib.rs b/crates/uv-resolver/src/lib.rs index 129bbac3e..c7fd1aea1 100644 --- a/crates/uv-resolver/src/lib.rs +++ b/crates/uv-resolver/src/lib.rs @@ -1,6 +1,7 @@ pub use dependency_mode::DependencyMode; pub use error::ResolveError; pub use exclusions::Exclusions; +pub use flat_index::FlatIndex; pub use manifest::Manifest; pub use options::{Options, OptionsBuilder}; pub use preferences::{Preference, PreferenceError}; @@ -9,7 +10,7 @@ pub use python_requirement::PythonRequirement; pub use resolution::{AnnotationStyle, Diagnostic, DisplayResolutionGraph, ResolutionGraph}; pub use resolution_mode::ResolutionMode; pub use resolver::{ - BuildId, DefaultResolverProvider, InMemoryIndex, PackageVersionsResult, + BuildId, DefaultResolverProvider, InMemoryIndex, MetadataResponse, PackageVersionsResult, Reporter as ResolverReporter, Resolver, ResolverProvider, VersionsResponse, WheelMetadataResult, }; @@ -24,6 +25,7 @@ mod dependency_provider; mod editables; mod error; mod exclusions; +mod flat_index; mod manifest; mod options; mod pins; diff --git a/crates/uv-resolver/src/manifest.rs b/crates/uv-resolver/src/manifest.rs index e6b6607f6..2801aef1b 100644 --- a/crates/uv-resolver/src/manifest.rs +++ b/crates/uv-resolver/src/manifest.rs @@ -1,8 +1,9 @@ use distribution_types::LocalEditable; use pep508_rs::{MarkerEnvironment, Requirement}; use pypi_types::Metadata23; +use uv_configuration::{Constraints, Overrides}; use uv_normalize::PackageName; -use uv_types::{Constraints, Overrides, RequestedRequirements}; +use uv_types::RequestedRequirements; use crate::{preferences::Preference, Exclusions}; diff --git a/crates/uv-resolver/src/preferences.rs b/crates/uv-resolver/src/preferences.rs index 95bccb464..376ceaf67 100644 --- a/crates/uv-resolver/src/preferences.rs +++ b/crates/uv-resolver/src/preferences.rs @@ -6,7 +6,7 @@ use pep440_rs::{Operator, Version}; use pep508_rs::{ MarkerEnvironment, Requirement, RequirementsTxtRequirement, UnnamedRequirement, VersionOrUrl, }; -use pypi_types::{HashError, Hashes}; +use pypi_types::{HashDigest, HashError}; use requirements_txt::RequirementEntry; use tracing::trace; use uv_normalize::PackageName; @@ -23,7 +23,7 @@ pub enum PreferenceError { #[derive(Clone, Debug)] pub struct Preference { requirement: Requirement, - hashes: Vec, + hashes: Vec, } impl Preference { @@ -40,7 +40,7 @@ impl Preference { .hashes .iter() .map(String::as_str) - .map(Hashes::from_str) + .map(HashDigest::from_str) .collect::>()?, }) } @@ -146,7 +146,7 @@ impl Preferences { &self, package_name: &PackageName, version: &Version, - ) -> Option<&[Hashes]> { + ) -> Option<&[HashDigest]> { self.0 .get(package_name) .filter(|pin| pin.version() == version) @@ -158,7 +158,7 @@ impl Preferences { #[derive(Debug, Clone)] struct Pin { version: Version, - hashes: Vec, + hashes: Vec, } impl Pin { @@ -168,7 +168,7 @@ impl Pin { } /// Return the hashes of the pinned package. - fn hashes(&self) -> &[Hashes] { + fn hashes(&self) -> &[HashDigest] { &self.hashes } } diff --git a/crates/uv-resolver/src/pubgrub/dependencies.rs b/crates/uv-resolver/src/pubgrub/dependencies.rs index 28760cee5..6920fb0c1 100644 --- a/crates/uv-resolver/src/pubgrub/dependencies.rs +++ b/crates/uv-resolver/src/pubgrub/dependencies.rs @@ -5,8 +5,8 @@ use tracing::warn; use distribution_types::Verbatim; use pep440_rs::Version; use pep508_rs::{MarkerEnvironment, Requirement, VersionOrUrl}; +use uv_configuration::{Constraints, Overrides}; use uv_normalize::{ExtraName, PackageName}; -use uv_types::{Constraints, Overrides}; use crate::pubgrub::specifier::PubGrubSpecifier; use crate::pubgrub::PubGrubPackage; diff --git a/crates/uv-resolver/src/pubgrub/report.rs b/crates/uv-resolver/src/pubgrub/report.rs index dfb93997a..f4c7aea9e 100644 --- a/crates/uv-resolver/src/pubgrub/report.rs +++ b/crates/uv-resolver/src/pubgrub/report.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; use std::cmp::Ordering; -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; use std::ops::Bound; use derivative::Derivative; @@ -17,7 +17,7 @@ use uv_normalize::PackageName; use crate::candidate_selector::CandidateSelector; use crate::python_requirement::PythonRequirement; -use crate::resolver::UnavailablePackage; +use crate::resolver::{IncompletePackage, UnavailablePackage}; use super::PubGrubPackage; @@ -342,6 +342,7 @@ impl PubGrubReportFormatter<'_> { selector: &Option, index_locations: &Option, unavailable_packages: &FxHashMap, + incomplete_packages: &FxHashMap>, ) -> IndexSet { /// Returns `true` if pre-releases were allowed for a package. fn allowed_prerelease(package: &PubGrubPackage, selector: &CandidateSelector) -> bool { @@ -354,7 +355,7 @@ impl PubGrubReportFormatter<'_> { let mut hints = IndexSet::default(); match derivation_tree { DerivationTree::External(external) => match external { - External::NoVersions(package, set, _) => { + External::Unavailable(package, set, _) | External::NoVersions(package, set, _) => { // Check for no versions due to pre-release options if let Some(selector) = selector { let any_prerelease = set.iter().any(|(start, end)| { @@ -404,6 +405,7 @@ impl PubGrubReportFormatter<'_> { index_locations.flat_index().peekable().peek().is_none(); if let PubGrubPackage::Package(name, ..) = package { + // Add hints due to the package being entirely unavailable. match unavailable_packages.get(name) { Some(UnavailablePackage::NoIndex) => { if no_find_links { @@ -413,13 +415,64 @@ impl PubGrubReportFormatter<'_> { Some(UnavailablePackage::Offline) => { hints.insert(PubGrubHint::Offline); } - _ => {} + Some(UnavailablePackage::InvalidMetadata(reason)) => { + hints.insert(PubGrubHint::InvalidPackageMetadata { + package: package.clone(), + reason: reason.clone(), + }); + } + Some(UnavailablePackage::InvalidStructure(reason)) => { + hints.insert(PubGrubHint::InvalidPackageStructure { + package: package.clone(), + reason: reason.clone(), + }); + } + Some(UnavailablePackage::NotFound) => {} + None => {} + } + + // Add hints due to the package being unavailable at specific versions. + if let Some(versions) = incomplete_packages.get(name) { + for (version, incomplete) in versions.iter().rev() { + if set.contains(version) { + match incomplete { + IncompletePackage::Offline => { + hints.insert(PubGrubHint::Offline); + } + IncompletePackage::InvalidMetadata(reason) => { + hints.insert(PubGrubHint::InvalidVersionMetadata { + package: package.clone(), + version: version.clone(), + reason: reason.clone(), + }); + } + IncompletePackage::InconsistentMetadata(reason) => { + hints.insert( + PubGrubHint::InconsistentVersionMetadata { + package: package.clone(), + version: version.clone(), + reason: reason.clone(), + }, + ); + } + IncompletePackage::InvalidStructure(reason) => { + hints.insert( + PubGrubHint::InvalidVersionStructure { + package: package.clone(), + version: version.clone(), + reason: reason.clone(), + }, + ); + } + } + break; + } + } } } } } External::NotRoot(..) => {} - External::Unavailable(..) => {} External::FromDependencyOf(..) => {} }, DerivationTree::Derived(derived) => { @@ -428,12 +481,14 @@ impl PubGrubReportFormatter<'_> { selector, index_locations, unavailable_packages, + incomplete_packages, )); hints.extend(self.hints( &derived.cause2, selector, index_locations, unavailable_packages, + incomplete_packages, )); } } @@ -462,8 +517,45 @@ pub(crate) enum PubGrubHint { /// Requirements were unavailable due to lookups in the index being disabled and no extra /// index was provided via `--find-links` NoIndex, - /// A package was not found in the registry, but + /// A package was not found in the registry, but network access was disabled. Offline, + /// Metadata for a package could not be parsed. + InvalidPackageMetadata { + package: PubGrubPackage, + #[derivative(PartialEq = "ignore", Hash = "ignore")] + reason: String, + }, + /// The structure of a package was invalid (e.g., multiple `.dist-info` directories). + InvalidPackageStructure { + package: PubGrubPackage, + #[derivative(PartialEq = "ignore", Hash = "ignore")] + reason: String, + }, + /// Metadata for a package version could not be parsed. + InvalidVersionMetadata { + package: PubGrubPackage, + #[derivative(PartialEq = "ignore", Hash = "ignore")] + version: Version, + #[derivative(PartialEq = "ignore", Hash = "ignore")] + reason: String, + }, + /// Metadata for a package version was inconsistent (e.g., the package name did not match that + /// of the file). + InconsistentVersionMetadata { + package: PubGrubPackage, + #[derivative(PartialEq = "ignore", Hash = "ignore")] + version: Version, + #[derivative(PartialEq = "ignore", Hash = "ignore")] + reason: String, + }, + /// The structure of a package version was invalid (e.g., multiple `.dist-info` directories). + InvalidVersionStructure { + package: PubGrubPackage, + #[derivative(PartialEq = "ignore", Hash = "ignore")] + version: Version, + #[derivative(PartialEq = "ignore", Hash = "ignore")] + reason: String, + }, } impl std::fmt::Display for PubGrubHint { @@ -505,6 +597,71 @@ impl std::fmt::Display for PubGrubHint { ":".bold(), ) } + Self::InvalidPackageMetadata { package, reason } => { + write!( + f, + "{}{} Metadata for {} could not be parsed:\n{}", + "hint".bold().cyan(), + ":".bold(), + package.bold(), + textwrap::indent(reason, " ") + ) + } + Self::InvalidPackageStructure { package, reason } => { + write!( + f, + "{}{} The structure of {} was invalid:\n{}", + "hint".bold().cyan(), + ":".bold(), + package.bold(), + textwrap::indent(reason, " ") + ) + } + Self::InvalidVersionMetadata { + package, + version, + reason, + } => { + write!( + f, + "{}{} Metadata for {}=={} could not be parsed:\n{}", + "hint".bold().cyan(), + ":".bold(), + package.bold(), + version.bold(), + textwrap::indent(reason, " ") + ) + } + Self::InvalidVersionStructure { + package, + version, + reason, + } => { + write!( + f, + "{}{} The structure of {}=={} was invalid:\n{}", + "hint".bold().cyan(), + ":".bold(), + package.bold(), + version.bold(), + textwrap::indent(reason, " ") + ) + } + PubGrubHint::InconsistentVersionMetadata { + package, + version, + reason, + } => { + write!( + f, + "{}{} Metadata for {}=={} was inconsistent:\n{}", + "hint".bold().cyan(), + ":".bold(), + package.bold(), + version.bold(), + textwrap::indent(reason, " ") + ) + } } } } diff --git a/crates/uv-resolver/src/resolution.rs b/crates/uv-resolver/src/resolution.rs index 667f79154..533a979b0 100644 --- a/crates/uv-resolver/src/resolution.rs +++ b/crates/uv-resolver/src/resolution.rs @@ -12,13 +12,13 @@ use pubgrub::type_aliases::SelectedDependencies; use rustc_hash::{FxHashMap, FxHashSet}; use distribution_types::{ - Dist, DistributionMetadata, LocalEditable, Name, PackageId, ResolvedDist, Verbatim, + Dist, DistributionMetadata, IndexUrl, LocalEditable, Name, ResolvedDist, Verbatim, VersionId, VersionOrUrl, }; use once_map::OnceMap; use pep440_rs::Version; use pep508_rs::MarkerEnvironment; -use pypi_types::{Hashes, Metadata23}; +use pypi_types::HashDigest; use uv_distribution::to_precise; use uv_normalize::{ExtraName, PackageName}; @@ -28,7 +28,7 @@ use crate::pins::FilePins; use crate::preferences::Preferences; use crate::pubgrub::{PubGrubDistribution, PubGrubPackage}; use crate::redirect::apply_redirect; -use crate::resolver::{InMemoryIndex, VersionsResponse}; +use crate::resolver::{InMemoryIndex, MetadataResponse, VersionsResponse}; use crate::{Manifest, ResolveError}; /// Indicate the style of annotation comments, used to indicate the dependencies that requested each @@ -50,7 +50,7 @@ pub struct ResolutionGraph { /// The underlying graph. petgraph: petgraph::graph::Graph, petgraph::Directed>, /// The metadata for every distribution in this resolution. - hashes: FxHashMap>, + hashes: FxHashMap>, /// The enabled extras for every distribution in this resolution. extras: FxHashMap>, /// The set of editable requirements in this resolution. @@ -66,7 +66,7 @@ impl ResolutionGraph { selection: &SelectedDependencies, pins: &FilePins, packages: &OnceMap, - distributions: &OnceMap, + distributions: &OnceMap, state: &State, preferences: &Preferences, editables: Editables, @@ -96,15 +96,20 @@ impl ResolutionGraph { // Add its hashes to the index, preserving those that were already present in // the lockfile if necessary. - if let Some(hash) = preferences.match_hashes(package_name, version) { - hashes.insert(package_name.clone(), hash.to_vec()); + if let Some(digests) = preferences + .match_hashes(package_name, version) + .filter(|digests| !digests.is_empty()) + { + hashes.insert(package_name.clone(), digests.to_vec()); } else if let Some(versions_response) = packages.get(package_name) { - if let VersionsResponse::Found(ref version_map) = *versions_response { - hashes.insert(package_name.clone(), { - let mut hash = version_map.hashes(version); - hash.sort_unstable(); - hash - }); + if let VersionsResponse::Found(ref version_maps) = *versions_response { + for version_map in version_maps { + if let Some(mut digests) = version_map.hashes(version) { + digests.sort_unstable(); + hashes.insert(package_name.clone(), digests); + break; + } + } } } @@ -124,15 +129,18 @@ impl ResolutionGraph { // Add its hashes to the index, preserving those that were already present in // the lockfile if necessary. - if let Some(hash) = preferences.match_hashes(package_name, version) { - hashes.insert(package_name.clone(), hash.to_vec()); - } else if let Some(versions_response) = packages.get(package_name) { - if let VersionsResponse::Found(ref version_map) = *versions_response { - hashes.insert(package_name.clone(), { - let mut hash = version_map.hashes(version); - hash.sort_unstable(); - hash - }); + if let Some(digests) = preferences + .match_hashes(package_name, version) + .filter(|digests| !digests.is_empty()) + { + hashes.insert(package_name.clone(), digests.to_vec()); + } else if let Some(metadata_response) = + distributions.get(&pinned_package.version_id()) + { + if let MetadataResponse::Found(ref archive) = *metadata_response { + let mut digests = archive.hashes.clone(); + digests.sort_unstable(); + hashes.insert(package_name.clone(), digests); } } @@ -160,14 +168,21 @@ impl ResolutionGraph { }); } } else { - let metadata = distributions.get(&dist.package_id()).unwrap_or_else(|| { + let response = distributions.get(&dist.version_id()).unwrap_or_else(|| { panic!( "Every package should have metadata: {:?}", - dist.package_id() + dist.version_id() ) }); - if metadata.provides_extras.contains(extra) { + let MetadataResponse::Found(archive) = &*response else { + panic!( + "Every package should have metadata: {:?}", + dist.version_id() + ) + }; + + if archive.metadata.provides_extras.contains(extra) { extras .entry(package_name.clone()) .or_insert_with(Vec::new) @@ -207,14 +222,21 @@ impl ResolutionGraph { }); } } else { - let metadata = distributions.get(&dist.package_id()).unwrap_or_else(|| { + let response = distributions.get(&dist.version_id()).unwrap_or_else(|| { panic!( "Every package should have metadata: {:?}", - dist.package_id() + dist.version_id() ) }); - if metadata.provides_extras.contains(extra) { + let MetadataResponse::Found(archive) = &*response else { + panic!( + "Every package should have metadata: {:?}", + dist.version_id() + ) + }; + + if archive.metadata.provides_extras.contains(extra) { extras .entry(package_name.clone()) .or_insert_with(Vec::new) @@ -407,17 +429,23 @@ impl ResolutionGraph { let mut seen_marker_values = FxHashSet::default(); for i in self.petgraph.node_indices() { let dist = &self.petgraph[i]; - let package_id = match dist.version_or_url() { + let version_id = match dist.version_or_url() { VersionOrUrl::Version(version) => { - PackageId::from_registry(dist.name().clone(), version.clone()) + VersionId::from_registry(dist.name().clone(), version.clone()) } - VersionOrUrl::Url(verbatim_url) => PackageId::from_url(verbatim_url.raw()), + VersionOrUrl::Url(verbatim_url) => VersionId::from_url(verbatim_url.raw()), }; - let md = index + let res = index .distributions - .get(&package_id) + .get(&version_id) .expect("every package in resolution graph has metadata"); - for req in manifest.apply(&md.requires_dist) { + let MetadataResponse::Found(archive, ..) = &*res else { + panic!( + "Every package should have metadata: {:?}", + dist.version_id() + ) + }; + for req in manifest.apply(&archive.metadata.requires_dist) { let Some(ref marker_tree) = req.marker else { continue; }; @@ -469,6 +497,7 @@ impl ResolutionGraph { /// A [`std::fmt::Display`] implementation for the resolution graph. #[derive(Debug)] +#[allow(clippy::struct_excessive_bools)] pub struct DisplayResolutionGraph<'a> { /// The underlying graph. resolution: &'a ResolutionGraph, @@ -481,6 +510,8 @@ pub struct DisplayResolutionGraph<'a> { /// Whether to include annotations in the output, to indicate which dependency or dependencies /// requested each package. include_annotations: bool, + /// Whether to include indexes in the output, to indicate which index was used for each package. + include_index_annotation: bool, /// The style of annotation comments, used to indicate the dependencies that requested each /// package. annotation_style: AnnotationStyle, @@ -494,6 +525,7 @@ impl<'a> From<&'a ResolutionGraph> for DisplayResolutionGraph<'a> { false, false, true, + false, AnnotationStyle::default(), ) } @@ -501,12 +533,14 @@ impl<'a> From<&'a ResolutionGraph> for DisplayResolutionGraph<'a> { impl<'a> DisplayResolutionGraph<'a> { /// Create a new [`DisplayResolutionGraph`] for the given graph. + #[allow(clippy::fn_params_excessive_bools)] pub fn new( underlying: &'a ResolutionGraph, no_emit_packages: &'a [PackageName], show_hashes: bool, include_extras: bool, include_annotations: bool, + include_index_annotation: bool, annotation_style: AnnotationStyle, ) -> DisplayResolutionGraph<'a> { Self { @@ -515,6 +549,7 @@ impl<'a> DisplayResolutionGraph<'a> { show_hashes, include_extras, include_annotations, + include_index_annotation, annotation_style, } } @@ -552,6 +587,14 @@ impl<'a> Node<'a> { Node::Distribution(name, _, _) => NodeKey::Distribution(name), } } + + /// Return the [`IndexUrl`] of the distribution, if any. + fn index(&self) -> Option<&IndexUrl> { + match self { + Node::Editable(_, _) => None, + Node::Distribution(_, dist, _) => dist.index(), + } + } } impl Verbatim for Node<'_> { @@ -625,12 +668,10 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> { .filter(|hashes| !hashes.is_empty()) { for hash in hashes { - if let Some(hash) = hash.to_string() { - has_hashes = true; - line.push_str(" \\\n"); - line.push_str(" --hash="); - line.push_str(&hash); - } + has_hashes = true; + line.push_str(" \\\n"); + line.push_str(" --hash="); + line.push_str(&hash.to_string()); } } } @@ -638,6 +679,8 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> { // Determine the annotation comment and separator (between comment and requirement). let mut annotation = None; + // If enabled, include annotations to indicate the dependencies that requested each + // package (e.g., `# via mypy`). if self.include_annotations { // Display all dependencies. let mut edges = self @@ -692,6 +735,14 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> { // Write the line as is. writeln!(f, "{line}")?; } + + // If enabled, include indexes to indicate which index was used for each package (e.g., + // `# from https://pypi.org/simple`). + if self.include_index_annotation { + if let Some(index) = node.index() { + writeln!(f, "{}", format!(" # from {index}").green())?; + } + } } Ok(()) diff --git a/crates/uv-resolver/src/resolver/batch_prefetch.rs b/crates/uv-resolver/src/resolver/batch_prefetch.rs new file mode 100644 index 000000000..558cdc2b7 --- /dev/null +++ b/crates/uv-resolver/src/resolver/batch_prefetch.rs @@ -0,0 +1,176 @@ +use std::cmp::min; + +use pubgrub::range::Range; +use rustc_hash::FxHashMap; +use tokio::sync::mpsc::Sender; +use tracing::{debug, trace}; + +use distribution_types::{DistributionMetadata, ResolvedDistRef}; +use pep440_rs::Version; + +use crate::candidate_selector::{CandidateDist, CandidateSelector}; +use crate::pubgrub::PubGrubPackage; +use crate::resolver::Request; +use crate::{InMemoryIndex, ResolveError, VersionsResponse}; + +enum BatchPrefetchStrategy { + /// Go through the next versions assuming the existing selection and its constraints + /// remain. + Compatible { + compatible: Range, + previous: Version, + }, + /// We encounter cases (botocore) where the above doesn't work: Say we previously selected + /// a==x.y.z, which depends on b==x.y.z. a==x.y.z is incompatible, but we don't know that + /// yet. We just selected b==x.y.z and want to prefetch, since for all versions of a we try, + /// we have to wait for the matching version of b. The exiting range gives us only one version + /// of b, so the compatible strategy doesn't prefetch any version. Instead, we try the next + /// heuristic where the next version of b will be x.y.(z-1) and so forth. + InOrder { previous: Version }, +} + +/// Prefetch a large number of versions if we already unsuccessfully tried many versions. +/// +/// This is an optimization specifically targeted at cold cache urllib3/boto3/botocore, where we +/// have to fetch the metadata for a lot of versions. +/// +/// Note that these all heuristics that could totally prefetch lots of irrelevant versions. +#[derive(Default)] +pub(crate) struct BatchPrefetcher { + tried_versions: FxHashMap, + last_prefetch: FxHashMap, +} + +impl BatchPrefetcher { + /// Prefetch a large number of versions if we already unsuccessfully tried many versions. + pub(crate) async fn prefetch_batches( + &mut self, + next: &PubGrubPackage, + version: &Version, + current_range: &Range, + request_sink: &Sender, + index: &InMemoryIndex, + selector: &CandidateSelector, + ) -> anyhow::Result<(), ResolveError> { + let PubGrubPackage::Package(package_name, None, None) = &next else { + return Ok(()); + }; + + let (num_tried, do_prefetch) = self.should_prefetch(next); + if !do_prefetch { + return Ok(()); + } + let total_prefetch = min(num_tried, 50); + + // This is immediate, we already fetched the version map. + let versions_response = index + .packages + .wait(package_name) + .await + .ok_or(ResolveError::Unregistered)?; + + let VersionsResponse::Found(ref version_map) = *versions_response else { + return Ok(()); + }; + + let mut phase = BatchPrefetchStrategy::Compatible { + compatible: current_range.clone(), + previous: version.clone(), + }; + let mut prefetch_count = 0; + for _ in 0..total_prefetch { + let candidate = match phase { + BatchPrefetchStrategy::Compatible { + compatible, + previous, + } => { + if let Some(candidate) = + selector.select_no_preference(package_name, &compatible, version_map) + { + let compatible = compatible.intersection( + &Range::singleton(candidate.version().clone()).complement(), + ); + phase = BatchPrefetchStrategy::Compatible { + compatible, + previous: candidate.version().clone(), + }; + candidate + } else { + // We exhausted the compatible version, switch to ignoring the existing + // constraints on the package and instead going through versions in order. + phase = BatchPrefetchStrategy::InOrder { previous }; + continue; + } + } + BatchPrefetchStrategy::InOrder { previous } => { + let range = if selector.use_highest_version(package_name) { + Range::strictly_lower_than(previous) + } else { + Range::strictly_higher_than(previous) + }; + if let Some(candidate) = + selector.select_no_preference(package_name, &range, version_map) + { + phase = BatchPrefetchStrategy::InOrder { + previous: candidate.version().clone(), + }; + candidate + } else { + // Both strategies exhausted their candidates. + break; + } + } + }; + + let CandidateDist::Compatible(dist) = candidate.dist() else { + continue; + }; + // Avoid building a lot of source distributions. + if !dist.prefetchable() { + continue; + } + let dist = dist.for_resolution(); + + // Emit a request to fetch the metadata for this version. + trace!( + "Prefetching {prefetch_count} ({}) {}", + match phase { + BatchPrefetchStrategy::Compatible { .. } => "compatible", + BatchPrefetchStrategy::InOrder { .. } => "in order", + }, + dist + ); + prefetch_count += 1; + if index.distributions.register(candidate.version_id()) { + let request = match dist { + ResolvedDistRef::Installable(dist) => Request::Dist(dist.clone()), + ResolvedDistRef::Installed(dist) => Request::Installed(dist.clone()), + }; + request_sink.send(request).await?; + } + } + + debug!("Prefetching {prefetch_count} {package_name} versions"); + + self.last_prefetch.insert(next.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) { + *self.tried_versions.entry(package).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 num_tried = self.tried_versions.get(next).copied().unwrap_or_default(); + let previous_prefetch = self.last_prefetch.get(next).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) + } +} diff --git a/crates/uv-resolver/src/resolver/index.rs b/crates/uv-resolver/src/resolver/index.rs index 7b4e1e336..614baf206 100644 --- a/crates/uv-resolver/src/resolver/index.rs +++ b/crates/uv-resolver/src/resolver/index.rs @@ -1,11 +1,10 @@ use std::sync::Arc; -use distribution_types::PackageId; +use distribution_types::VersionId; use once_map::OnceMap; -use pypi_types::Metadata23; use uv_normalize::PackageName; -use crate::resolver::provider::VersionsResponse; +use crate::resolver::provider::{MetadataResponse, VersionsResponse}; /// In-memory index of package metadata. #[derive(Default)] @@ -15,18 +14,18 @@ pub struct InMemoryIndex { pub(crate) packages: OnceMap, /// A map from package ID to metadata for that distribution. - pub(crate) distributions: OnceMap, + pub(crate) distributions: OnceMap, } impl InMemoryIndex { /// Insert a [`VersionsResponse`] into the index. - pub fn insert_package(&self, package_name: PackageName, metadata: VersionsResponse) { - self.packages.done(package_name, metadata); + pub fn insert_package(&self, package_name: PackageName, response: VersionsResponse) { + self.packages.done(package_name, response); } /// Insert a [`Metadata23`] into the index. - pub fn insert_metadata(&self, package_id: PackageId, metadata: Metadata23) { - self.distributions.done(package_id, metadata); + pub fn insert_metadata(&self, version_id: VersionId, response: MetadataResponse) { + self.distributions.done(version_id, response); } /// Get the [`VersionsResponse`] for a given package name, without waiting. @@ -34,8 +33,8 @@ impl InMemoryIndex { self.packages.get(package_name) } - /// Get the [`Metadata23`] for a given package ID, without waiting. - pub fn get_metadata(&self, package_id: &PackageId) -> Option> { - self.distributions.get(package_id) + /// Get the [`MetadataResponse`] for a given package ID, without waiting. + pub fn get_metadata(&self, version_id: &VersionId) -> Option> { + self.distributions.get(version_id) } } diff --git a/crates/uv-resolver/src/resolver/mod.rs b/crates/uv-resolver/src/resolver/mod.rs index 5b99e307d..74a564682 100644 --- a/crates/uv-resolver/src/resolver/mod.rs +++ b/crates/uv-resolver/src/resolver/mod.rs @@ -6,7 +6,6 @@ use std::ops::Deref; use std::sync::Arc; use anyhow::Result; - use dashmap::{DashMap, DashSet}; use futures::{FutureExt, StreamExt}; use itertools::Itertools; @@ -15,7 +14,7 @@ use pubgrub::range::Range; use pubgrub::solver::{Incompatibility, State}; use rustc_hash::{FxHashMap, FxHashSet}; use tokio_stream::wrappers::ReceiverStream; -use tracing::{debug, info_span, instrument, trace, Instrument}; +use tracing::{debug, info_span, instrument, trace, warn, Instrument}; use distribution_types::{ BuiltDist, Dist, DistributionMetadata, IncompatibleDist, IncompatibleSource, IncompatibleWheel, @@ -27,14 +26,14 @@ use pep508_rs::{MarkerEnvironment, Requirement}; use platform_tags::Tags; use pypi_types::Metadata23; pub(crate) use urls::Urls; -use uv_client::{FlatIndex, RegistryClient}; -use uv_distribution::DistributionDatabase; +use uv_client::RegistryClient; +use uv_configuration::{Constraints, Overrides}; +use uv_distribution::{ArchiveMetadata, DistributionDatabase}; use uv_interpreter::Interpreter; use uv_normalize::PackageName; -use uv_types::{BuildContext, Constraints, InstalledPackagesProvider, Overrides}; +use uv_types::{BuildContext, HashStrategy, InstalledPackagesProvider}; use crate::candidate_selector::{CandidateDist, CandidateSelector}; - use crate::editables::Editables; use crate::error::ResolveError; use crate::manifest::Manifest; @@ -46,16 +45,18 @@ use crate::pubgrub::{ }; use crate::python_requirement::PythonRequirement; use crate::resolution::ResolutionGraph; +use crate::resolver::batch_prefetch::BatchPrefetcher; pub use crate::resolver::index::InMemoryIndex; pub use crate::resolver::provider::{ - DefaultResolverProvider, PackageVersionsResult, ResolverProvider, VersionsResponse, - WheelMetadataResult, + DefaultResolverProvider, MetadataResponse, PackageVersionsResult, ResolverProvider, + VersionsResponse, WheelMetadataResult, }; use crate::resolver::reporter::Facade; pub use crate::resolver::reporter::{BuildId, Reporter}; use crate::yanks::AllowedYanks; -use crate::{DependencyMode, Exclusions, Options, VersionMap}; +use crate::{DependencyMode, Exclusions, FlatIndex, Options}; +mod batch_prefetch; mod index; mod locals; mod provider; @@ -70,15 +71,32 @@ pub(crate) enum UnavailableVersion { IncompatibleDist(IncompatibleDist), } -/// The package is unavailable and cannot be used +/// The package is unavailable and cannot be used. #[derive(Debug, Clone)] pub(crate) enum UnavailablePackage { - /// Index lookups were disabled (i.e., `--no-index`) and the package was not found in a flat index (i.e. from `--find-links`) + /// Index lookups were disabled (i.e., `--no-index`) and the package was not found in a flat index (i.e. from `--find-links`). NoIndex, /// Network requests were disabled (i.e., `--offline`), and the package was not found in the cache. Offline, - /// The package was not found in the registry + /// The package was not found in the registry. NotFound, + /// The package metadata was found, but could not be parsed. + InvalidMetadata(String), + /// The package has an invalid structure. + InvalidStructure(String), +} + +/// The package is unavailable at specific versions. +#[derive(Debug, Clone)] +pub(crate) enum IncompletePackage { + /// Network requests were disabled (i.e., `--offline`), and the wheel metadata was not found in the cache. + Offline, + /// The wheel metadata was found, but could not be parsed. + InvalidMetadata(String), + /// The wheel metadata was found, but the metadata was inconsistent. + InconsistentMetadata(String), + /// The wheel has an invalid structure. + InvalidStructure(String), } enum ResolverVersion { @@ -103,13 +121,16 @@ pub struct Resolver< urls: Urls, locals: Locals, dependency_mode: DependencyMode, + hasher: &'a HashStrategy, markers: &'a MarkerEnvironment, python_requirement: PythonRequirement, selector: CandidateSelector, index: &'a InMemoryIndex, installed_packages: &'a InstalledPackages, - /// Incompatibilities for packages that are entirely unavailable + /// Incompatibilities for packages that are entirely unavailable. unavailable_packages: DashMap, + /// Incompatibilities for packages that are unavailable at specific versions. + incomplete_packages: DashMap>, /// The set of all registry-based packages visited during resolution. visited: DashSet, reporter: Option>, @@ -135,6 +156,7 @@ impl< client: &'a RegistryClient, flat_index: &'a FlatIndex, index: &'a InMemoryIndex, + hasher: &'a HashStrategy, build_context: &'a Context, installed_packages: &'a InstalledPackages, ) -> Result { @@ -145,6 +167,7 @@ impl< tags, PythonRequirement::new(interpreter, markers), AllowedYanks::from_manifest(&manifest, markers), + hasher, options.exclude_newer, build_context.no_binary(), build_context.no_build(), @@ -152,6 +175,7 @@ impl< Self::new_custom_io( manifest, options, + hasher, markers, PythonRequirement::new(interpreter, markers), index, @@ -168,9 +192,11 @@ impl< > Resolver<'a, Provider, InstalledPackages> { /// Initialize a new resolver using a user provided backend. + #[allow(clippy::too_many_arguments)] pub fn new_custom_io( manifest: Manifest, options: Options, + hasher: &'a HashStrategy, markers: &'a MarkerEnvironment, python_requirement: PythonRequirement, index: &'a InMemoryIndex, @@ -180,6 +206,7 @@ impl< Ok(Self { index, unavailable_packages: DashMap::default(), + incomplete_packages: DashMap::default(), visited: DashSet::default(), selector: CandidateSelector::for_resolution(options, &manifest, markers), dependency_mode: options.dependency_mode, @@ -192,6 +219,7 @@ impl< preferences: Preferences::from_iter(manifest.preferences, markers), exclusions: manifest.exclusions, editables: Editables::from_requirements(manifest.editables), + hasher, markers, python_requirement, reporter: None, @@ -215,14 +243,14 @@ impl< pub async fn resolve(self) -> Result { // A channel to fetch package metadata (e.g., given `flask`, fetch all versions) and version // metadata (e.g., given `flask==1.0.0`, fetch the metadata for that version). - // Channel size is set to the same size as the task buffer for simplicity. - let (request_sink, request_stream) = tokio::sync::mpsc::channel(50); + // Channel size is set large to accommodate batch prefetching. + let (request_sink, request_stream) = tokio::sync::mpsc::channel(300); // Run the fetcher. let requests_fut = self.fetch(request_stream).fuse(); // Run the solver. - let resolve_fut = self.solve(request_sink).fuse(); + let resolve_fut = self.solve(request_sink).boxed().fuse(); // Wait for both to complete. match tokio::try_join!(requests_fut, resolve_fut) { @@ -242,7 +270,8 @@ impl< .with_selector(self.selector.clone()) .with_python_requirement(&self.python_requirement) .with_index_locations(self.provider.index_locations()) - .with_unavailable_packages(&self.unavailable_packages), + .with_unavailable_packages(&self.unavailable_packages) + .with_incomplete_packages(&self.incomplete_packages), ) } else { err @@ -258,6 +287,7 @@ impl< request_sink: tokio::sync::mpsc::Sender, ) -> Result { let root = PubGrubPackage::Root(self.project.clone()); + let mut prefetcher = BatchPrefetcher::default(); // Keep track of the packages for which we've requested metadata. let mut pins = FilePins::default(); @@ -306,6 +336,8 @@ impl< }; next = highest_priority_pkg; + prefetcher.version_tried(next.clone()); + let term_intersection = state .partial_solution .term_intersection_for_package(&next) @@ -344,6 +376,12 @@ impl< UnavailablePackage::NotFound => { "was not found in the package registry" } + UnavailablePackage::InvalidMetadata(_) => { + "was found, but the metadata could not be parsed" + } + UnavailablePackage::InvalidStructure(_) => { + "was found, but has an invalid format" + } }) } else { None @@ -407,6 +445,17 @@ impl< } }; + prefetcher + .prefetch_batches( + &next, + &version, + term_intersection.unwrap_positive(), + &request_sink, + self.index, + &self.selector, + ) + .await?; + self.on_progress(&next, &version); if added_dependencies @@ -421,18 +470,10 @@ impl< .await? { Dependencies::Unavailable(reason) => { - let message = { - if matches!(package, PubGrubPackage::Root(_)) { - // Including front-matter for the root package is redundant - reason.clone() - } else { - format!("its dependencies are unusable because {reason}") - } - }; state.add_incompatibility(Incompatibility::unavailable( package.clone(), version.clone(), - message, + reason.clone(), )); continue; } @@ -482,19 +523,27 @@ impl< match package { PubGrubPackage::Root(_) => {} PubGrubPackage::Python(_) => {} - PubGrubPackage::Package(package_name, _extra, None) => { + PubGrubPackage::Package(name, _extra, None) => { + // Verify that the package is allowed under the hash-checking policy. + if !self.hasher.allows_package(name) { + return Err(ResolveError::UnhashedPackage(name.clone())); + } + // Emit a request to fetch the metadata for this package. - if self.index.packages.register(package_name.clone()) { - priorities.add(package_name.clone()); - request_sink - .send(Request::Package(package_name.clone())) - .await?; + if self.index.packages.register(name.clone()) { + priorities.add(name.clone()); + request_sink.send(Request::Package(name.clone())).await?; } } - PubGrubPackage::Package(package_name, _extra, Some(url)) => { + PubGrubPackage::Package(name, _extra, Some(url)) => { + // Verify that the package is allowed under the hash-checking policy. + if !self.hasher.allows_url(url) { + return Err(ResolveError::UnhashedPackage(name.clone())); + } + // Emit a request to fetch the metadata for this distribution. - let dist = Dist::from_url(package_name.clone(), url.clone())?; - if self.index.distributions.register(dist.package_id()) { + let dist = Dist::from_url(name.clone(), url.clone())?; + if self.index.distributions.register(dist.version_id()) { priorities.add(dist.name().clone()); request_sink.send(Request::Dist(dist)).await?; } @@ -592,12 +641,44 @@ impl< } let dist = PubGrubDistribution::from_url(package_name, url); - let metadata = self + let response = self .index .distributions - .wait(&dist.package_id()) + .wait(&dist.version_id()) .await .ok_or(ResolveError::Unregistered)?; + + // If we failed to fetch the metadata for a URL, we can't proceed. + let metadata = match &*response { + MetadataResponse::Found(archive) => &archive.metadata, + MetadataResponse::Offline => { + self.unavailable_packages + .insert(package_name.clone(), UnavailablePackage::Offline); + return Ok(None); + } + MetadataResponse::InvalidMetadata(err) => { + self.unavailable_packages.insert( + package_name.clone(), + UnavailablePackage::InvalidMetadata(err.to_string()), + ); + return Ok(None); + } + MetadataResponse::InconsistentMetadata(err) => { + self.unavailable_packages.insert( + package_name.clone(), + UnavailablePackage::InvalidMetadata(err.to_string()), + ); + return Ok(None); + } + MetadataResponse::InvalidStructure(err) => { + self.unavailable_packages.insert( + package_name.clone(), + UnavailablePackage::InvalidStructure(err.to_string()), + ); + return Ok(None); + } + }; + let version = &metadata.version; // The version is incompatible with the requirement. @@ -632,23 +713,22 @@ impl< .ok_or(ResolveError::Unregistered)?; self.visited.insert(package_name.clone()); - let empty_version_map = VersionMap::default(); - let version_map = match *versions_response { - VersionsResponse::Found(ref version_map) => version_map, + let version_maps = match *versions_response { + VersionsResponse::Found(ref version_maps) => version_maps.as_slice(), VersionsResponse::NoIndex => { self.unavailable_packages .insert(package_name.clone(), UnavailablePackage::NoIndex); - &empty_version_map + &[] } VersionsResponse::Offline => { self.unavailable_packages .insert(package_name.clone(), UnavailablePackage::Offline); - &empty_version_map + &[] } VersionsResponse::NotFound => { self.unavailable_packages .insert(package_name.clone(), UnavailablePackage::NotFound); - &empty_version_map + &[] } }; @@ -664,7 +744,7 @@ impl< let Some(candidate) = self.selector.select( package_name, range, - version_map, + version_maps, &self.preferences, self.installed_packages, &self.exclusions, @@ -714,8 +794,7 @@ impl< let version = candidate.version().clone(); // Emit a request to fetch the metadata for this version. - - if self.index.distributions.register(candidate.package_id()) { + if self.index.distributions.register(candidate.version_id()) { let request = match dist.for_resolution() { ResolvedDistRef::Installable(dist) => Request::Dist(dist.clone()), ResolvedDistRef::Installed(dist) => Request::Installed(dist.clone()), @@ -799,13 +878,13 @@ impl< Some(url) => PubGrubDistribution::from_url(package_name, url), None => PubGrubDistribution::from_registry(package_name, version), }; - let package_id = dist.package_id(); + let version_id = dist.version_id(); // Wait for the metadata to be available. self.index .distributions - .wait(&package_id) - .instrument(info_span!("distributions_wait", %package_id)) + .wait(&version_id) + .instrument(info_span!("distributions_wait", %version_id)) .await .ok_or(ResolveError::Unregistered)?; } @@ -850,7 +929,7 @@ impl< Some(url) => PubGrubDistribution::from_url(package_name, url), None => PubGrubDistribution::from_registry(package_name, version), }; - let package_id = dist.package_id(); + let version_id = dist.version_id(); // If the package does not exist in the registry or locally, we cannot fetch its dependencies if self.unavailable_packages.get(package_name).is_some() @@ -869,14 +948,67 @@ impl< } // Wait for the metadata to be available. - let metadata = self + let response = self .index .distributions - .wait(&package_id) - .instrument(info_span!("distributions_wait", %package_id)) + .wait(&version_id) + .instrument(info_span!("distributions_wait", %version_id)) .await .ok_or(ResolveError::Unregistered)?; + let metadata = match &*response { + MetadataResponse::Found(archive) => &archive.metadata, + MetadataResponse::Offline => { + self.incomplete_packages + .entry(package_name.clone()) + .or_default() + .insert(version.clone(), IncompletePackage::Offline); + return Ok(Dependencies::Unavailable( + "network connectivity is disabled, but the metadata wasn't found in the cache" + .to_string(), + )); + } + MetadataResponse::InvalidMetadata(err) => { + warn!("Unable to extract metadata for {package_name}: {err}"); + self.incomplete_packages + .entry(package_name.clone()) + .or_default() + .insert( + version.clone(), + IncompletePackage::InvalidMetadata(err.to_string()), + ); + return Ok(Dependencies::Unavailable( + "the package metadata could not be parsed".to_string(), + )); + } + MetadataResponse::InconsistentMetadata(err) => { + warn!("Unable to extract metadata for {package_name}: {err}"); + self.incomplete_packages + .entry(package_name.clone()) + .or_default() + .insert( + version.clone(), + IncompletePackage::InconsistentMetadata(err.to_string()), + ); + return Ok(Dependencies::Unavailable( + "the package metadata was inconsistent".to_string(), + )); + } + MetadataResponse::InvalidStructure(err) => { + warn!("Unable to extract metadata for {package_name}: {err}"); + self.incomplete_packages + .entry(package_name.clone()) + .or_default() + .insert( + version.clone(), + IncompletePackage::InvalidStructure(err.to_string()), + ); + return Ok(Dependencies::Unavailable( + "the package has an invalid format".to_string(), + )); + } + }; + let mut constraints = PubGrubDependencies::from_requirements( &metadata.requires_dist, &self.constraints, @@ -926,23 +1058,42 @@ impl< } Some(Response::Installed { dist, metadata }) => { trace!("Received installed distribution metadata for: {dist}"); - self.index.distributions.done(dist.package_id(), metadata); + self.index.distributions.done( + dist.version_id(), + MetadataResponse::Found(ArchiveMetadata::from(metadata)), + ); } Some(Response::Dist { dist: Dist::Built(dist), metadata, }) => { trace!("Received built distribution metadata for: {dist}"); - self.index.distributions.done(dist.package_id(), metadata); + match &metadata { + MetadataResponse::InvalidMetadata(err) => { + warn!("Unable to extract metadata for {dist}: {err}"); + } + MetadataResponse::InvalidStructure(err) => { + warn!("Unable to extract metadata for {dist}: {err}"); + } + _ => {} + } + self.index.distributions.done(dist.version_id(), metadata); } Some(Response::Dist { - dist: Dist::Source(distribution), + dist: Dist::Source(dist), metadata, }) => { - trace!("Received source distribution metadata for: {distribution}"); - self.index - .distributions - .done(distribution.package_id(), metadata); + trace!("Received source distribution metadata for: {dist}"); + match &metadata { + MetadataResponse::InvalidMetadata(err) => { + warn!("Unable to extract metadata for {dist}: {err}"); + } + MetadataResponse::InvalidStructure(err) => { + warn!("Unable to extract metadata for {dist}: {err}"); + } + _ => {} + } + self.index.distributions.done(dist.version_id(), metadata); } None => {} } @@ -1047,7 +1198,7 @@ impl< }; // Emit a request to fetch the metadata for this version. - if self.index.distributions.register(candidate.package_id()) { + if self.index.distributions.register(candidate.version_id()) { let dist = dist.for_resolution().to_owned(); let response = match dist { @@ -1150,7 +1301,10 @@ enum Response { /// The returned metadata for a package hosted on a registry. Package(PackageName, VersionsResponse), /// The returned metadata for a distribution. - Dist { dist: Dist, metadata: Metadata23 }, + Dist { + dist: Dist, + metadata: MetadataResponse, + }, /// The returned metadata for an already-installed distribution. Installed { dist: InstalledDist, diff --git a/crates/uv-resolver/src/resolver/provider.rs b/crates/uv-resolver/src/resolver/provider.rs index 266f7fc1e..3479620ac 100644 --- a/crates/uv-resolver/src/resolver/provider.rs +++ b/crates/uv-resolver/src/resolver/provider.rs @@ -5,24 +5,26 @@ use chrono::{DateTime, Utc}; use distribution_types::{Dist, IndexLocations}; use platform_tags::Tags; -use pypi_types::Metadata23; -use uv_client::{FlatIndex, RegistryClient}; -use uv_distribution::DistributionDatabase; -use uv_normalize::PackageName; -use uv_types::{BuildContext, NoBinary, NoBuild}; +use uv_client::RegistryClient; +use uv_configuration::{NoBinary, NoBuild}; +use uv_distribution::{ArchiveMetadata, DistributionDatabase}; +use uv_normalize::PackageName; +use uv_types::{BuildContext, HashStrategy}; + +use crate::flat_index::FlatIndex; use crate::python_requirement::PythonRequirement; use crate::version_map::VersionMap; use crate::yanks::AllowedYanks; pub type PackageVersionsResult = Result; -pub type WheelMetadataResult = Result; +pub type WheelMetadataResult = Result; /// The response when requesting versions for a package #[derive(Debug)] pub enum VersionsResponse { /// The package was found in the registry with the included versions - Found(VersionMap), + Found(Vec), /// The package was not found in the registry NotFound, /// The package was not found in the local registry @@ -31,6 +33,20 @@ pub enum VersionsResponse { Offline, } +#[derive(Debug)] +pub enum MetadataResponse { + /// The wheel metadata was found and parsed successfully. + Found(ArchiveMetadata), + /// The wheel metadata was found, but could not be parsed. + InvalidMetadata(Box), + /// The wheel metadata was found, but the metadata was inconsistent. + InconsistentMetadata(Box), + /// The wheel has an invalid structure. + InvalidStructure(Box), + /// The wheel metadata was not found in the cache and the network is not available. + Offline, +} + pub trait ResolverProvider: Send + Sync { /// Get the version map for a package. fn get_package_versions<'io>( @@ -67,6 +83,7 @@ pub struct DefaultResolverProvider<'a, Context: BuildContext + Send + Sync> { tags: Tags, python_requirement: PythonRequirement, allowed_yanks: AllowedYanks, + hasher: HashStrategy, exclude_newer: Option>, no_binary: NoBinary, no_build: NoBuild, @@ -82,6 +99,7 @@ impl<'a, Context: BuildContext + Send + Sync> DefaultResolverProvider<'a, Contex tags: &'a Tags, python_requirement: PythonRequirement, allowed_yanks: AllowedYanks, + hasher: &'a HashStrategy, exclude_newer: Option>, no_binary: &'a NoBinary, no_build: &'a NoBuild, @@ -93,6 +111,7 @@ impl<'a, Context: BuildContext + Send + Sync> DefaultResolverProvider<'a, Contex tags: tags.clone(), python_requirement, allowed_yanks, + hasher: hasher.clone(), exclude_newer, no_binary: no_binary.clone(), no_build: no_build.clone(), @@ -108,34 +127,38 @@ impl<'a, Context: BuildContext + Send + Sync> ResolverProvider &'io self, package_name: &'io PackageName, ) -> PackageVersionsResult { - let result = self.client.simple(package_name).await; - - // If the "Simple API" request was successful, convert to `VersionMap` on the Tokio - // threadpool, since it can be slow. - match result { - Ok((index, metadata)) => Ok(VersionsResponse::Found(VersionMap::from_metadata( - metadata, - package_name, - &index, - &self.tags, - &self.python_requirement, - &self.allowed_yanks, - self.exclude_newer.as_ref(), - self.flat_index.get(package_name).cloned(), - &self.no_binary, - &self.no_build, - ))), + match self.client.simple(package_name).await { + Ok(results) => Ok(VersionsResponse::Found( + results + .into_iter() + .map(|(index, metadata)| { + VersionMap::from_metadata( + metadata, + package_name, + &index, + &self.tags, + &self.python_requirement, + &self.allowed_yanks, + &self.hasher, + self.exclude_newer.as_ref(), + self.flat_index.get(package_name).cloned(), + &self.no_binary, + &self.no_build, + ) + }) + .collect(), + )), Err(err) => match err.into_kind() { uv_client::ErrorKind::PackageNotFound(_) => { if let Some(flat_index) = self.flat_index.get(package_name).cloned() { - Ok(VersionsResponse::Found(VersionMap::from(flat_index))) + Ok(VersionsResponse::Found(vec![VersionMap::from(flat_index)])) } else { Ok(VersionsResponse::NotFound) } } uv_client::ErrorKind::NoIndex(_) => { if let Some(flat_index) = self.flat_index.get(package_name).cloned() { - Ok(VersionsResponse::Found(VersionMap::from(flat_index))) + Ok(VersionsResponse::Found(vec![VersionMap::from(flat_index)])) } else if self.flat_index.offline() { Ok(VersionsResponse::Offline) } else { @@ -144,7 +167,7 @@ impl<'a, Context: BuildContext + Send + Sync> ResolverProvider } uv_client::ErrorKind::Offline(_) => { if let Some(flat_index) = self.flat_index.get(package_name).cloned() { - Ok(VersionsResponse::Found(VersionMap::from(flat_index))) + Ok(VersionsResponse::Found(vec![VersionMap::from(flat_index)])) } else { Ok(VersionsResponse::Offline) } @@ -154,8 +177,40 @@ impl<'a, Context: BuildContext + Send + Sync> ResolverProvider } } + /// Fetch the metadata for a distribution, building it if necessary. async fn get_or_build_wheel_metadata<'io>(&'io self, dist: &'io Dist) -> WheelMetadataResult { - self.fetcher.get_or_build_wheel_metadata(dist).await + match self + .fetcher + .get_or_build_wheel_metadata(dist, self.hasher.get(dist)) + .await + { + Ok(metadata) => Ok(MetadataResponse::Found(metadata)), + Err(err) => match err { + uv_distribution::Error::Client(client) => match client.into_kind() { + uv_client::ErrorKind::Offline(_) => Ok(MetadataResponse::Offline), + uv_client::ErrorKind::MetadataParseError(_, _, err) => { + Ok(MetadataResponse::InvalidMetadata(err)) + } + uv_client::ErrorKind::DistInfo(err) => { + Ok(MetadataResponse::InvalidStructure(Box::new(err))) + } + kind => Err(uv_client::Error::from(kind).into()), + }, + uv_distribution::Error::VersionMismatch { .. } => { + Ok(MetadataResponse::InconsistentMetadata(Box::new(err))) + } + uv_distribution::Error::NameMismatch { .. } => { + Ok(MetadataResponse::InconsistentMetadata(Box::new(err))) + } + uv_distribution::Error::Metadata(err) => { + Ok(MetadataResponse::InvalidMetadata(Box::new(err))) + } + uv_distribution::Error::DistInfo(err) => { + Ok(MetadataResponse::InvalidStructure(Box::new(err))) + } + err => Err(err), + }, + } } fn index_locations(&self) -> &IndexLocations { diff --git a/crates/uv-resolver/src/version_map.rs b/crates/uv-resolver/src/version_map.rs index b3045e600..91e41c4e2 100644 --- a/crates/uv-resolver/src/version_map.rs +++ b/crates/uv-resolver/src/version_map.rs @@ -2,23 +2,25 @@ use std::collections::btree_map::{BTreeMap, Entry}; use std::sync::OnceLock; use chrono::{DateTime, Utc}; +use rkyv::{de::deserializers::SharedDeserializeMap, Deserialize}; use rustc_hash::FxHashSet; -use tracing::{instrument, warn}; +use tracing::instrument; use distribution_filename::{DistFilename, WheelFilename}; use distribution_types::{ - Dist, IncompatibleSource, IncompatibleWheel, IndexUrl, PrioritizedDist, + Dist, Hash, IncompatibleSource, IncompatibleWheel, IndexUrl, PrioritizedDist, SourceDistCompatibility, WheelCompatibility, }; use pep440_rs::{Version, VersionSpecifiers}; -use platform_tags::Tags; -use pypi_types::{Hashes, Yanked}; -use rkyv::{de::deserializers::SharedDeserializeMap, Deserialize}; -use uv_client::{FlatDistributions, OwnedArchive, SimpleMetadata, VersionFiles}; +use platform_tags::{TagCompatibility, Tags}; +use pypi_types::{HashDigest, Yanked}; +use uv_client::{OwnedArchive, SimpleMetadata, VersionFiles}; +use uv_configuration::{NoBinary, NoBuild}; use uv_normalize::PackageName; -use uv_types::{NoBinary, NoBuild}; +use uv_types::HashStrategy; use uv_warnings::warn_user_once; +use crate::flat_index::FlatDistributions; use crate::{python_requirement::PythonRequirement, yanks::AllowedYanks}; /// A map from versions to distributions. @@ -46,6 +48,7 @@ impl VersionMap { tags: &Tags, python_requirement: &PythonRequirement, allowed_yanks: &AllowedYanks, + hasher: &HashStrategy, exclude_newer: Option<&DateTime>, flat_index: Option, no_binary: &NoBinary, @@ -109,6 +112,7 @@ impl VersionMap { .allowed_versions(package_name) .cloned() .unwrap_or_default(); + let required_hashes = hasher.get_package(package_name).digests().to_vec(); Self { inner: VersionMapInner::Lazy(VersionMapLazy { map, @@ -120,6 +124,7 @@ impl VersionMap { python_requirement: python_requirement.clone(), exclude_newer: exclude_newer.copied(), allowed_yanks, + required_hashes, }), } } @@ -152,7 +157,9 @@ impl VersionMap { /// which can be used to lazily request a [`CompatibleDist`]. This is /// useful in cases where one can skip materializing a full distribution /// for each version. - pub(crate) fn iter(&self) -> impl DoubleEndedIterator { + pub(crate) fn iter( + &self, + ) -> impl DoubleEndedIterator + ExactSizeIterator { match self.inner { VersionMapInner::Eager(ref map) => { either::Either::Left(map.iter().map(|(version, dist)| { @@ -174,16 +181,10 @@ impl VersionMap { } /// Return the [`Hashes`] for the given version, if any. - pub(crate) fn hashes(&self, version: &Version) -> Vec { + pub(crate) fn hashes(&self, version: &Version) -> Option> { match self.inner { - VersionMapInner::Eager(ref map) => map - .get(version) - .map(|file| file.hashes().to_vec()) - .unwrap_or_default(), - VersionMapInner::Lazy(ref lazy) => lazy - .get(version) - .map(|file| file.hashes().to_vec()) - .unwrap_or_default(), + VersionMapInner::Eager(ref map) => map.get(version).map(|file| file.hashes().to_vec()), + VersionMapInner::Lazy(ref lazy) => lazy.get(version).map(|file| file.hashes().to_vec()), } } @@ -306,6 +307,8 @@ struct VersionMapLazy { exclude_newer: Option>, /// Which yanked versions are allowed allowed_yanks: FxHashSet, + /// The hashes of allowed distributions. + required_hashes: Vec, } impl VersionMapLazy { @@ -382,13 +385,14 @@ impl VersionMapLazy { let version = filename.version().clone(); let requires_python = file.requires_python.clone(); let yanked = file.yanked.clone(); - let hash = file.hashes.clone(); + let hashes = file.hashes.clone(); match filename { DistFilename::WheelFilename(filename) => { let compatibility = self.wheel_compatibility( &filename, &version, requires_python, + &hashes, yanked, excluded, upload_time, @@ -398,12 +402,13 @@ impl VersionMapLazy { file, self.index.clone(), ); - priority_dist.insert_built(dist, Some(hash), compatibility); + priority_dist.insert_built(dist, hashes, compatibility); } DistFilename::SourceDistFilename(filename) => { let compatibility = self.source_dist_compatibility( &version, requires_python, + &hashes, yanked, excluded, upload_time, @@ -413,7 +418,7 @@ impl VersionMapLazy { file, self.index.clone(), ); - priority_dist.insert_source(dist, Some(hash), compatibility); + priority_dist.insert_source(dist, hashes, compatibility); } } } @@ -426,10 +431,12 @@ impl VersionMapLazy { simple.dist.get_or_init(get_or_init).as_ref() } + #[allow(clippy::too_many_arguments)] fn source_dist_compatibility( &self, version: &Version, requires_python: Option, + hashes: &[HashDigest], yanked: Option, excluded: bool, upload_time: Option, @@ -466,14 +473,32 @@ impl VersionMapLazy { } } - SourceDistCompatibility::Compatible + // Check if hashes line up. If hashes aren't required, they're considered matching. + let hash = if self.required_hashes.is_empty() { + Hash::Matched + } else { + if hashes.is_empty() { + Hash::Missing + } else if hashes + .iter() + .any(|hash| self.required_hashes.contains(hash)) + { + Hash::Matched + } else { + Hash::Mismatched + } + }; + + SourceDistCompatibility::Compatible(hash) } + #[allow(clippy::too_many_arguments)] fn wheel_compatibility( &self, filename: &WheelFilename, version: &Version, requires_python: Option, + hashes: &[HashDigest], yanked: Option, excluded: bool, upload_time: Option, @@ -504,8 +529,31 @@ impl VersionMapLazy { } } - // Determine a compatibility for the wheel based on tags - WheelCompatibility::from(filename.compatibility(&self.tags)) + // Determine a compatibility for the wheel based on tags. + let priority = match filename.compatibility(&self.tags) { + TagCompatibility::Incompatible(tag) => { + return WheelCompatibility::Incompatible(IncompatibleWheel::Tag(tag)) + } + TagCompatibility::Compatible(priority) => priority, + }; + + // Check if hashes line up. If hashes aren't required, they're considered matching. + let hash = if self.required_hashes.is_empty() { + Hash::Matched + } else { + if hashes.is_empty() { + Hash::Missing + } else if hashes + .iter() + .any(|hash| self.required_hashes.contains(hash)) + { + Hash::Matched + } else { + Hash::Mismatched + } + }; + + WheelCompatibility::Compatible(hash, priority) } } diff --git a/crates/uv-resolver/tests/resolver.rs b/crates/uv-resolver/tests/resolver.rs index 3fc94d237..9a4951e1b 100644 --- a/crates/uv-resolver/tests/resolver.rs +++ b/crates/uv-resolver/tests/resolver.rs @@ -14,15 +14,15 @@ use distribution_types::{IndexLocations, Resolution, SourceDist}; use pep508_rs::{MarkerEnvironment, Requirement, StringVersion}; use platform_tags::{Arch, Os, Platform, Tags}; use uv_cache::Cache; -use uv_client::{FlatIndex, RegistryClientBuilder}; +use uv_client::RegistryClientBuilder; +use uv_configuration::{BuildKind, Constraints, NoBinary, NoBuild, Overrides, SetupPyStrategy}; use uv_interpreter::{find_default_python, Interpreter, PythonEnvironment}; use uv_resolver::{ - DisplayResolutionGraph, Exclusions, InMemoryIndex, Manifest, Options, OptionsBuilder, - PreReleaseMode, Preference, ResolutionGraph, ResolutionMode, Resolver, + DisplayResolutionGraph, Exclusions, FlatIndex, InMemoryIndex, Manifest, Options, + OptionsBuilder, PreReleaseMode, Preference, ResolutionGraph, ResolutionMode, Resolver, }; use uv_types::{ - BuildContext, BuildIsolation, BuildKind, Constraints, EmptyInstalledPackages, NoBinary, - NoBuild, Overrides, SetupPyStrategy, SourceBuildTrait, + BuildContext, BuildIsolation, EmptyInstalledPackages, HashStrategy, SourceBuildTrait, }; // Exclude any packages uploaded after this date. @@ -125,6 +125,7 @@ async fn resolve( find_default_python(&Cache::temp().unwrap()).expect("Expected a python to be installed"); let interpreter = Interpreter::artificial(real_interpreter.platform().clone(), markers.clone()); let build_context = DummyContext::new(Cache::temp()?, interpreter.clone()); + let hashes = HashStrategy::None; let installed_packages = EmptyInstalledPackages; let resolver = Resolver::new( manifest, @@ -135,6 +136,7 @@ async fn resolve( &client, &flat_index, &index, + &hashes, &build_context, &installed_packages, )?; diff --git a/crates/uv-toolchain/Cargo.toml b/crates/uv-toolchain/Cargo.toml new file mode 100644 index 000000000..59bab562e --- /dev/null +++ b/crates/uv-toolchain/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "uv-toolchain" +version = "0.1.0" +edition.workspace = true +rust-version.workspace = true +homepage.workspace = true +documentation.workspace = true +repository.workspace = true +authors.workspace = true +license.workspace = true + +[dependencies] +uv-client = { workspace = true } +uv-extract = { workspace = true } +uv-fs = { workspace = true } +pep440_rs = { workspace = true } +pep508_rs = { workspace = true } + +anyhow = { workspace = true } +fs-err = { workspace = true } +futures = { workspace = true } +once_cell = {workspace = true} +reqwest = { workspace = true } +reqwest-middleware = { workspace = true } +tempfile = { workspace = true } +thiserror = { workspace = true } +tokio = { workspace = true } +tokio-util = { workspace = true, features = ["compat"] } +tracing = { workspace = true } +url = { workspace = true } + +[lints] +workspace = true diff --git a/scripts/bootstrap/fetch-version-metadata.py b/crates/uv-toolchain/fetch-version-metadata.py similarity index 69% rename from scripts/bootstrap/fetch-version-metadata.py rename to crates/uv-toolchain/fetch-version-metadata.py index d3d5e4b25..2dc4b3d9a 100755 --- a/scripts/bootstrap/fetch-version-metadata.py +++ b/crates/uv-toolchain/fetch-version-metadata.py @@ -2,15 +2,11 @@ """ Fetch Python version metadata. -Generates the bootstrap `versions.json` file. - -Installation: - - pip install requests==2.31.0 +Generates the `python-version-metadata.json` file. Usage: - scripts/bootstrap/fetch-versions + python fetch-version-metadata.py Acknowledgements: @@ -22,25 +18,19 @@ import argparse import hashlib import json import logging -import os import re -import sys +import urllib.error +import urllib.request from itertools import chain from pathlib import Path from urllib.parse import unquote -try: - import requests -except ImportError: - print("ERROR: requests is required; install with `pip install requests==2.31.0`") - sys.exit(1) - SELF_DIR = Path(__file__).parent RELEASE_URL = "https://api.github.com/repos/indygreg/python-build-standalone/releases" HEADERS = { "X-GitHub-Api-Version": "2022-11-28", } -VERSIONS_FILE = SELF_DIR / "versions.json" +VERSIONS_FILE = SELF_DIR / "python-version-metadata.json" FLAVOR_PREFERENCES = [ "shared-pgo", "shared-noopt", @@ -63,6 +53,8 @@ SPECIAL_TRIPLES = { "linux64": "x86_64-unknown-linux-gnu", "windows-amd64": "x86_64-pc-windows", "windows-x86": "i686-pc-windows", + "windows-amd64-shared": "x86_64-pc-windows", + "windows-x86-shared": "i686-pc-windows", "linux64-musl": "x86_64-unknown-linux-musl", } @@ -88,8 +80,14 @@ _suffix_re = re.compile( ) ) -# to match the output of the `arch` command -ARCH_MAP = {"aarch64": "arm64"} +# Normalized mappings to match the Rust types +ARCH_MAP = { + "ppc64": "powerpc64", + "ppc64le": "powerpc64le", + "i686": "x86", + "i386": "x86", +} +OS_MAP = {"darwin": "macos"} def parse_filename(filename): @@ -114,10 +112,8 @@ def normalize_triple(triple): triple = SPECIAL_TRIPLES.get(triple, triple) pieces = triple.split("-") try: - arch = pieces[0] - # Normalize - arch = ARCH_MAP.get(arch, arch) - platform = pieces[2] + arch = normalize_arch(pieces[0]) + operating_system = normalize_os(pieces[2]) if pieces[2] == "linux": # On linux, the triple has four segments, the last one is the libc libc = pieces[3] @@ -126,14 +122,27 @@ def normalize_triple(triple): except IndexError: logging.debug("Skipping %r: unknown triple", triple) return - return "%s-%s-%s" % (arch, platform, libc) + return "%s-%s-%s" % (arch, operating_system, libc) -def read_sha256(session, url): - resp = session.get(url + ".sha256") - if not resp.ok: +def normalize_arch(arch): + arch = ARCH_MAP.get(arch, arch) + pieces = arch.split("_") + # Strip `_vN` from `x86_64` + return "_".join(pieces[:2]) + + +def normalize_os(os): + return OS_MAP.get(os, os) + + +def read_sha256(url): + try: + resp = urllib.request.urlopen(url + ".sha256") + except urllib.error.HTTPError: return None - return resp.text.strip() + assert resp.status == 200 + return resp.read().decode().strip() def sha256(path): @@ -150,8 +159,8 @@ def sha256(path): return h.hexdigest() -def _sort_key(info): - triple, flavor, url = info +def _sort_by_flavor_preference(info): + _triple, flavor, _url = info try: pref = FLAVOR_PREFERENCES.index(flavor) except ValueError: @@ -159,32 +168,22 @@ def _sort_key(info): return pref -def get_session() -> requests.Session: - session = requests.Session() - session.headers = HEADERS.copy() - - token = os.environ.get("GITHUB_TOKEN") - if token: - session.headers["Authorization"] = "Bearer " + token - else: - logging.warning( - "An authentication token was not found at `GITHUB_TOKEN`, rate limits may be encountered.", - ) - - return session +def _sort_by_interpreter_and_version(info): + interpreter, version_tuple, _ = info + return (interpreter, version_tuple) -def find(args): +def find(): """ Find available Python versions and write metadata to a file. """ results = {} - session = get_session() + # Collect all available Python downloads for page in range(1, 100): logging.debug("Reading release page %s...", page) - resp = session.get("%s?page=%d" % (RELEASE_URL, page)) - rows = resp.json() + resp = urllib.request.urlopen("%s?page=%d" % (RELEASE_URL, page)) + rows = json.loads(resp.read()) if not rows: break for row in rows: @@ -204,34 +203,47 @@ def find(args): continue results.setdefault(py_ver, []).append((triple, flavor, url)) - cpython_results = {} + # Collapse CPython variants to a single URL flavor per triple + cpython_results: dict[tuple[int, int, int], dict[tuple[str, str, str], str]] = {} for py_ver, choices in results.items(): - choices.sort(key=_sort_key) urls = {} - for triple, flavor, url in choices: + for triple, flavor, url in sorted(choices, key=_sort_by_flavor_preference): triple = tuple(triple.split("-")) + # Skip existing triples, preferring the first flavor if triple in urls: continue urls[triple] = url cpython_results[tuple(map(int, py_ver.split(".")))] = urls + # Collect variants across interpreter kinds + # TODO(zanieb): Note we only support CPython downloads at this time + # but this will include PyPy chain in the future. final_results = {} for interpreter, py_ver, choices in sorted( chain( (("cpython",) + x for x in cpython_results.items()), ), - key=lambda x: x[:2], + key=_sort_by_interpreter_and_version, + # Reverse the ordering so newer versions are first reverse=True, ): - for (arch, platform, libc), url in sorted(choices.items()): - key = "%s-%s.%s.%s-%s-%s-%s" % (interpreter, *py_ver, platform, arch, libc) + # Sort by the remaining information for determinism + # This groups download metadata in triple component order + for (arch, operating_system, libc), url in sorted(choices.items()): + key = "%s-%s.%s.%s-%s-%s-%s" % ( + interpreter, + *py_ver, + operating_system, + arch, + libc, + ) logging.info("Found %s", key) - sha256 = read_sha256(session, url) + sha256 = read_sha256(url) final_results[key] = { "name": interpreter, "arch": arch, - "os": platform, + "os": operating_system, "libc": libc, "major": py_ver[0], "minor": py_ver[1], @@ -273,7 +285,7 @@ def main(): datefmt="%Y-%m-%d %H:%M:%S", ) - find(args) + find() if __name__ == "__main__": diff --git a/scripts/bootstrap/versions.json b/crates/uv-toolchain/python-version-metadata.json similarity index 63% rename from scripts/bootstrap/versions.json rename to crates/uv-toolchain/python-version-metadata.json index c95201415..3306ca69d 100644 --- a/scripts/bootstrap/versions.json +++ b/crates/uv-toolchain/python-version-metadata.json @@ -1,18 +1,106 @@ { - "cpython-3.12.1-darwin-arm64-none": { + "cpython-3.12.2-linux-aarch64-gnu": { "name": "cpython", - "arch": "arm64", - "os": "darwin", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 2, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "469a7fd0d0a09936c5db41b5ac83bb29d5bfeb721aa483ac92f3f7ac4d311097" + }, + "cpython-3.12.2-macos-aarch64-none": { + "name": "cpython", + "arch": "aarch64", + "os": "macos", "libc": "none", "major": 3, "minor": 12, - "patch": 1, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "61e51e3490537b800fcefad718157cf775de41044e95aa538b63ab599f66f3a9" + "patch": 2, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "2afcc8b25c55793f6ceb0bef2e547e101f53c9e25a0fe0332320e5381a1f0fdb" }, - "cpython-3.12.1-linux-arm64-gnu": { + "cpython-3.12.2-linux-powerpc64le-gnu": { "name": "cpython", - "arch": "arm64", + "arch": "powerpc64le", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 2, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "1d70476fb9013cc93e787417680b34629b510e6e2145cf48bb2f0fe887f7a4d8" + }, + "cpython-3.12.2-linux-s390x-gnu": { + "name": "cpython", + "arch": "s390x", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 2, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "f40b88607928b5ee34ff87c1d574c8493a1604d7a40474e1b03731184186f419" + }, + "cpython-3.12.2-windows-x86-none": { + "name": "cpython", + "arch": "x86", + "os": "windows", + "libc": "none", + "major": 3, + "minor": 12, + "patch": 2, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + "sha256": "ee985ae6a6a98f4d5bd19fd8c59f45235911d19b64e1dbd026261b8103f15db5" + }, + "cpython-3.12.2-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 2, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "15b61ed9d33b35ad014a13a68a55d8ea5ba7fb70945644747f4e53c659f2fed6" + }, + "cpython-3.12.2-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 12, + "patch": 2, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-x86_64-unknown-linux-musl-lto-full.tar.zst", + "sha256": "7ec1dc7ad8223ec5839a57d232fd3cf730987f7b0f88b2c4f15ee935bcabbaa9" + }, + "cpython-3.12.2-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 12, + "patch": 2, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "b4b4d19c36e86803aa0b4410395f5568bef28d82666efba926e44dbe06345a12" + }, + "cpython-3.12.2-windows-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "windows", + "libc": "none", + "major": 3, + "minor": 12, + "patch": 2, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + "sha256": "a1daf5e8ceb23d34ea29b16b5123b06694810fe7acc5c8384426435c63bf731e" + }, + "cpython-3.12.1-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, @@ -21,20 +109,20 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-aarch64-unknown-linux-gnu-debug-full.tar.zst", "sha256": "9009da24f436611d0bf086b8ea62aaed1c27104af5b770ddcfc92b60db06da8c" }, - "cpython-3.12.1-windows-i686-none": { + "cpython-3.12.1-macos-aarch64-none": { "name": "cpython", - "arch": "i686", - "os": "windows", + "arch": "aarch64", + "os": "macos", "libc": "none", "major": 3, "minor": 12, "patch": 1, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-i686-pc-windows-msvc-shared-pgo-full.tar.zst", - "sha256": "22866d35fdf58e90e75d6ba9aa78c288b452ea7041fa9bc5549eca9daa431883" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "61e51e3490537b800fcefad718157cf775de41044e95aa538b63ab599f66f3a9" }, - "cpython-3.12.1-linux-ppc64le-gnu": { + "cpython-3.12.1-linux-powerpc64le-gnu": { "name": "cpython", - "arch": "ppc64le", + "arch": "powerpc64le", "os": "linux", "libc": "gnu", "major": 3, @@ -54,16 +142,16 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-s390x-unknown-linux-gnu-debug-full.tar.zst", "sha256": "505a4fbace661a43b354a059022eb31efb406859a5f7227109ebf0f278f20503" }, - "cpython-3.12.1-darwin-x86_64-none": { + "cpython-3.12.1-windows-x86-none": { "name": "cpython", - "arch": "x86_64", - "os": "darwin", + "arch": "x86", + "os": "windows", "libc": "none", "major": 3, "minor": 12, "patch": 1, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "bf2b176b0426d7b4d4909c1b19bbb25b4893f9ebdc61e32df144df2b10dcc800" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + "sha256": "22866d35fdf58e90e75d6ba9aa78c288b452ea7041fa9bc5549eca9daa431883" }, "cpython-3.12.1-linux-x86_64-gnu": { "name": "cpython", @@ -87,6 +175,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-x86_64-unknown-linux-musl-lto-full.tar.zst", "sha256": "c4b07a02d8f0986b56e010a67132e5eeba1def4991c6c06ed184f831a484a06f" }, + "cpython-3.12.1-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 12, + "patch": 1, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "bf2b176b0426d7b4d4909c1b19bbb25b4893f9ebdc61e32df144df2b10dcc800" + }, "cpython-3.12.1-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -98,86 +197,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "d9bc1b566250bf51818976bf98bf50e1f4c59b2503b50d29250cac5ab5ef6b38" }, - "cpython-3.12.1-linux-x86_64_v2-gnu": { + "cpython-3.12.0-linux-aarch64-gnu": { "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 12, - "patch": 1, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "21a5182c499954bde10344c7cc3ba9f69a39f0b485a9420871bdf65f26587bb7" - }, - "cpython-3.12.1-linux-x86_64_v2-musl": { - "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 12, - "patch": 1, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-x86_64_v2-unknown-linux-musl-lto-full.tar.zst", - "sha256": "47a85a6f99be6ec1746e25a5002a1b942d26036b78f32f6c26ff1285196b2411" - }, - "cpython-3.12.1-linux-x86_64_v3-gnu": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 12, - "patch": 1, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "d2088f53a3e160973ec34376c5a8bc4f430626ea154a57a8ae868f37b43320f3" - }, - "cpython-3.12.1-linux-x86_64_v3-musl": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 12, - "patch": 1, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-x86_64_v3-unknown-linux-musl-lto-full.tar.zst", - "sha256": "24f76157c615f2cf6a92437290f711b27cd5b29bcea4f17b50ee83920f307e2a" - }, - "cpython-3.12.1-linux-x86_64_v4-gnu": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 12, - "patch": 1, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "b5c640ffdde33f3d333ed772878694c3be79caf5707de3da23aa8f77cfad4164" - }, - "cpython-3.12.1-linux-x86_64_v4-musl": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 12, - "patch": 1, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-x86_64_v4-unknown-linux-musl-lto-full.tar.zst", - "sha256": "6e521b73faee3f44161db17f8cd89599c54fbf28f28de215851f9b9e7ded8b75" - }, - "cpython-3.12.0-darwin-arm64-none": { - "name": "cpython", - "arch": "arm64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 12, - "patch": 0, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "25fc8cd41e975d18d13bcc8f8beffa096ff8a0b86c4a737e1c6617900092c966" - }, - "cpython-3.12.0-linux-arm64-gnu": { - "name": "cpython", - "arch": "arm64", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, @@ -186,20 +208,20 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-aarch64-unknown-linux-gnu-debug-full.tar.zst", "sha256": "eb05c976374a9a44596ce340ab35e5461014f30202c3cbe10edcbfbe5ac4a6a1" }, - "cpython-3.12.0-windows-i686-none": { + "cpython-3.12.0-macos-aarch64-none": { "name": "cpython", - "arch": "i686", - "os": "windows", + "arch": "aarch64", + "os": "macos", "libc": "none", "major": 3, "minor": 12, "patch": 0, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-i686-pc-windows-msvc-shared-pgo-full.tar.zst", - "sha256": "465e91b6e6d0d1c40c8a4bce3642c4adcb9b75cf03fbd5fd5a33a36358249289" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "25fc8cd41e975d18d13bcc8f8beffa096ff8a0b86c4a737e1c6617900092c966" }, - "cpython-3.12.0-linux-ppc64le-gnu": { + "cpython-3.12.0-linux-powerpc64le-gnu": { "name": "cpython", - "arch": "ppc64le", + "arch": "powerpc64le", "os": "linux", "libc": "gnu", "major": 3, @@ -219,16 +241,16 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-s390x-unknown-linux-gnu-debug-full.tar.zst", "sha256": "5b1a1effbb43df57ad014fcebf4b20089e504d89613e7b8db22d9ccb9fb00a6c" }, - "cpython-3.12.0-darwin-x86_64-none": { + "cpython-3.12.0-windows-x86-none": { "name": "cpython", - "arch": "x86_64", - "os": "darwin", + "arch": "x86", + "os": "windows", "libc": "none", "major": 3, "minor": 12, "patch": 0, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "3b4781e7fd4efabe574ba0954e54c35c7d5ac4dc5b2990b40796c1c6aec67d79" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + "sha256": "465e91b6e6d0d1c40c8a4bce3642c4adcb9b75cf03fbd5fd5a33a36358249289" }, "cpython-3.12.0-linux-x86_64-gnu": { "name": "cpython", @@ -252,6 +274,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-x86_64-unknown-linux-musl-lto-full.tar.zst", "sha256": "91b42595cb4b69ff396e746dc492caf67b952a3ed1a367a4ace1acc965ed9cdb" }, + "cpython-3.12.0-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 12, + "patch": 0, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "3b4781e7fd4efabe574ba0954e54c35c7d5ac4dc5b2990b40796c1c6aec67d79" + }, "cpython-3.12.0-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -263,86 +296,108 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "5bdff7ed56550d96f9b26a27a8c25f0cc58a03bff19e5f52bba84366183cab8b" }, - "cpython-3.12.0-linux-x86_64_v2-gnu": { + "cpython-3.11.8-linux-aarch64-gnu": { "name": "cpython", - "arch": "x86_64_v2", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, - "minor": 12, - "patch": 0, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "572f8559f0e8a086c4380ea4417d44f6f3751afd18d01e14e04099ec33e1b199" + "minor": 11, + "patch": 8, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "45bf082aca6b7d5e7261852720a72b92f5305e9fdb07b10f6588cb51d8f83ff2" }, - "cpython-3.12.0-linux-x86_64_v2-musl": { + "cpython-3.11.8-macos-aarch64-none": { "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 12, - "patch": 0, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-x86_64_v2-unknown-linux-musl-lto-full.tar.zst", - "sha256": "ec028edc5fcca34b5c5988074e44d91158d924695f26ca5885c6b4a1daa6349d" - }, - "cpython-3.12.0-linux-x86_64_v3-gnu": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 12, - "patch": 0, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "1ae35f54bad2b473298858a3cb7bf811291fdd40c8159eff4ff593168ed8765e" - }, - "cpython-3.12.0-linux-x86_64_v3-musl": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 12, - "patch": 0, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-x86_64_v3-unknown-linux-musl-lto-full.tar.zst", - "sha256": "154cc904e028f93fa5b6dfb23aa3d9241db5c210c99a8facab9425e6a5f54a31" - }, - "cpython-3.12.0-linux-x86_64_v4-gnu": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 12, - "patch": 0, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "3c900c3495453cfa7e7626026ef0d8be3adf589b2b810969f6a9f44dba3c129d" - }, - "cpython-3.12.0-linux-x86_64_v4-musl": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 12, - "patch": 0, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-x86_64_v4-unknown-linux-musl-lto-full.tar.zst", - "sha256": "7f850e95a29b43dff3656e3d403d98c30049a3a90a74aac1c6763339c6d26632" - }, - "cpython-3.11.7-darwin-arm64-none": { - "name": "cpython", - "arch": "arm64", - "os": "darwin", + "arch": "aarch64", + "os": "macos", "libc": "none", "major": 3, "minor": 11, - "patch": 7, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "c1f3dd13825906a5eae23ed8de9b653edb620568b2e0226eef3784eb1cce7eed" + "patch": 8, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "c0650884b929253b8688797d1955850f6e339bf0428b3d935f62ab3159f66362" }, - "cpython-3.11.7-linux-arm64-gnu": { + "cpython-3.11.8-linux-powerpc64le-gnu": { "name": "cpython", - "arch": "arm64", + "arch": "powerpc64le", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 8, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "a9716f2eebebe03de47d6d5d603d6ff78abf5eb38f88bf7607b17fd85e74ff16" + }, + "cpython-3.11.8-linux-s390x-gnu": { + "name": "cpython", + "arch": "s390x", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 8, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "d495830b5980ed689bd7588aa556bac9c43ff766d8a8b32e7791b8ed664b04f3" + }, + "cpython-3.11.8-windows-x86-none": { + "name": "cpython", + "arch": "x86", + "os": "windows", + "libc": "none", + "major": 3, + "minor": 11, + "patch": 8, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + "sha256": "c3e90962996177a027bd73dd9fd8c42a2d6ef832cda26db4ab4efc6105160537" + }, + "cpython-3.11.8-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 8, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "d959c43184878d564b5368ce4d753cf059600aafdf3e50280e850f94b5a4ba61" + }, + "cpython-3.11.8-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 11, + "patch": 8, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-x86_64-unknown-linux-musl-lto-full.tar.zst", + "sha256": "a03a9d8c1f770ce418716a2e8185df7b3a9e0012cdc220f9f2d24480a432650b" + }, + "cpython-3.11.8-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 11, + "patch": 8, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "54f8c8ad7313b3505e495bb093825d85eab244306ca4278836a2c7b5b74fb053" + }, + "cpython-3.11.8-windows-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "windows", + "libc": "none", + "major": 3, + "minor": 11, + "patch": 8, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + "sha256": "6da82390f7ac49f6c4b19a5b8019c4ddc1eef2c5ad6a2f2d32773a27663a4e14" + }, + "cpython-3.11.7-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, @@ -351,20 +406,20 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-aarch64-unknown-linux-gnu-debug-full.tar.zst", "sha256": "e3a375f8f16198ccf8dbede231536544265e5b4b6b0f0df97c5b29503c5864e2" }, - "cpython-3.11.7-windows-i686-none": { + "cpython-3.11.7-macos-aarch64-none": { "name": "cpython", - "arch": "i686", - "os": "windows", + "arch": "aarch64", + "os": "macos", "libc": "none", "major": 3, "minor": 11, "patch": 7, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-i686-pc-windows-msvc-shared-pgo-full.tar.zst", - "sha256": "6613f1f9238d19969d8a2827deec84611cb772503207056cc9f0deb89bea48cd" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "c1f3dd13825906a5eae23ed8de9b653edb620568b2e0226eef3784eb1cce7eed" }, - "cpython-3.11.7-linux-ppc64le-gnu": { + "cpython-3.11.7-linux-powerpc64le-gnu": { "name": "cpython", - "arch": "ppc64le", + "arch": "powerpc64le", "os": "linux", "libc": "gnu", "major": 3, @@ -384,16 +439,16 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-s390x-unknown-linux-gnu-debug-full.tar.zst", "sha256": "91b33369025b7e0079f603cd2a99f9a5932daa8ded113d5090f29c075c993df7" }, - "cpython-3.11.7-darwin-x86_64-none": { + "cpython-3.11.7-windows-x86-none": { "name": "cpython", - "arch": "x86_64", - "os": "darwin", + "arch": "x86", + "os": "windows", "libc": "none", "major": 3, "minor": 11, "patch": 7, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "3f8caf73f2bfe22efa9666974c119727e163716e88af8ed3caa1e0ae5493de61" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + "sha256": "6613f1f9238d19969d8a2827deec84611cb772503207056cc9f0deb89bea48cd" }, "cpython-3.11.7-linux-x86_64-gnu": { "name": "cpython", @@ -417,6 +472,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-x86_64-unknown-linux-musl-lto-full.tar.zst", "sha256": "f387d373d64447bbba8a5657712f93b1dbdfd7246cdfe5a0493f39b83d46ec7c" }, + "cpython-3.11.7-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 11, + "patch": 7, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "3f8caf73f2bfe22efa9666974c119727e163716e88af8ed3caa1e0ae5493de61" + }, "cpython-3.11.7-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -428,86 +494,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "89d1d8f080e5494ea57918fc5ecf3d483ffef943cd5a336e64da150cd44b4aa0" }, - "cpython-3.11.7-linux-x86_64_v2-gnu": { + "cpython-3.11.6-linux-aarch64-gnu": { "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 11, - "patch": 7, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "24dba70107ca3999c0a2742b3bf898f740a063736f3cd208e80e056adf19cd7f" - }, - "cpython-3.11.7-linux-x86_64_v2-musl": { - "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 11, - "patch": 7, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-x86_64_v2-unknown-linux-musl-lto-full.tar.zst", - "sha256": "a6c43c58ff21316242bd43efa90837f4a344e64ba4f4306c5ddc49005bfc1dd3" - }, - "cpython-3.11.7-linux-x86_64_v3-gnu": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 11, - "patch": 7, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "6f5246b7cb8cc36a98025c70f829d2e5d197a7d20d51f21ca44b1e4242b13f0d" - }, - "cpython-3.11.7-linux-x86_64_v3-musl": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 11, - "patch": 7, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-x86_64_v3-unknown-linux-musl-lto-full.tar.zst", - "sha256": "23af703408cb9a91eb2e673640576524ba616d8d1f8af079d22e67192e174400" - }, - "cpython-3.11.7-linux-x86_64_v4-gnu": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 11, - "patch": 7, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "74e5053f3f40d4ea018d79139d8a739c0ab0d457b8a9f1597a160bd88fbd58ab" - }, - "cpython-3.11.7-linux-x86_64_v4-musl": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 11, - "patch": 7, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-x86_64_v4-unknown-linux-musl-lto-full.tar.zst", - "sha256": "63d3cd05a6c9e7597d1c2ec3d02473170a4f2eb4acf5558974c639cf787a44a1" - }, - "cpython-3.11.6-darwin-arm64-none": { - "name": "cpython", - "arch": "arm64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 11, - "patch": 6, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "6e9007bcbbf51203e89c34a87ed42561630a35bc4eb04a565c92ba7159fe5826" - }, - "cpython-3.11.6-linux-arm64-gnu": { - "name": "cpython", - "arch": "arm64", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, @@ -516,20 +505,20 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-aarch64-unknown-linux-gnu-debug-full.tar.zst", "sha256": "d63d6eb065e60899b25853fe6bbd9f60ea6c3b12f4854adc75cb818bad55f4e9" }, - "cpython-3.11.6-windows-i686-none": { + "cpython-3.11.6-macos-aarch64-none": { "name": "cpython", - "arch": "i686", - "os": "windows", + "arch": "aarch64", + "os": "macos", "libc": "none", "major": 3, "minor": 11, "patch": 6, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-i686-pc-windows-msvc-shared-pgo-full.tar.zst", - "sha256": "2670731428191d4476bf260c8144ccf06f9e5f8ac6f2de1dc444ca96ab627082" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "6e9007bcbbf51203e89c34a87ed42561630a35bc4eb04a565c92ba7159fe5826" }, - "cpython-3.11.6-linux-ppc64le-gnu": { + "cpython-3.11.6-linux-powerpc64le-gnu": { "name": "cpython", - "arch": "ppc64le", + "arch": "powerpc64le", "os": "linux", "libc": "gnu", "major": 3, @@ -549,16 +538,16 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-s390x-unknown-linux-gnu-debug-full.tar.zst", "sha256": "78252aa883fed18de7bb9b146450e42dd75d78c345f56c1301bb042317a1d4f7" }, - "cpython-3.11.6-darwin-x86_64-none": { + "cpython-3.11.6-windows-x86-none": { "name": "cpython", - "arch": "x86_64", - "os": "darwin", + "arch": "x86", + "os": "windows", "libc": "none", "major": 3, "minor": 11, "patch": 6, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "3685156e4139e89484c071ba1a1b85be0b4e302a786de5a170d3b0713863c2e8" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + "sha256": "2670731428191d4476bf260c8144ccf06f9e5f8ac6f2de1dc444ca96ab627082" }, "cpython-3.11.6-linux-x86_64-gnu": { "name": "cpython", @@ -582,6 +571,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-x86_64-unknown-linux-musl-lto-full.tar.zst", "sha256": "1b6e32ec93c5a18a03a9da9e2a3a3738d67b733df0795edcff9fd749c33ab931" }, + "cpython-3.11.6-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 11, + "patch": 6, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "3685156e4139e89484c071ba1a1b85be0b4e302a786de5a170d3b0713863c2e8" + }, "cpython-3.11.6-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -593,86 +593,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "38d2c2fa2f9effbf486207bef7141d1b5c385ad30729ab0c976e6a852a2a9401" }, - "cpython-3.11.6-linux-x86_64_v2-gnu": { + "cpython-3.11.5-linux-aarch64-gnu": { "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 11, - "patch": 6, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "2ef8df0de9c400b5d57971fe5bcff36b4dca2410504a9edbd407572ea61044e0" - }, - "cpython-3.11.6-linux-x86_64_v2-musl": { - "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 11, - "patch": 6, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-x86_64_v2-unknown-linux-musl-lto-full.tar.zst", - "sha256": "a626529e7ebe28755090d16d22fc3f8b85dd7d19ab9791d224336f002b9b51a5" - }, - "cpython-3.11.6-linux-x86_64_v3-gnu": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 11, - "patch": 6, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "d96c26d88966873184fc0ee99ca7b941d274b669b1b11e185749fc065d12908f" - }, - "cpython-3.11.6-linux-x86_64_v3-musl": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 11, - "patch": 6, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-x86_64_v3-unknown-linux-musl-lto-full.tar.zst", - "sha256": "cec6af027d49c6218680d78960392c4853d74d2f0244ac374ed216ed57f3a79b" - }, - "cpython-3.11.6-linux-x86_64_v4-gnu": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 11, - "patch": 6, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "865506f3eb1ff9a25f458c1b46d4fe6ceffac869ca01c203c258e3563c87630e" - }, - "cpython-3.11.6-linux-x86_64_v4-musl": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 11, - "patch": 6, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-x86_64_v4-unknown-linux-musl-lto-full.tar.zst", - "sha256": "f6fb43020a46b6f82c552f95db1170c15139d2b6542d38e08ea915902e9da961" - }, - "cpython-3.11.5-darwin-arm64-none": { - "name": "cpython", - "arch": "arm64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 11, - "patch": 5, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "7bee180b764722a73c2599fbe2c3a6121cf6bbcb08cb3082851e93c43fe130e7" - }, - "cpython-3.11.5-linux-arm64-gnu": { - "name": "cpython", - "arch": "arm64", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, @@ -681,31 +604,20 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-aarch64-unknown-linux-gnu-debug-full.tar.zst", "sha256": "ac4b1e91d1cb7027595bfa4667090406331b291b2e346fb74e42b7031b216787" }, - "cpython-3.11.5-linux-i686-gnu": { + "cpython-3.11.5-macos-aarch64-none": { "name": "cpython", - "arch": "i686", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 11, - "patch": 5, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-i686-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "75d27b399b323c25d8250fda9857e388bf1b03ba1eb7925ec23cf12042a63a88" - }, - "cpython-3.11.5-windows-i686-none": { - "name": "cpython", - "arch": "i686", - "os": "windows", + "arch": "aarch64", + "os": "macos", "libc": "none", "major": 3, "minor": 11, "patch": 5, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-i686-pc-windows-msvc-shared-pgo-full.tar.zst", - "sha256": "c9ffe9c2c88685ce3064f734cbdfede0a07de7d826fada58f8045f3bd8f81a9d" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "7bee180b764722a73c2599fbe2c3a6121cf6bbcb08cb3082851e93c43fe130e7" }, - "cpython-3.11.5-linux-ppc64le-gnu": { + "cpython-3.11.5-linux-powerpc64le-gnu": { "name": "cpython", - "arch": "ppc64le", + "arch": "powerpc64le", "os": "linux", "libc": "gnu", "major": 3, @@ -725,16 +637,27 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-s390x-unknown-linux-gnu-debug-full.tar.zst", "sha256": "b0819032ec336d6e1d9e9bfdba546bf854a7b7248f8720a6d07da72c4ac927e5" }, - "cpython-3.11.5-darwin-x86_64-none": { + "cpython-3.11.5-linux-x86-gnu": { "name": "cpython", - "arch": "x86_64", - "os": "darwin", + "arch": "x86", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 5, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-i686-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "75d27b399b323c25d8250fda9857e388bf1b03ba1eb7925ec23cf12042a63a88" + }, + "cpython-3.11.5-windows-x86-none": { + "name": "cpython", + "arch": "x86", + "os": "windows", "libc": "none", "major": 3, "minor": 11, "patch": 5, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "e43d70a49919641ca2939a5a9107b13d5fef8c13af0f511a33a94bb6af2044f0" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + "sha256": "c9ffe9c2c88685ce3064f734cbdfede0a07de7d826fada58f8045f3bd8f81a9d" }, "cpython-3.11.5-linux-x86_64-gnu": { "name": "cpython", @@ -758,6 +681,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-x86_64-unknown-linux-musl-lto-full.tar.zst", "sha256": "9dcf19ee54fb936cb9fd0f02fd655e790663534bc12e142e460c1b30a0b54dbd" }, + "cpython-3.11.5-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 11, + "patch": 5, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "e43d70a49919641ca2939a5a9107b13d5fef8c13af0f511a33a94bb6af2044f0" + }, "cpython-3.11.5-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -769,86 +703,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "6e4d20e6d498f9edeb3c28cb9541ad20f675f16da350b078e40a9dcfd93cdc3d" }, - "cpython-3.11.5-linux-x86_64_v2-gnu": { + "cpython-3.11.4-linux-aarch64-gnu": { "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 11, - "patch": 5, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "d7f5abc89c66a8a1d086394c80a94a17b5b26887983dbf5a80998302f3626ab2" - }, - "cpython-3.11.5-linux-x86_64_v2-musl": { - "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 11, - "patch": 5, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-x86_64_v2-unknown-linux-musl-lto-full.tar.zst", - "sha256": "bbd36a2ce9896b3f9c3fd69c8d58477bf34a08bc92276ea4ca90551071017cae" - }, - "cpython-3.11.5-linux-x86_64_v3-gnu": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 11, - "patch": 5, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "292c37355054d6f4f864b4e772d8bd23541b744d73b3293523f461dcfcad2750" - }, - "cpython-3.11.5-linux-x86_64_v3-musl": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 11, - "patch": 5, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-x86_64_v3-unknown-linux-musl-lto-full.tar.zst", - "sha256": "9d697a4832d03c7c41bcde53a90d606af7804e4c142a2882e8e237fa628eecf0" - }, - "cpython-3.11.5-linux-x86_64_v4-gnu": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 11, - "patch": 5, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "dfa7e0d8fbcde146c5a952f8fa8a1a1121d4b64e7e9e0153d81a06d149a101d3" - }, - "cpython-3.11.5-linux-x86_64_v4-musl": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 11, - "patch": 5, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-x86_64_v4-unknown-linux-musl-lto-full.tar.zst", - "sha256": "0c10a26ab61cff71fbd64952c669d414f0fa050c8a77de128d2bbf6eec240a32" - }, - "cpython-3.11.4-darwin-arm64-none": { - "name": "cpython", - "arch": "arm64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 11, - "patch": 4, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "988d476c806f71a3233ff4266eda166a5d28cf83ba306ac88b4220554fc83e8c" - }, - "cpython-3.11.4-linux-arm64-gnu": { - "name": "cpython", - "arch": "arm64", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, @@ -857,31 +714,20 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-aarch64-unknown-linux-gnu-debug-full.tar.zst", "sha256": "37cf00439b57adf7ffef4a349d62dcf09739ba67b670e903b00b25f81fbb8a68" }, - "cpython-3.11.4-linux-i686-gnu": { + "cpython-3.11.4-macos-aarch64-none": { "name": "cpython", - "arch": "i686", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 11, - "patch": 4, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-i686-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "a9051364b5c2e28205f8484cae03d16c86b45df5d117324e846d0f5e870fe9fb" - }, - "cpython-3.11.4-windows-i686-none": { - "name": "cpython", - "arch": "i686", - "os": "windows", + "arch": "aarch64", + "os": "macos", "libc": "none", "major": 3, "minor": 11, "patch": 4, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-i686-pc-windows-msvc-shared-pgo-full.tar.zst", - "sha256": "0d22f43c5bb3f27ff2f9e8c60b0d7abd391bb2cac1790b0960970ff5580f6e9a" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "988d476c806f71a3233ff4266eda166a5d28cf83ba306ac88b4220554fc83e8c" }, - "cpython-3.11.4-linux-ppc64le-gnu": { + "cpython-3.11.4-linux-powerpc64le-gnu": { "name": "cpython", - "arch": "ppc64le", + "arch": "powerpc64le", "os": "linux", "libc": "gnu", "major": 3, @@ -901,16 +747,27 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-s390x-unknown-linux-gnu-debug-full.tar.zst", "sha256": "8ef6b5fa86b4abf51865b346b7cf8df36e474ed308869fc0ac3fe82de39194a4" }, - "cpython-3.11.4-darwin-x86_64-none": { + "cpython-3.11.4-linux-x86-gnu": { "name": "cpython", - "arch": "x86_64", - "os": "darwin", + "arch": "x86", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 4, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-i686-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "a9051364b5c2e28205f8484cae03d16c86b45df5d117324e846d0f5e870fe9fb" + }, + "cpython-3.11.4-windows-x86-none": { + "name": "cpython", + "arch": "x86", + "os": "windows", "libc": "none", "major": 3, "minor": 11, "patch": 4, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "6d9765785316c7f1c07def71b413c92c84302f798b30ee09e2e0b5da28353a51" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + "sha256": "0d22f43c5bb3f27ff2f9e8c60b0d7abd391bb2cac1790b0960970ff5580f6e9a" }, "cpython-3.11.4-linux-x86_64-gnu": { "name": "cpython", @@ -934,6 +791,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-x86_64-unknown-linux-musl-lto-full.tar.zst", "sha256": "fc2ea02ced875c90b8d025b409d58c4f045df8ba951bfa2b8b0a3cfe11c3b41c" }, + "cpython-3.11.4-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 11, + "patch": 4, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "6d9765785316c7f1c07def71b413c92c84302f798b30ee09e2e0b5da28353a51" + }, "cpython-3.11.4-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -945,86 +813,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "1692d795d6199b2261161ae54250009ffad0317929302903f6f2c773befd4d76" }, - "cpython-3.11.4-linux-x86_64_v2-gnu": { + "cpython-3.11.3-linux-aarch64-gnu": { "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 11, - "patch": 4, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "95ac78a3d834ce1feaa45bfe4997563df0bc65c3d2711a5d6d4f39d3c240ffd4" - }, - "cpython-3.11.4-linux-x86_64_v2-musl": { - "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 11, - "patch": 4, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-x86_64_v2-unknown-linux-musl-lto-full.tar.zst", - "sha256": "54f9206357c118b4ea26f0e3ebe9a558153fff8c42ff4c8546d4c8464956401b" - }, - "cpython-3.11.4-linux-x86_64_v3-gnu": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 11, - "patch": 4, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "85859b2993d3c52506625e1dc62b0cb4d9e38816e290d7630fe258aae6b8bca4" - }, - "cpython-3.11.4-linux-x86_64_v3-musl": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 11, - "patch": 4, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-x86_64_v3-unknown-linux-musl-lto-full.tar.zst", - "sha256": "b95e54792e8db7531a1ad23d574a0fb4c8510f75d3102b2d41386460345c5383" - }, - "cpython-3.11.4-linux-x86_64_v4-gnu": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 11, - "patch": 4, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "a3100464c5f1bc3beb1e15c2bf01ad1aff3888d20a25a1ded1a5baff7be54ff1" - }, - "cpython-3.11.4-linux-x86_64_v4-musl": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 11, - "patch": 4, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-x86_64_v4-unknown-linux-musl-lto-full.tar.zst", - "sha256": "7e346bf5a96e3719f75889b98baf02e38dd1be812be261e5e5867d357f4f5eab" - }, - "cpython-3.11.3-darwin-arm64-none": { - "name": "cpython", - "arch": "arm64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 11, - "patch": 3, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "cd296d628ceebf55a78c7f6a7aed379eba9dbd72045d002e1c2c85af0d6f5049" - }, - "cpython-3.11.3-linux-arm64-gnu": { - "name": "cpython", - "arch": "arm64", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, @@ -1033,31 +824,20 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-aarch64-unknown-linux-gnu-debug-full.tar.zst", "sha256": "991521082b0347878ba855c4986d77cc805c22ef75159bc95dd24bfd80275e27" }, - "cpython-3.11.3-linux-i686-gnu": { + "cpython-3.11.3-macos-aarch64-none": { "name": "cpython", - "arch": "i686", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 11, - "patch": 3, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-i686-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "7bd694eb848328e96f524ded0f9b9eca6230d71fce3cd49b335a5c33450f3e04" - }, - "cpython-3.11.3-windows-i686-none": { - "name": "cpython", - "arch": "i686", - "os": "windows", + "arch": "aarch64", + "os": "macos", "libc": "none", "major": 3, "minor": 11, "patch": 3, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-i686-pc-windows-msvc-shared-pgo-full.tar.zst", - "sha256": "877c90ef778a526aa25ab417034f5e70728ac14e5eb1fa5cfd741f531203a3fc" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "cd296d628ceebf55a78c7f6a7aed379eba9dbd72045d002e1c2c85af0d6f5049" }, - "cpython-3.11.3-linux-ppc64le-gnu": { + "cpython-3.11.3-linux-powerpc64le-gnu": { "name": "cpython", - "arch": "ppc64le", + "arch": "powerpc64le", "os": "linux", "libc": "gnu", "major": 3, @@ -1066,16 +846,27 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-ppc64le-unknown-linux-gnu-debug-full.tar.zst", "sha256": "241d583be3ecc34d76fafa0d186cb504ce5625eb2c0e895dc4f4073a649e5c73" }, - "cpython-3.11.3-darwin-x86_64-none": { + "cpython-3.11.3-linux-x86-gnu": { "name": "cpython", - "arch": "x86_64", - "os": "darwin", + "arch": "x86", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 3, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-i686-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "7bd694eb848328e96f524ded0f9b9eca6230d71fce3cd49b335a5c33450f3e04" + }, + "cpython-3.11.3-windows-x86-none": { + "name": "cpython", + "arch": "x86", + "os": "windows", "libc": "none", "major": 3, "minor": 11, "patch": 3, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "2fbb31a8bc6663e2d31d3054319b51a29b1915c03222a94b9d563233e11d1bef" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + "sha256": "877c90ef778a526aa25ab417034f5e70728ac14e5eb1fa5cfd741f531203a3fc" }, "cpython-3.11.3-linux-x86_64-gnu": { "name": "cpython", @@ -1099,6 +890,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-x86_64-unknown-linux-musl-lto-full.tar.zst", "sha256": "8c5adef5bc627f39e93b920af86ef740e917aa698530ff727978d446a07bbd8b" }, + "cpython-3.11.3-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 11, + "patch": 3, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "2fbb31a8bc6663e2d31d3054319b51a29b1915c03222a94b9d563233e11d1bef" + }, "cpython-3.11.3-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -1110,86 +912,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "9d27e607fb1cb2d766e17f27853013d8c0f0b09ac53127aaff03ec89ab13370d" }, - "cpython-3.11.3-linux-x86_64_v2-gnu": { + "cpython-3.11.1-linux-aarch64-gnu": { "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 11, - "patch": 3, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "f42165d39624cd664ad2c77401b03f2b86c1eaa346a8d97595dc3da9477f7ba7" - }, - "cpython-3.11.3-linux-x86_64_v2-musl": { - "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 11, - "patch": 3, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-x86_64_v2-unknown-linux-musl-lto-full.tar.zst", - "sha256": "da2b6278ba05879dfa1929c38260d70ee839425fa175f253417370c97974f679" - }, - "cpython-3.11.3-linux-x86_64_v3-gnu": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 11, - "patch": 3, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "9b049c8397cb33d8ee8ce810f971569dbeddc058325120dbc9463efd05fd97f4" - }, - "cpython-3.11.3-linux-x86_64_v3-musl": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 11, - "patch": 3, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-x86_64_v3-unknown-linux-musl-lto-full.tar.zst", - "sha256": "ba81489c9aa454d8c76c4a0cc12c1a90c66f677f5b4eebc62348a7575983a49b" - }, - "cpython-3.11.3-linux-x86_64_v4-gnu": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 11, - "patch": 3, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "8977784dad18e495cfaaaffa4d3196cba76ddcb6ba665375a3c8d707267478b5" - }, - "cpython-3.11.3-linux-x86_64_v4-musl": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 11, - "patch": 3, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-x86_64_v4-unknown-linux-musl-lto-full.tar.zst", - "sha256": "b985b9b3b57ec919272a01706fb85eef59d4877f6970419160fdf8cfffb4caa8" - }, - "cpython-3.11.1-darwin-arm64-none": { - "name": "cpython", - "arch": "arm64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 11, - "patch": 1, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "da187194cc351d827232b1d2d85b2855d7e25a4ada3e47bc34b4f87b1d989be5" - }, - "cpython-3.11.1-linux-arm64-gnu": { - "name": "cpython", - "arch": "arm64", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, @@ -1198,9 +923,20 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-aarch64-unknown-linux-gnu-debug-full.tar.zst", "sha256": "8fe27d850c02aa7bb34088fad5b48df90b4b841f40e1472243b8ab9da8776e40" }, - "cpython-3.11.1-linux-i686-gnu": { + "cpython-3.11.1-macos-aarch64-none": { "name": "cpython", - "arch": "i686", + "arch": "aarch64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 11, + "patch": 1, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "da187194cc351d827232b1d2d85b2855d7e25a4ada3e47bc34b4f87b1d989be5" + }, + "cpython-3.11.1-linux-x86-gnu": { + "name": "cpython", + "arch": "x86", "os": "linux", "libc": "gnu", "major": 3, @@ -1209,9 +945,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-i686-unknown-linux-gnu-debug-full.tar.zst", "sha256": "7986ebe82c07ecd2eb94fd1b3c9ebbb2366db2360e38f29ae0543e857551d0bf" }, - "cpython-3.11.1-windows-i686-none": { + "cpython-3.11.1-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -1220,17 +956,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-i686-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "b062ac2c72a85510fb9300675bd5c716baede21e9482ef6335247b4aa006584c" }, - "cpython-3.11.1-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 11, - "patch": 1, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "0eb61be53ee13cf75a30b8a164ef513a2c7995b25b118a3a503245d46231b13a" - }, "cpython-3.11.1-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -1253,6 +978,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-x86_64-unknown-linux-musl-lto-full.tar.zst", "sha256": "ec5da5b428f6d91d96cde2621c0380f67bb96e4257d2628bc70b50e75ec5f629" }, + "cpython-3.11.1-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 11, + "patch": 1, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "0eb61be53ee13cf75a30b8a164ef513a2c7995b25b118a3a503245d46231b13a" + }, "cpython-3.11.1-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -1264,126 +1000,38 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "f5c46fffda7d7894b975af728f739b02d1cec50fd4a3ea49f69de9ceaae74b17" }, - "cpython-3.11.1-linux-x86_64_v2-gnu": { + "cpython-3.10.13-linux-aarch64-gnu": { "name": "cpython", - "arch": "x86_64_v2", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, - "minor": 11, - "patch": 1, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "ea2d1de07fbd276723d61cb9f3c8fe4415f1207b3351593a6985e8e4338e89e0" + "minor": 10, + "patch": 13, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "06a53040504e1e2fdcb32dc0d61b123bea76725b5c14031c8f64e28f52ae5a5f" }, - "cpython-3.11.1-linux-x86_64_v2-musl": { + "cpython-3.10.13-macos-aarch64-none": { "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 11, - "patch": 1, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-x86_64_v2-unknown-linux-musl-lto-full.tar.zst", - "sha256": "8df1c3ff768c308b113e847e64726b5743c45fde3292b3ad60ffbd40e15b4ac7" - }, - "cpython-3.11.1-linux-x86_64_v3-gnu": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 11, - "patch": 1, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "be1259db03ae12ca8c8cdc1a75a3f4aa47579725f2e62f71022f6049690b6498" - }, - "cpython-3.11.1-linux-x86_64_v3-musl": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 11, - "patch": 1, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-x86_64_v3-unknown-linux-musl-lto-full.tar.zst", - "sha256": "c4f843cf506e3d7e230e2842f2b8e4e8f470ff3c8707e0b70d0d1285bb938986" - }, - "cpython-3.11.1-linux-x86_64_v4-gnu": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 11, - "patch": 1, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "abf6c9a813e3c600b095ccfe0fb8c21e2b4156df342e9cf4ea34cb5759a0ff1c" - }, - "cpython-3.11.1-linux-x86_64_v4-musl": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 11, - "patch": 1, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-x86_64_v4-unknown-linux-musl-lto-full.tar.zst", - "sha256": "9524d8988d552ee6be87bbda76b38431e43c68099fa1ff98368a4fea634e78ff" - }, - "cpython-3.10.13-darwin-arm64-none": { - "name": "cpython", - "arch": "arm64", - "os": "darwin", + "arch": "aarch64", + "os": "macos", "libc": "none", "major": 3, "minor": 10, "patch": 13, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.10.13%2B20240107-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "d1a777a0688bafd2a62050c680508769d9b6c14779f64fee591f4e135c11e711" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "57b83a4aa32bdbe7611f1290313ef24f2574dff5fa59181c0ccb26c14c688b73" }, - "cpython-3.10.13-linux-arm64-gnu": { + "cpython-3.10.13-linux-powerpc64le-gnu": { "name": "cpython", - "arch": "arm64", + "arch": "powerpc64le", "os": "linux", "libc": "gnu", "major": 3, "minor": 10, "patch": 13, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.10.13%2B20240107-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "2927269de5d39b935285b676154793877102d6528a1302bab5d58c2cfbf848d9" - }, - "cpython-3.10.13-linux-i686-gnu": { - "name": "cpython", - "arch": "i686", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 13, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.10.13%2B20230826-i686-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "08a3a1ff61b7ed2c87db7a9f88630781d98fabc2efb499f38ae0ead05973eb56" - }, - "cpython-3.10.13-windows-i686-none": { - "name": "cpython", - "arch": "i686", - "os": "windows", - "libc": "none", - "major": 3, - "minor": 10, - "patch": 13, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.10.13%2B20240107-i686-pc-windows-msvc-shared-pgo-full.tar.zst", - "sha256": "0e2e96365d06411a78bc1e1b19cc5a148034743fe6ecf5a5c8e890985fcadbb4" - }, - "cpython-3.10.13-linux-ppc64le-gnu": { - "name": "cpython", - "arch": "ppc64le", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 13, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.10.13%2B20240107-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "24d5acc86495b15ba4b907d151ae62dfbcf2dbe61313b448f470aa333c8a9793" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "cab4c8756445d1d1987c7c94d3bcf323684e44fb9070329d8287d4c38e155711" }, "cpython-3.10.13-linux-s390x-gnu": { "name": "cpython", @@ -1393,19 +1041,30 @@ "major": 3, "minor": 10, "patch": 13, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.10.13%2B20240107-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "9dd2dc8f5bb6d011561a83577d4ca5d7ffcb0cd1aeecae32c8ba378eb77b2819" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "fe46914541126297c7a8636845c2e7188868eaa617bb6e293871fca4a5cb63f7" }, - "cpython-3.10.13-darwin-x86_64-none": { + "cpython-3.10.13-linux-x86-gnu": { "name": "cpython", - "arch": "x86_64", - "os": "darwin", + "arch": "x86", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 13, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.10.13%2B20230826-i686-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "08a3a1ff61b7ed2c87db7a9f88630781d98fabc2efb499f38ae0ead05973eb56" + }, + "cpython-3.10.13-windows-x86-none": { + "name": "cpython", + "arch": "x86", + "os": "windows", "libc": "none", "major": 3, "minor": 10, "patch": 13, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.10.13%2B20240107-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "b61f6f9cf0c35fd6df90b424e757a3bc1b483e8f8d8fadfa6c1ddd1a0c39c003" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + "sha256": "c8b99dcf267c574fdfbdf4e9d63ec7a4aa4608565fee3fba0b2f73843b9713b2" }, "cpython-3.10.13-linux-x86_64-gnu": { "name": "cpython", @@ -1415,8 +1074,8 @@ "major": 3, "minor": 10, "patch": 13, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.10.13%2B20240107-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "fade5ea8fab973421e2e721d5ea4a6fa908db56e74b8af2bf3b4f4ce10b28aeb" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "41b20e9d87f57d27f608685b714a57eea81c9e079aa647d59837ec6659536626" }, "cpython-3.10.13-linux-x86_64-musl": { "name": "cpython", @@ -1426,8 +1085,19 @@ "major": 3, "minor": 10, "patch": 13, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.10.13%2B20240107-x86_64-unknown-linux-musl-lto-full.tar.zst", - "sha256": "95f66cf891eb474fb1904aa63e1e6f800238f7737269a21d933912cd26cbf816" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-x86_64-unknown-linux-musl-lto-full.tar.zst", + "sha256": "fd18e6039be25bf23d13caf5140569df71d61312b823b715b3c788747fec48e9" + }, + "cpython-3.10.13-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 10, + "patch": 13, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "a41c1e28e2a646bac69e023873d40a43c5958d251c6adfa83d5811a7cb034c7a" }, "cpython-3.10.13-windows-x86_64-none": { "name": "cpython", @@ -1437,89 +1107,12 @@ "major": 3, "minor": 10, "patch": 13, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.10.13%2B20240107-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", - "sha256": "8271db063eea7a32f327121b4d828bd10b9ecd1447d01fcfe8c7518e587ede63" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + "sha256": "6a2c8f37509556e5d463b1f437cdf7772ebd84cdf183c258d783e64bb3109505" }, - "cpython-3.10.13-linux-x86_64_v2-gnu": { + "cpython-3.10.12-linux-aarch64-gnu": { "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 13, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.10.13%2B20240107-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "a13433753e7a4d28205df17c76e46481d7e74d9cfef61e2a10764903e5c11eb9" - }, - "cpython-3.10.13-linux-x86_64_v2-musl": { - "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 10, - "patch": 13, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.10.13%2B20240107-x86_64_v2-unknown-linux-musl-lto-full.tar.zst", - "sha256": "6d020f3a2a61f9112e128364cec7d19c2aa2b26664f411ddc639712be552fd6b" - }, - "cpython-3.10.13-linux-x86_64_v3-gnu": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 13, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.10.13%2B20240107-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "e183e76dd20bd89a2f2510939681987fa3964003bdaee5091533b61d5bd8ec8d" - }, - "cpython-3.10.13-linux-x86_64_v3-musl": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 10, - "patch": 13, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.10.13%2B20240107-x86_64_v3-unknown-linux-musl-lto-full.tar.zst", - "sha256": "3adc05761f244f836993c0eca2312cf65d21b8a86103d73129a5cb9233ba1116" - }, - "cpython-3.10.13-linux-x86_64_v4-gnu": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 13, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.10.13%2B20240107-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "bc4f0719037ed1fb84439fbf89c640859b841575f6804ef88dd969613a7c4980" - }, - "cpython-3.10.13-linux-x86_64_v4-musl": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 10, - "patch": 13, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.10.13%2B20240107-x86_64_v4-unknown-linux-musl-lto-full.tar.zst", - "sha256": "d29ee6b971179f13976559239e5741a3d7898360fd2591cc657a92ef1bfa4aa9" - }, - "cpython-3.10.12-darwin-arm64-none": { - "name": "cpython", - "arch": "arm64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 10, - "patch": 12, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "a7d0cadbe867cc53dd47d7327244154157a7cca02edb88cf3bb760a4f91d4e44" - }, - "cpython-3.10.12-linux-arm64-gnu": { - "name": "cpython", - "arch": "arm64", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, @@ -1528,31 +1121,20 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-aarch64-unknown-linux-gnu-debug-full.tar.zst", "sha256": "e30f2b4fd9bd79b9122e2975f3c17c9ddd727f8326b2e246378e81f7ecc7d74f" }, - "cpython-3.10.12-linux-i686-gnu": { + "cpython-3.10.12-macos-aarch64-none": { "name": "cpython", - "arch": "i686", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 12, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-i686-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "89c83fcdfd41c67e2dd2a037982556c657dc55fc1938c6f6cdcd5ffa614c1fb3" - }, - "cpython-3.10.12-windows-i686-none": { - "name": "cpython", - "arch": "i686", - "os": "windows", + "arch": "aarch64", + "os": "macos", "libc": "none", "major": 3, "minor": 10, "patch": 12, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-i686-pc-windows-msvc-shared-pgo-full.tar.zst", - "sha256": "0743b9976f20b06d9cf12de9d1b2dfe06b13f76978275e9dac73a275624bde2c" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "a7d0cadbe867cc53dd47d7327244154157a7cca02edb88cf3bb760a4f91d4e44" }, - "cpython-3.10.12-linux-ppc64le-gnu": { + "cpython-3.10.12-linux-powerpc64le-gnu": { "name": "cpython", - "arch": "ppc64le", + "arch": "powerpc64le", "os": "linux", "libc": "gnu", "major": 3, @@ -1572,16 +1154,27 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-s390x-unknown-linux-gnu-debug-full.tar.zst", "sha256": "756579b52acb9b13b162ac901e56ff311def443e69d7f7259a91198b76a30ecb" }, - "cpython-3.10.12-darwin-x86_64-none": { + "cpython-3.10.12-linux-x86-gnu": { "name": "cpython", - "arch": "x86_64", - "os": "darwin", + "arch": "x86", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 12, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-i686-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "89c83fcdfd41c67e2dd2a037982556c657dc55fc1938c6f6cdcd5ffa614c1fb3" + }, + "cpython-3.10.12-windows-x86-none": { + "name": "cpython", + "arch": "x86", + "os": "windows", "libc": "none", "major": 3, "minor": 10, "patch": 12, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "f1fa448384dd48033825e56ee6b5afc76c5dd67dcf2b73b61d2b252ae2e87bca" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + "sha256": "0743b9976f20b06d9cf12de9d1b2dfe06b13f76978275e9dac73a275624bde2c" }, "cpython-3.10.12-linux-x86_64-gnu": { "name": "cpython", @@ -1605,6 +1198,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-x86_64-unknown-linux-musl-lto-full.tar.zst", "sha256": "b343cbe7c41b7698b568ea5252328cdccb213100efa71da8d3db6e21afd9f6cf" }, + "cpython-3.10.12-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 10, + "patch": 12, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "f1fa448384dd48033825e56ee6b5afc76c5dd67dcf2b73b61d2b252ae2e87bca" + }, "cpython-3.10.12-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -1616,86 +1220,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "cb6e7c84d9e369a0ee76c9ea73d415a113ba9982db58f44e6bab5414838d35f3" }, - "cpython-3.10.12-linux-x86_64_v2-gnu": { + "cpython-3.10.11-linux-aarch64-gnu": { "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 12, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "b4d606147bcb75735dd55928330e111dec35fb2c825d2e3fd71eca23eaa11e5f" - }, - "cpython-3.10.12-linux-x86_64_v2-musl": { - "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 10, - "patch": 12, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-x86_64_v2-unknown-linux-musl-lto-full.tar.zst", - "sha256": "78e9e5f03b4fd8ebd2253121f7ac8516d39761d28ac094d3f8025fc576ade1bd" - }, - "cpython-3.10.12-linux-x86_64_v3-gnu": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 12, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "3f85d12509db9cfe11785c0947d1e5baca0167e3eaa7065f9424a65fa159fb2f" - }, - "cpython-3.10.12-linux-x86_64_v3-musl": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 10, - "patch": 12, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-x86_64_v3-unknown-linux-musl-lto-full.tar.zst", - "sha256": "dbab7a72fa1ef45813cc25977dece74667a05a02b18dbc774aab7fcb395da424" - }, - "cpython-3.10.12-linux-x86_64_v4-gnu": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 12, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "259e0c20061ed25a72e72912eb212a8571d0607659edba580272db809af7206e" - }, - "cpython-3.10.12-linux-x86_64_v4-musl": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 10, - "patch": 12, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-x86_64_v4-unknown-linux-musl-lto-full.tar.zst", - "sha256": "ce13736e1faa4fcb559c206c632f9232ba8f2b1f106c4af44433f3936b06d4d0" - }, - "cpython-3.10.11-darwin-arm64-none": { - "name": "cpython", - "arch": "arm64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 10, - "patch": 11, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "da9c8a3cd04485fd397387ea2fa56f3cac71827aafb51d8438b2868f86eb345b" - }, - "cpython-3.10.11-linux-arm64-gnu": { - "name": "cpython", - "arch": "arm64", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, @@ -1704,31 +1231,20 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-aarch64-unknown-linux-gnu-debug-full.tar.zst", "sha256": "a5271cc014f2ce2ab54a0789556c15b84668e2afcc530512818c4b87c6a94483" }, - "cpython-3.10.11-linux-i686-gnu": { + "cpython-3.10.11-macos-aarch64-none": { "name": "cpython", - "arch": "i686", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 11, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-i686-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "9304d6eeef48bd246a2959ebc76b20dbb2c6a81aa1d214f4471cb273c11717f2" - }, - "cpython-3.10.11-windows-i686-none": { - "name": "cpython", - "arch": "i686", - "os": "windows", + "arch": "aarch64", + "os": "macos", "libc": "none", "major": 3, "minor": 10, "patch": 11, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-i686-pc-windows-msvc-shared-pgo-full.tar.zst", - "sha256": "60e76e136ab23b891ed1212e58bd11a73a19cd9fd884ec1c5653ca1c159d674e" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "da9c8a3cd04485fd397387ea2fa56f3cac71827aafb51d8438b2868f86eb345b" }, - "cpython-3.10.11-linux-ppc64le-gnu": { + "cpython-3.10.11-linux-powerpc64le-gnu": { "name": "cpython", - "arch": "ppc64le", + "arch": "powerpc64le", "os": "linux", "libc": "gnu", "major": 3, @@ -1737,16 +1253,27 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-ppc64le-unknown-linux-gnu-debug-full.tar.zst", "sha256": "ac32e3788109ff0cc536a6108072d9203217df744cf56d3a4ab0b19857d8e244" }, - "cpython-3.10.11-darwin-x86_64-none": { + "cpython-3.10.11-linux-x86-gnu": { "name": "cpython", - "arch": "x86_64", - "os": "darwin", + "arch": "x86", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 11, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-i686-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "9304d6eeef48bd246a2959ebc76b20dbb2c6a81aa1d214f4471cb273c11717f2" + }, + "cpython-3.10.11-windows-x86-none": { + "name": "cpython", + "arch": "x86", + "os": "windows", "libc": "none", "major": 3, "minor": 10, "patch": 11, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "e84c12aa0285235eed365971ceedf040f4d8014f5342d371e138a4da9e4e9b7c" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + "sha256": "60e76e136ab23b891ed1212e58bd11a73a19cd9fd884ec1c5653ca1c159d674e" }, "cpython-3.10.11-linux-x86_64-gnu": { "name": "cpython", @@ -1770,6 +1297,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-x86_64-unknown-linux-musl-lto-full.tar.zst", "sha256": "7918188e01a266915dd0945711e274d45c8d7fb540d48240e13c4fd96f43afbb" }, + "cpython-3.10.11-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 10, + "patch": 11, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "e84c12aa0285235eed365971ceedf040f4d8014f5342d371e138a4da9e4e9b7c" + }, "cpython-3.10.11-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -1781,86 +1319,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "9b4dc4a335b6122ce783bc80f5015b683e3ab1a56054751c5df494db0521da67" }, - "cpython-3.10.11-linux-x86_64_v2-gnu": { + "cpython-3.10.9-linux-aarch64-gnu": { "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 11, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "cd6316c2731d2282587475e781e240677c89a678694cf3e58f12e4c2e57add43" - }, - "cpython-3.10.11-linux-x86_64_v2-musl": { - "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 10, - "patch": 11, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-x86_64_v2-unknown-linux-musl-lto-full.tar.zst", - "sha256": "af8c79e71dc8d958bcf80c675b383d210e08880bf80a08e038422274551f8ee5" - }, - "cpython-3.10.11-linux-x86_64_v3-gnu": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 11, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "ea5006c1545afed6f16e833a0d4bac14ec289cc0f5911e12a4ef8704a742cf8e" - }, - "cpython-3.10.11-linux-x86_64_v3-musl": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 10, - "patch": 11, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-x86_64_v3-unknown-linux-musl-lto-full.tar.zst", - "sha256": "532526149236ee3223af38e8946bff2d78ab7cb8756d91ed6676959a1eec4f01" - }, - "cpython-3.10.11-linux-x86_64_v4-gnu": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 11, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "6ebe8edb6bb89109b351e6f4361be7f1563860e1ae1cb8926c17ca19588e5fa3" - }, - "cpython-3.10.11-linux-x86_64_v4-musl": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 10, - "patch": 11, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-x86_64_v4-unknown-linux-musl-lto-full.tar.zst", - "sha256": "58bd65a4e0b595b4d18dc9fc05dc384043d1106e6d0c221679eaefb277fa8784" - }, - "cpython-3.10.9-darwin-arm64-none": { - "name": "cpython", - "arch": "arm64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 10, - "patch": 9, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "2508b8d4b725bb45c3e03d2ddd2b8441f1a74677cb6bd6076e692c0923135ded" - }, - "cpython-3.10.9-linux-arm64-gnu": { - "name": "cpython", - "arch": "arm64", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, @@ -1869,9 +1330,20 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-aarch64-unknown-linux-gnu-debug-full.tar.zst", "sha256": "2c0996dd1fe35314e06e042081b24fb53f3b7b361c3e1b94a6ed659c275ca069" }, - "cpython-3.10.9-linux-i686-gnu": { + "cpython-3.10.9-macos-aarch64-none": { "name": "cpython", - "arch": "i686", + "arch": "aarch64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 10, + "patch": 9, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "2508b8d4b725bb45c3e03d2ddd2b8441f1a74677cb6bd6076e692c0923135ded" + }, + "cpython-3.10.9-linux-x86-gnu": { + "name": "cpython", + "arch": "x86", "os": "linux", "libc": "gnu", "major": 3, @@ -1880,9 +1352,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-i686-unknown-linux-gnu-debug-full.tar.zst", "sha256": "f8c3a63620f412c4a9ccfb6e2435a96a55775550c81a452d164caa6d03a6a1da" }, - "cpython-3.10.9-windows-i686-none": { + "cpython-3.10.9-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -1891,17 +1363,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-i686-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "3d79cfd229ec12b678bbfd79c30fb4cbad9950d6bfb29741d2315b11839998b4" }, - "cpython-3.10.9-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 10, - "patch": 9, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "1153b4d3b03cf1e1d8ec93c098160586f665fcc2d162c0812140a716a688df58" - }, "cpython-3.10.9-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -1924,6 +1385,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-x86_64-unknown-linux-musl-lto-full.tar.zst", "sha256": "1310f187a73b00164ec4ca34e643841c5c34cbb93fe0b3a3f9504e5ea5001ec7" }, + "cpython-3.10.9-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 10, + "patch": 9, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "1153b4d3b03cf1e1d8ec93c098160586f665fcc2d162c0812140a716a688df58" + }, "cpython-3.10.9-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -1935,86 +1407,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "4cfa6299a78a3959102c461d126e4869616f0a49c60b44220c000fc9aecddd78" }, - "cpython-3.10.9-linux-x86_64_v2-gnu": { + "cpython-3.10.8-linux-aarch64-gnu": { "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 9, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "fbd196fc7680e499f374a6946b3618b42980e2890a6d533993337b563bd8abb0" - }, - "cpython-3.10.9-linux-x86_64_v2-musl": { - "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 10, - "patch": 9, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-x86_64_v2-unknown-linux-musl-lto-full.tar.zst", - "sha256": "430a3d75bd4f0dca37bf00bab86c4f8fd78f09a37e5cbd89e07263c5fa88327a" - }, - "cpython-3.10.9-linux-x86_64_v3-gnu": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 9, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "ece59c02c9fdc77500bc1b60636077bc0a9536d6e63052c727db11488ae2de8c" - }, - "cpython-3.10.9-linux-x86_64_v3-musl": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 10, - "patch": 9, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-x86_64_v3-unknown-linux-musl-lto-full.tar.zst", - "sha256": "1aae9aa444aa03e03bdf24816c8c503fa76488b00fcd1e822350ef22a05edc00" - }, - "cpython-3.10.9-linux-x86_64_v4-gnu": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 9, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "f4aab4df5c104f5d438a1a3b4601e45963a13c4100cbafee832336e47f6479cb" - }, - "cpython-3.10.9-linux-x86_64_v4-musl": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 10, - "patch": 9, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-x86_64_v4-unknown-linux-musl-lto-full.tar.zst", - "sha256": "5abe36719d4e42de95f5c6319785cfb024699beda1e72788231962d4e24c1e67" - }, - "cpython-3.10.8-darwin-arm64-none": { - "name": "cpython", - "arch": "arm64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 10, - "patch": 8, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "f8ba5f87153a17717e900ff7bba20e2eefe8a53a5bd3c78f9f6922d6d910912d" - }, - "cpython-3.10.8-linux-arm64-gnu": { - "name": "cpython", - "arch": "arm64", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, @@ -2023,9 +1418,20 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-aarch64-unknown-linux-gnu-debug-full.tar.zst", "sha256": "879e76260be226512693e37a28cc3a6670b5ee270a4440e4b04a7b415dba451c" }, - "cpython-3.10.8-linux-i686-gnu": { + "cpython-3.10.8-macos-aarch64-none": { "name": "cpython", - "arch": "i686", + "arch": "aarch64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 10, + "patch": 8, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "f8ba5f87153a17717e900ff7bba20e2eefe8a53a5bd3c78f9f6922d6d910912d" + }, + "cpython-3.10.8-linux-x86-gnu": { + "name": "cpython", + "arch": "x86", "os": "linux", "libc": "gnu", "major": 3, @@ -2034,9 +1440,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-i686-unknown-linux-gnu-debug-full.tar.zst", "sha256": "ab434eccffeec4f6f51af017e4eed69d4f1ea55f48c5b89b8a8779df3fa799df" }, - "cpython-3.10.8-windows-i686-none": { + "cpython-3.10.8-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -2045,17 +1451,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-i686-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "7547ea172f7fa3d7619855f28780da9feb615b6cb52c5c64d34f65b542799fee" }, - "cpython-3.10.8-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 10, - "patch": 8, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "a18f81ecc7da0779be960ad35c561a834866c0e6d1310a4f742fddfd6163753f" - }, "cpython-3.10.8-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -2078,6 +1473,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-x86_64-unknown-linux-musl-lto-full.tar.zst", "sha256": "bb87e933afcfd2e8de045e5a691feff1fb8fb06a09315b37d187762fddfc4546" }, + "cpython-3.10.8-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 10, + "patch": 8, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "a18f81ecc7da0779be960ad35c561a834866c0e6d1310a4f742fddfd6163753f" + }, "cpython-3.10.8-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -2089,86 +1495,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "ab40f9584be896c697c5fca351ab82d7b55f01b8eb0494f0a15a67562e49161a" }, - "cpython-3.10.8-linux-x86_64_v2-gnu": { + "cpython-3.10.7-linux-aarch64-gnu": { "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 8, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "96c8f51e23a1bd8b9a2d9adc0e2457fa74062f16d25d2d0d659b8a6c94631824" - }, - "cpython-3.10.8-linux-x86_64_v2-musl": { - "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 10, - "patch": 8, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-x86_64_v2-unknown-linux-musl-lto-full.tar.zst", - "sha256": "c9a496301e22fabad5a27160fc199e25a10dc2775c26efa7b905ee9a58e5076a" - }, - "cpython-3.10.8-linux-x86_64_v3-gnu": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 8, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "1053c4ca16bda71ae9f2a973cc82118b9e41aa6e426302d9709d9c74c60af50d" - }, - "cpython-3.10.8-linux-x86_64_v3-musl": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 10, - "patch": 8, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-x86_64_v3-unknown-linux-musl-lto-full.tar.zst", - "sha256": "bfcfb51a02082d1136259142b590a52a7b9f5e21bf1177bf988129b4f8f99933" - }, - "cpython-3.10.8-linux-x86_64_v4-gnu": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 8, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "f23f744868d70cf2e9588aa961e9682060b6c3d6f55c65a61505e23c4895b7e4" - }, - "cpython-3.10.8-linux-x86_64_v4-musl": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 10, - "patch": 8, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-x86_64_v4-unknown-linux-musl-lto-full.tar.zst", - "sha256": "0574cab92283d240650aef5cc5716969e8e1a7a40d298d23bf0922d0988b19f9" - }, - "cpython-3.10.7-darwin-arm64-none": { - "name": "cpython", - "arch": "arm64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 10, - "patch": 7, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "9f44cf63441a90f4ec99a032a2bda43971ae7964822daa0ee730a9cba15d50da" - }, - "cpython-3.10.7-linux-arm64-gnu": { - "name": "cpython", - "arch": "arm64", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, @@ -2177,9 +1506,20 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-aarch64-unknown-linux-gnu-debug-full.tar.zst", "sha256": "9f346729b523e860194635eb67c9f6bc8f12728ba7ddfe4fd80f2e6d685781e3" }, - "cpython-3.10.7-linux-i686-gnu": { + "cpython-3.10.7-macos-aarch64-none": { "name": "cpython", - "arch": "i686", + "arch": "aarch64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 10, + "patch": 7, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "9f44cf63441a90f4ec99a032a2bda43971ae7964822daa0ee730a9cba15d50da" + }, + "cpython-3.10.7-linux-x86-gnu": { + "name": "cpython", + "arch": "x86", "os": "linux", "libc": "gnu", "major": 3, @@ -2188,9 +1528,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-i686-unknown-linux-gnu-debug-full.tar.zst", "sha256": "a79816c50abeb2752530f68b4d7d95b6f48392f44a9a7f135b91807d76872972" }, - "cpython-3.10.7-windows-i686-none": { + "cpython-3.10.7-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -2199,17 +1539,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-i686-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "323532701cb468199d6f14031b991f945d4bbf986ca818185e17e132d3763bdf" }, - "cpython-3.10.7-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 10, - "patch": 7, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "e03e28dc9fe55ea5ca06fece8f2f2a16646b217d28c0cd09ebcd512f444fdc90" - }, "cpython-3.10.7-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -2232,6 +1561,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-x86_64-unknown-linux-musl-lto-full.tar.zst", "sha256": "7f2c933d23c0f38cf145c2d6c65b5cf53bb589690d394fd4c01b2230c23c2bff" }, + "cpython-3.10.7-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 10, + "patch": 7, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "e03e28dc9fe55ea5ca06fece8f2f2a16646b217d28c0cd09ebcd512f444fdc90" + }, "cpython-3.10.7-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -2243,86 +1583,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "5363974e6ee6c91dbd6bc3533e38b02a26abc2ff1c9a095912f237b916be22d3" }, - "cpython-3.10.7-linux-x86_64_v2-gnu": { + "cpython-3.10.6-linux-aarch64-gnu": { "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 7, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "fb44d19c8e4da5d5c467b6bbfc1b0350fa7faee71462fbd6ac3d69633c2b334a" - }, - "cpython-3.10.7-linux-x86_64_v2-musl": { - "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 10, - "patch": 7, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-x86_64_v2-unknown-linux-musl-lto-full.tar.zst", - "sha256": "01258e2cd7a6950c31b850ae99ba7c4a351338ad3e01bab28b077d5eb7e1f509" - }, - "cpython-3.10.7-linux-x86_64_v3-gnu": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 7, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "c028a4c945329731be66285168a074ac2fc7f25d28c5bbb9bbbb532d45a82ab4" - }, - "cpython-3.10.7-linux-x86_64_v3-musl": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 10, - "patch": 7, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-x86_64_v3-unknown-linux-musl-lto-full.tar.zst", - "sha256": "ad93efc6c9b1bd25368b0e50e35ba6a21511d68ebfb209811585869666874cfc" - }, - "cpython-3.10.7-linux-x86_64_v4-gnu": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 7, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "b9ddf213ba0f69ac200dd50e5d2c6a52468307a584a64efe422ef537f446c0da" - }, - "cpython-3.10.7-linux-x86_64_v4-musl": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 10, - "patch": 7, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-x86_64_v4-unknown-linux-musl-lto-full.tar.zst", - "sha256": "9f3883a4eabe72243a6677467ec117247176353d12352f1dd1d32fa0ef92a18c" - }, - "cpython-3.10.6-darwin-arm64-none": { - "name": "cpython", - "arch": "arm64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 10, - "patch": 6, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "159230851a69cf5cab80318bce48674244d7c6304de81f44c22ff0abdf895cfa" - }, - "cpython-3.10.6-linux-arm64-gnu": { - "name": "cpython", - "arch": "arm64", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, @@ -2331,9 +1594,20 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-aarch64-unknown-linux-gnu-debug-full.tar.zst", "sha256": "edc1c9742b824caebbc5cb224c8990aa8658b81593fd9219accf3efa3e849501" }, - "cpython-3.10.6-linux-i686-gnu": { + "cpython-3.10.6-macos-aarch64-none": { "name": "cpython", - "arch": "i686", + "arch": "aarch64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 10, + "patch": 6, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "159230851a69cf5cab80318bce48674244d7c6304de81f44c22ff0abdf895cfa" + }, + "cpython-3.10.6-linux-x86-gnu": { + "name": "cpython", + "arch": "x86", "os": "linux", "libc": "gnu", "major": 3, @@ -2342,9 +1616,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-i686-unknown-linux-gnu-debug-full.tar.zst", "sha256": "07fa4f5499b8885d1eea49caf5476d76305ab73494b7398dfd22c14093859e4f" }, - "cpython-3.10.6-windows-i686-none": { + "cpython-3.10.6-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -2353,17 +1627,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-i686-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "8d9a259e15d5a1be48ef13cd5627d7f6c15eadf41a3539e99ed1deee668c075e" }, - "cpython-3.10.6-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 10, - "patch": 6, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "9405499573a7aa8b67d070d096ded4f3e571f18c2b34762606ecc8025290b122" - }, "cpython-3.10.6-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -2386,6 +1649,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-x86_64-unknown-linux-musl-lto-full.tar.zst", "sha256": "f859a72da0bb2f1261f8cebdac931b05b59474c7cb65cee8e85c34fc014dd452" }, + "cpython-3.10.6-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 10, + "patch": 6, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "9405499573a7aa8b67d070d096ded4f3e571f18c2b34762606ecc8025290b122" + }, "cpython-3.10.6-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -2397,86 +1671,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "01dc349721594b1bb5b582651f81479a24352f718fdf6279101caa0f377b160a" }, - "cpython-3.10.6-linux-x86_64_v2-gnu": { + "cpython-3.10.5-linux-aarch64-gnu": { "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 6, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "4a0cc9f5b57ca1eff38d0c9ee71f38f9bf28fbc34d150edd4914b32b69a84f17" - }, - "cpython-3.10.6-linux-x86_64_v2-musl": { - "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 10, - "patch": 6, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-x86_64_v2-unknown-linux-musl-lto-full.tar.zst", - "sha256": "55d7d942223ca7ccb972dee02770ddd6b3cf61d674b3488f3917dac847688624" - }, - "cpython-3.10.6-linux-x86_64_v3-gnu": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 6, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "38bc029b11deed33097aae7e1b2e56b6d85f06df24355ff6105bf85fbb2fcb9b" - }, - "cpython-3.10.6-linux-x86_64_v3-musl": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 10, - "patch": 6, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-x86_64_v3-unknown-linux-musl-lto-full.tar.zst", - "sha256": "da135320610e0e35926f2e5767b03d6c641220a4595423cbd3cf126fe1ff1868" - }, - "cpython-3.10.6-linux-x86_64_v4-gnu": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 6, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "dc779272bd2363abda4a8a784f6dea2dd7d8b2d86cdcfae5e8970df6e5dfbd76" - }, - "cpython-3.10.6-linux-x86_64_v4-musl": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 10, - "patch": 6, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-x86_64_v4-unknown-linux-musl-lto-full.tar.zst", - "sha256": "8b3349fe4919adda35edce8108d96f398479620f01efdeee1ef1c667037f1c20" - }, - "cpython-3.10.5-darwin-arm64-none": { - "name": "cpython", - "arch": "arm64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 10, - "patch": 5, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "f68d25dbe9daa96187fa9e05dd8969f46685547fecf1861a99af898f96a5379e" - }, - "cpython-3.10.5-linux-arm64-gnu": { - "name": "cpython", - "arch": "arm64", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, @@ -2485,9 +1682,20 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-aarch64-unknown-linux-gnu-debug-full.tar.zst", "sha256": "9fa6970a3d0a5dc26c4ed272bb1836d1f1f7a8f4b9d67f634d0262ff8c1fed0b" }, - "cpython-3.10.5-linux-i686-gnu": { + "cpython-3.10.5-macos-aarch64-none": { "name": "cpython", - "arch": "i686", + "arch": "aarch64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 10, + "patch": 5, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "f68d25dbe9daa96187fa9e05dd8969f46685547fecf1861a99af898f96a5379e" + }, + "cpython-3.10.5-linux-x86-gnu": { + "name": "cpython", + "arch": "x86", "os": "linux", "libc": "gnu", "major": 3, @@ -2496,9 +1704,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-i686-unknown-linux-gnu-debug-full.tar.zst", "sha256": "63fcfc425adabc034c851dadfb499de3083fd7758582191c12162ad2471256b0" }, - "cpython-3.10.5-windows-i686-none": { + "cpython-3.10.5-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -2507,17 +1715,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-i686-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "e201192f0aa73904bc5a5f43d1ce4c9fb243dfe02138e690676713fe02c7d662" }, - "cpython-3.10.5-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 10, - "patch": 5, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "5e372e6738a733532aa985730d9a47ee4c77b7c706e91ef61d37aacbb2e54845" - }, "cpython-3.10.5-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -2540,6 +1737,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-x86_64-unknown-linux-musl-lto-full.tar.zst", "sha256": "3682e0add14a3bac654afe467a84981628b0c7ebdccd4ebf26dfaa916238e2fe" }, + "cpython-3.10.5-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 10, + "patch": 5, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "5e372e6738a733532aa985730d9a47ee4c77b7c706e91ef61d37aacbb2e54845" + }, "cpython-3.10.5-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -2551,86 +1759,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "cff35feefe423d4282e9a3e1bb756d0acbb2f776b1ada82c44c71ac3e1491448" }, - "cpython-3.10.5-linux-x86_64_v2-gnu": { + "cpython-3.10.4-linux-aarch64-gnu": { "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 5, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "fdf3ade3f03f4a755f378d9abea1e9eb6a6a3fb18ce4620d5b7a6cb223a59741" - }, - "cpython-3.10.5-linux-x86_64_v2-musl": { - "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 10, - "patch": 5, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-x86_64_v2-unknown-linux-musl-lto-full.tar.zst", - "sha256": "26ad08f1e22d75501fe8579795ab8aee3e52f9c1b1037e87dd349632fa4620b5" - }, - "cpython-3.10.5-linux-x86_64_v3-gnu": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 5, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "137823acc0d98178c31ce0f75cef5fb0d3850edaf692ced2fab84ef9777d2c01" - }, - "cpython-3.10.5-linux-x86_64_v3-musl": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 10, - "patch": 5, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-x86_64_v3-unknown-linux-musl-lto-full.tar.zst", - "sha256": "125e101edfca4477694c12b3fa310ff81326cc51a1b1b889cbfc3577ff5b8f5d" - }, - "cpython-3.10.5-linux-x86_64_v4-gnu": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 5, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "75fddbd0fa525d9455dab972c56ec8b6d39ddd62c12ab38dca81b558b1e70338" - }, - "cpython-3.10.5-linux-x86_64_v4-musl": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 10, - "patch": 5, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-x86_64_v4-unknown-linux-musl-lto-full.tar.zst", - "sha256": "ee4fdc2bc4797d64de3930750a8b0327cb789ee45483a23f09c913837171dd9b" - }, - "cpython-3.10.4-darwin-arm64-none": { - "name": "cpython", - "arch": "arm64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 10, - "patch": 4, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "c404f226195d79933b1e0a3ec88f0b79d35c873de592e223e11008f3a37f83d6" - }, - "cpython-3.10.4-linux-arm64-gnu": { - "name": "cpython", - "arch": "arm64", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, @@ -2639,9 +1770,20 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-aarch64-unknown-linux-gnu-debug-full.tar.zst", "sha256": "092369e9d170c4c1074e1b305accb74f9486e6185d2e3f3f971869ff89538d3e" }, - "cpython-3.10.4-linux-i686-gnu": { + "cpython-3.10.4-macos-aarch64-none": { "name": "cpython", - "arch": "i686", + "arch": "aarch64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 10, + "patch": 4, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "c404f226195d79933b1e0a3ec88f0b79d35c873de592e223e11008f3a37f83d6" + }, + "cpython-3.10.4-linux-x86-gnu": { + "name": "cpython", + "arch": "x86", "os": "linux", "libc": "gnu", "major": 3, @@ -2650,9 +1792,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-i686-unknown-linux-gnu-debug-full.tar.zst", "sha256": "ba940a74a7434fe78d81aed9fb1e5ccdc3d97191a2db35716fc94e3b6604ace0" }, - "cpython-3.10.4-windows-i686-none": { + "cpython-3.10.4-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -2661,17 +1803,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-i686-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "c37a47e46de93473916f700a790cb43515f00745fba6790004e2731ec934f4d3" }, - "cpython-3.10.4-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 10, - "patch": 4, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "e447f00fe53168d18cbfe110645dbf33982a17580b9e4424a411f9245d99cd21" - }, "cpython-3.10.4-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -2694,6 +1825,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-x86_64-unknown-linux-musl-lto-full.tar.zst", "sha256": "8b8b97f7746a3deca91ada408025457ced34f582dad2114b33ce6fec9cf35b28" }, + "cpython-3.10.4-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 10, + "patch": 4, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "e447f00fe53168d18cbfe110645dbf33982a17580b9e4424a411f9245d99cd21" + }, "cpython-3.10.4-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -2705,86 +1847,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "d636dc1bcca74dd9c6e3b26f7c081b3e229336e8378fe554bf8ba65fe780a2ac" }, - "cpython-3.10.4-linux-x86_64_v2-gnu": { + "cpython-3.10.3-linux-aarch64-gnu": { "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 4, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "e0849600bee6c588f1aa5f4b0b9342292cfb6eb40de0c705f60bd71f22b713d0" - }, - "cpython-3.10.4-linux-x86_64_v2-musl": { - "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 10, - "patch": 4, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-x86_64_v2-unknown-linux-musl-lto-full.tar.zst", - "sha256": "e036939d7b96fac35fcf9d01d2ddd6007f03f8a2e382d84ef31c0fcc37399e86" - }, - "cpython-3.10.4-linux-x86_64_v3-gnu": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 4, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "2875d103a83ff9f9f7ec2ec3ebc43d0e89f20416cd715b83b541b33f91d59832" - }, - "cpython-3.10.4-linux-x86_64_v3-musl": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 10, - "patch": 4, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-x86_64_v3-unknown-linux-musl-lto-full.tar.zst", - "sha256": "b83d649edc245c2f5f6347c09344108e9558dbbdf67ab7a596a76ff451394714" - }, - "cpython-3.10.4-linux-x86_64_v4-gnu": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 4, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "839f9cb8f1b0e0eb347164a04323f832b945091ba3482724325d3eed1a75daab" - }, - "cpython-3.10.4-linux-x86_64_v4-musl": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 10, - "patch": 4, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-x86_64_v4-unknown-linux-musl-lto-full.tar.zst", - "sha256": "3c0c1be54a9711dde87f38d1d8fe97d6d05ca55d590870371b8302d0d4588463" - }, - "cpython-3.10.3-darwin-arm64-none": { - "name": "cpython", - "arch": "arm64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 10, - "patch": 3, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "b1abefd0fc66922cf9749e4d5ceb97df4d3cfad0cd9cdc4bd04262a68d565698" - }, - "cpython-3.10.3-linux-arm64-gnu": { - "name": "cpython", - "arch": "arm64", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, @@ -2793,9 +1858,20 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-aarch64-unknown-linux-gnu-debug-full.tar.zst", "sha256": "101284d27578438da200be1f6b9a1ba621432c5549fa5517797ec320bf75e3d5" }, - "cpython-3.10.3-linux-i686-gnu": { + "cpython-3.10.3-macos-aarch64-none": { "name": "cpython", - "arch": "i686", + "arch": "aarch64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 10, + "patch": 3, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "b1abefd0fc66922cf9749e4d5ceb97df4d3cfad0cd9cdc4bd04262a68d565698" + }, + "cpython-3.10.3-linux-x86-gnu": { + "name": "cpython", + "arch": "x86", "os": "linux", "libc": "gnu", "major": 3, @@ -2804,9 +1880,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-i686-unknown-linux-gnu-debug-full.tar.zst", "sha256": "43c1cd6e203bfba1a2eeb96cd2a15ce0ebde0e72ecc9555934116459347a9c28" }, - "cpython-3.10.3-windows-i686-none": { + "cpython-3.10.3-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -2815,17 +1891,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-i686-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "fbc0924a138937fe435fcdb20b0c6241290558e07f158e5578bd91cc8acef469" }, - "cpython-3.10.3-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 10, - "patch": 3, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "bc5d6f284b506104ff6b4e36cec84cbdb4602dfed4c6fe19971a808eb8c439ec" - }, "cpython-3.10.3-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -2848,6 +1913,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-x86_64-unknown-linux-musl-lto-full.tar.zst", "sha256": "7c034d8a5787744939335ce43d64f2ddcc830a74e63773408d0c8f3c3a4e7916" }, + "cpython-3.10.3-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 10, + "patch": 3, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "bc5d6f284b506104ff6b4e36cec84cbdb4602dfed4c6fe19971a808eb8c439ec" + }, "cpython-3.10.3-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -2859,86 +1935,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "72b91d26f54321ba90a86a3bbc711fa1ac31e0704fec352b36e70b0251ffb13c" }, - "cpython-3.10.3-linux-x86_64_v2-gnu": { + "cpython-3.10.2-linux-aarch64-gnu": { "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 3, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "65215230eacebebb87aa1e56d539fc1770870024dcfdadb5d0e18365c1b3b0a9" - }, - "cpython-3.10.3-linux-x86_64_v2-musl": { - "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 10, - "patch": 3, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-x86_64_v2-unknown-linux-musl-lto-full.tar.zst", - "sha256": "87228dbf0063d26c67128e151f25f9de8cf5c2a041d5d424fef974cf32d5237f" - }, - "cpython-3.10.3-linux-x86_64_v3-gnu": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 3, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "e15dcd98efdde249e7c2857f80b93092245f8495d822cb6c7af3308a59729c21" - }, - "cpython-3.10.3-linux-x86_64_v3-musl": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 10, - "patch": 3, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-x86_64_v3-unknown-linux-musl-lto-full.tar.zst", - "sha256": "03897947f6920e12cd8fab4eca2c5c8d43ca2140f2eabf4a6d457ba8f41157f7" - }, - "cpython-3.10.3-linux-x86_64_v4-gnu": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 3, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "412570cf01df665c848dacc213c645a3c17aa5045b1805a86c6d9fa2d82eb9ce" - }, - "cpython-3.10.3-linux-x86_64_v4-musl": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 10, - "patch": 3, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-x86_64_v4-unknown-linux-musl-lto-full.tar.zst", - "sha256": "7af8e1000ceb0a63bfdaa62fd064cb98fcf3712a5c8b71d17744f19307bfa89b" - }, - "cpython-3.10.2-darwin-arm64-none": { - "name": "cpython", - "arch": "arm64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 10, - "patch": 2, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "1ef939fd471a9d346a7bc43d2c16fb483ddc4f98af6dad7f08a009e299977a1a" - }, - "cpython-3.10.2-linux-arm64-gnu": { - "name": "cpython", - "arch": "arm64", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, @@ -2947,9 +1946,20 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-aarch64-unknown-linux-gnu-debug-full.tar.zst", "sha256": "9936f1549f950311229465de509b35c062aa474e504c20a1d6f0f632da57e002" }, - "cpython-3.10.2-linux-i686-gnu": { + "cpython-3.10.2-macos-aarch64-none": { "name": "cpython", - "arch": "i686", + "arch": "aarch64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 10, + "patch": 2, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "1ef939fd471a9d346a7bc43d2c16fb483ddc4f98af6dad7f08a009e299977a1a" + }, + "cpython-3.10.2-linux-x86-gnu": { + "name": "cpython", + "arch": "x86", "os": "linux", "libc": "gnu", "major": 3, @@ -2958,9 +1968,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-i686-unknown-linux-gnu-debug-full.tar.zst", "sha256": "9be2a667f29ed048165cfb3f5dbe61703fd3e5956f8f517ae098740ac8411c0b" }, - "cpython-3.10.2-windows-i686-none": { + "cpython-3.10.2-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -2969,17 +1979,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-i686-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "698b09b1b8321a4dc43d62f6230b62adcd0df018b2bcf5f1b4a7ce53dcf23bcc" }, - "cpython-3.10.2-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 10, - "patch": 2, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "bacf720c13ab67685a384f1417e9c2420972d88f29c8b7c26e72874177f2d120" - }, "cpython-3.10.2-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -3002,6 +2001,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-x86_64-unknown-linux-musl-lto-full.tar.zst", "sha256": "df246cf27db346081935d33ce0344a185d1f08b04a4500eb1e21d4d922ee7eb4" }, + "cpython-3.10.2-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 10, + "patch": 2, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "bacf720c13ab67685a384f1417e9c2420972d88f29c8b7c26e72874177f2d120" + }, "cpython-3.10.2-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -3013,86 +2023,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "7397e78a4fbe429144adc1f33af942bdd5175184e082ac88f3023b3a740dd1a0" }, - "cpython-3.10.2-linux-x86_64_v2-gnu": { + "cpython-3.10.0-linux-aarch64-gnu": { "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 2, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "8c772010a27d7fd5a3e271c835c37b73bd41bc04a737523f12a3920bf721598b" - }, - "cpython-3.10.2-linux-x86_64_v2-musl": { - "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 10, - "patch": 2, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-x86_64_v2-unknown-linux-musl-lto-full.tar.zst", - "sha256": "db2474e7a0b6fa37629f26308b3c5f0a018abae4bc9529ecc2fbf6ef5e741053" - }, - "cpython-3.10.2-linux-x86_64_v3-gnu": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 2, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "03a6116ea6dbd5d4e667d4bec2b5032deefd604bc068908cd105327c417d0906" - }, - "cpython-3.10.2-linux-x86_64_v3-musl": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 10, - "patch": 2, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-x86_64_v3-unknown-linux-musl-lto-full.tar.zst", - "sha256": "d5489a850ffd2c5b85b4a52e26bb5022429e432823527c782e42c2f0dc333938" - }, - "cpython-3.10.2-linux-x86_64_v4-gnu": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 10, - "patch": 2, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "2262abca39027b52d96b397c5d3285b07a93fafd7dde52295876a331e9fb48c4" - }, - "cpython-3.10.2-linux-x86_64_v4-musl": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 10, - "patch": 2, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-x86_64_v4-unknown-linux-musl-lto-full.tar.zst", - "sha256": "b2956e7d7ed5dec84569e7381cfdbcd51dd1794f57af44864e3f2d8ee9f6a8c5" - }, - "cpython-3.10.0-darwin-arm64-none": { - "name": "cpython", - "arch": "arm64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 10, - "patch": 0, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.10.0-aarch64-apple-darwin-pgo%2Blto-20211017T1616.tar.zst", - "sha256": null - }, - "cpython-3.10.0-linux-arm64-gnu": { - "name": "cpython", - "arch": "arm64", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, @@ -3101,9 +2034,20 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.10.0-aarch64-unknown-linux-gnu-debug-20211017T1616.tar.zst", "sha256": null }, - "cpython-3.10.0-linux-i686-gnu": { + "cpython-3.10.0-macos-aarch64-none": { "name": "cpython", - "arch": "i686", + "arch": "aarch64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 10, + "patch": 0, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.10.0-aarch64-apple-darwin-pgo%2Blto-20211017T1616.tar.zst", + "sha256": null + }, + "cpython-3.10.0-linux-x86-gnu": { + "name": "cpython", + "arch": "x86", "os": "linux", "libc": "gnu", "major": 3, @@ -3112,9 +2056,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.10.0-i686-unknown-linux-gnu-debug-20211017T1616.tar.zst", "sha256": null }, - "cpython-3.10.0-windows-i686-none": { + "cpython-3.10.0-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -3123,17 +2067,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.10.0-i686-pc-windows-msvc-shared-pgo-20211017T1616.tar.zst", "sha256": null }, - "cpython-3.10.0-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 10, - "patch": 0, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.10.0-x86_64-apple-darwin-pgo%2Blto-20211017T1616.tar.zst", - "sha256": null - }, "cpython-3.10.0-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -3156,6 +2089,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.10.0-x86_64-unknown-linux-musl-lto-20211017T1616.tar.zst", "sha256": null }, + "cpython-3.10.0-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 10, + "patch": 0, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.10.0-x86_64-apple-darwin-pgo%2Blto-20211017T1616.tar.zst", + "sha256": null + }, "cpython-3.10.0-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -3167,60 +2111,38 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.10.0-x86_64-pc-windows-msvc-shared-pgo-20211017T1616.tar.zst", "sha256": null }, - "cpython-3.9.18-darwin-arm64-none": { + "cpython-3.9.18-linux-aarch64-gnu": { "name": "cpython", - "arch": "arm64", - "os": "darwin", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 18, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "d27efd4609a3e15ff901040529d5689be99f2ebfe5132ab980d066d775068265" + }, + "cpython-3.9.18-macos-aarch64-none": { + "name": "cpython", + "arch": "aarch64", + "os": "macos", "libc": "none", "major": 3, "minor": 9, "patch": 18, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.9.18%2B20240107-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "b7d31a15f7af359c59b01ed9c8accb4b6bdd1237b910699e6b2d14df8e2c1cdc" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "579f9b68bbb3a915cbab9682e4d3c253bc96b0556b8a860982c49c25c61f974a" }, - "cpython-3.9.18-linux-arm64-gnu": { + "cpython-3.9.18-linux-powerpc64le-gnu": { "name": "cpython", - "arch": "arm64", + "arch": "powerpc64le", "os": "linux", "libc": "gnu", "major": 3, "minor": 9, "patch": 18, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.9.18%2B20240107-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "16a1ff546e24790bbea66a52d469aa57ef4090566b4cca6fee29528f59f28c40" - }, - "cpython-3.9.18-linux-i686-gnu": { - "name": "cpython", - "arch": "i686", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 9, - "patch": 18, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.9.18%2B20230826-i686-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "7faf8fdfbad04e0356a9d52c9b8be4d40ffef85c9ab3e312c45bd64997ef8aa9" - }, - "cpython-3.9.18-windows-i686-none": { - "name": "cpython", - "arch": "i686", - "os": "windows", - "libc": "none", - "major": 3, - "minor": 9, - "patch": 18, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.9.18%2B20240107-i686-pc-windows-msvc-shared-pgo-full.tar.zst", - "sha256": "063c531d3c65f49212c9644ab0f00360a65d7c45bc1e6f78174685e2c165b260" - }, - "cpython-3.9.18-linux-ppc64le-gnu": { - "name": "cpython", - "arch": "ppc64le", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 9, - "patch": 18, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.9.18%2B20240107-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "bb8f31f18719654fd1f06724a4b37bb7a60abf8b6d14cec5fb1ba422bfa9bc31" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "c138eef19229351226a11e752230b8aa9d499ba9720f9f0574fa3260ccacb99b" }, "cpython-3.9.18-linux-s390x-gnu": { "name": "cpython", @@ -3230,19 +2152,30 @@ "major": 3, "minor": 9, "patch": 18, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.9.18%2B20240107-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "70e4418ebabe2d61b8d09c9b9a9b2e00b7f6d8658b37de2a4ff9fe8c24bc2f1d" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "dd05eff699ce5a7eee545bc05e4869c4e64ee02bf0c70691bcee215604c6b393" }, - "cpython-3.9.18-darwin-x86_64-none": { + "cpython-3.9.18-linux-x86-gnu": { "name": "cpython", - "arch": "x86_64", - "os": "darwin", + "arch": "x86", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 18, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.9.18%2B20230826-i686-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "7faf8fdfbad04e0356a9d52c9b8be4d40ffef85c9ab3e312c45bd64997ef8aa9" + }, + "cpython-3.9.18-windows-x86-none": { + "name": "cpython", + "arch": "x86", + "os": "windows", "libc": "none", "major": 3, "minor": 9, "patch": 18, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.9.18%2B20240107-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "aa2e549186ab9f831169ccc32965c81ba0fa62e471129f51988f40eaa9552309" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + "sha256": "212d413ab6f854f588cf368fdd2aa140bb7c7ee930e3f7ac1002cba1e50e9685" }, "cpython-3.9.18-linux-x86_64-gnu": { "name": "cpython", @@ -3252,8 +2185,8 @@ "major": 3, "minor": 9, "patch": 18, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.9.18%2B20240107-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "93ff5a32b0874ea1eb888fb735663a64a26e5fac54079c64fdd48ac379da99d4" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "e1fa92798fab6f3b44a48f24b8e284660c34738d560681b206f0deb0616465f9" }, "cpython-3.9.18-linux-x86_64-musl": { "name": "cpython", @@ -3263,8 +2196,19 @@ "major": 3, "minor": 9, "patch": 18, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.9.18%2B20240107-x86_64-unknown-linux-musl-lto-full.tar.zst", - "sha256": "ea096a98314f31186e1b0a6767d0a9b7b936a2d4003296887fd7e9fad0f50fd5" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-x86_64-unknown-linux-musl-lto-full.tar.zst", + "sha256": "8bf88ae2100e609902d98ec775468e3a41a834f6528e632d6d971f5f75340336" + }, + "cpython-3.9.18-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 18, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "146537b9b4a1baa672eed94373e149ca1ee339c4df121e8916d8436265e5245e" }, "cpython-3.9.18-windows-x86_64-none": { "name": "cpython", @@ -3274,89 +2218,12 @@ "major": 3, "minor": 9, "patch": 18, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.9.18%2B20240107-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", - "sha256": "3b9c7d6ed94260b83ed8f44ee9a7b8fce392259ce6591e538601f7353061a884" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + "sha256": "924ed4f375ef73c73a725ef18ec6a72726456673d5a116f132f60860a25dd674" }, - "cpython-3.9.18-linux-x86_64_v2-gnu": { + "cpython-3.9.17-linux-aarch64-gnu": { "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 9, - "patch": 18, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.9.18%2B20240107-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "2520fa2853ddc81e43418b48217128a2a9d8b7e2a282b838ba7d3e66e2776f8d" - }, - "cpython-3.9.18-linux-x86_64_v2-musl": { - "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 9, - "patch": 18, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.9.18%2B20240107-x86_64_v2-unknown-linux-musl-lto-full.tar.zst", - "sha256": "91e27451e1002ba29cf2e0e969654cf624ab72f3fe1c2adfd1a3aeaa1030ff0d" - }, - "cpython-3.9.18-linux-x86_64_v3-gnu": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 9, - "patch": 18, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.9.18%2B20240107-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "3ed47287cabecd02a074fad0ff8e2074ff39881e2c343e732cd1cdc62edefcbb" - }, - "cpython-3.9.18-linux-x86_64_v3-musl": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 9, - "patch": 18, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.9.18%2B20240107-x86_64_v3-unknown-linux-musl-lto-full.tar.zst", - "sha256": "20d61008ab8e1492a345521bf17f5b132d7d73d8ffd6f0dfb56d69506d2a6e73" - }, - "cpython-3.9.18-linux-x86_64_v4-gnu": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 9, - "patch": 18, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.9.18%2B20240107-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "fca812213f43eeedc67d42d6589a209a4f921b26b11454a7de361a64c8c9187b" - }, - "cpython-3.9.18-linux-x86_64_v4-musl": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 9, - "patch": 18, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.9.18%2B20240107-x86_64_v4-unknown-linux-musl-lto-full.tar.zst", - "sha256": "bc8fbbb07f7bd22faee3e90886a9c46bc1393e695e93e67fb5cea61e4775048a" - }, - "cpython-3.9.17-darwin-arm64-none": { - "name": "cpython", - "arch": "arm64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 9, - "patch": 17, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "2902e2a0add6d584999fa27896b721a359f7308404e936e80b01b07aa06e8f5e" - }, - "cpython-3.9.17-linux-arm64-gnu": { - "name": "cpython", - "arch": "arm64", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, @@ -3365,31 +2232,20 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-aarch64-unknown-linux-gnu-debug-full.tar.zst", "sha256": "1f6c43d92ba9f4e15149cf5db6ecde11e05eee92c070a085e44f46c559520257" }, - "cpython-3.9.17-linux-i686-gnu": { + "cpython-3.9.17-macos-aarch64-none": { "name": "cpython", - "arch": "i686", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 9, - "patch": 17, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-i686-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "1a9b7edc16683410c27bc5b4b1761143bef7831a1ad172e7e3581c152c6837a2" - }, - "cpython-3.9.17-windows-i686-none": { - "name": "cpython", - "arch": "i686", - "os": "windows", + "arch": "aarch64", + "os": "macos", "libc": "none", "major": 3, "minor": 9, "patch": 17, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-i686-pc-windows-msvc-shared-pgo-full.tar.zst", - "sha256": "ffac27bfb8bdf615d0fc6cbbe0becaa65b6ae73feec417919601497fce2be0ab" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "2902e2a0add6d584999fa27896b721a359f7308404e936e80b01b07aa06e8f5e" }, - "cpython-3.9.17-linux-ppc64le-gnu": { + "cpython-3.9.17-linux-powerpc64le-gnu": { "name": "cpython", - "arch": "ppc64le", + "arch": "powerpc64le", "os": "linux", "libc": "gnu", "major": 3, @@ -3409,16 +2265,27 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-s390x-unknown-linux-gnu-debug-full.tar.zst", "sha256": "bf8c846c1a4e52355d4ae294f4e1da9587d5415467eb6890bdf0f5a4c8cda396" }, - "cpython-3.9.17-darwin-x86_64-none": { + "cpython-3.9.17-linux-x86-gnu": { "name": "cpython", - "arch": "x86_64", - "os": "darwin", + "arch": "x86", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 17, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-i686-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "1a9b7edc16683410c27bc5b4b1761143bef7831a1ad172e7e3581c152c6837a2" + }, + "cpython-3.9.17-windows-x86-none": { + "name": "cpython", + "arch": "x86", + "os": "windows", "libc": "none", "major": 3, "minor": 9, "patch": 17, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "ba04f9813b78b61d60a27857949403a1b1dd8ac053e1f1aff72fe2689c238d3c" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + "sha256": "ffac27bfb8bdf615d0fc6cbbe0becaa65b6ae73feec417919601497fce2be0ab" }, "cpython-3.9.17-linux-x86_64-gnu": { "name": "cpython", @@ -3442,6 +2309,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-x86_64-unknown-linux-musl-lto-full.tar.zst", "sha256": "8496473a97e1dd43bf96fc1cf19f02f305608ef6a783e0112274e0ae01df4f2a" }, + "cpython-3.9.17-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 17, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "ba04f9813b78b61d60a27857949403a1b1dd8ac053e1f1aff72fe2689c238d3c" + }, "cpython-3.9.17-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -3453,86 +2331,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "209983b8227e4755197dfed4f6887e45b6a133f61e7eb913c0a934b0d0c3e00f" }, - "cpython-3.9.17-linux-x86_64_v2-gnu": { + "cpython-3.9.16-linux-aarch64-gnu": { "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 9, - "patch": 17, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "3faab5cb6c0bbb601be92575540036454fb34c0d7e4045760c62d782d8455f88" - }, - "cpython-3.9.17-linux-x86_64_v2-musl": { - "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 9, - "patch": 17, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-x86_64_v2-unknown-linux-musl-lto-full.tar.zst", - "sha256": "64dbebbc6f9d5444156d641b17f1874f6f287f2d524435cfb9f1a580533146c7" - }, - "cpython-3.9.17-linux-x86_64_v3-gnu": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 9, - "patch": 17, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "c38db0bd66d15fd2f29bd6299dd74040a93fbe780c811827c3f3eca04f9aa3ca" - }, - "cpython-3.9.17-linux-x86_64_v3-musl": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 9, - "patch": 17, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-x86_64_v3-unknown-linux-musl-lto-full.tar.zst", - "sha256": "292f0c45e88866e4a8b122db5248c9d90f73e4d43af906eb97ff084d6e9d5d62" - }, - "cpython-3.9.17-linux-x86_64_v4-gnu": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 9, - "patch": 17, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "c9188a2b7edfb9e24d7cdfde239363ff8c79904901201041c0400d3f82923cb5" - }, - "cpython-3.9.17-linux-x86_64_v4-musl": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 9, - "patch": 17, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-x86_64_v4-unknown-linux-musl-lto-full.tar.zst", - "sha256": "3f397ca020c9aa46c792c20aa5a8e96f922a5e77d02b6f87413597b88efe7f51" - }, - "cpython-3.9.16-darwin-arm64-none": { - "name": "cpython", - "arch": "arm64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 9, - "patch": 16, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "c86ed2bf3ff290af10f96183c53e2b29e954abb520806fbe01d3ef2f9d809a75" - }, - "cpython-3.9.16-linux-arm64-gnu": { - "name": "cpython", - "arch": "arm64", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, @@ -3541,31 +2342,20 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-aarch64-unknown-linux-gnu-debug-full.tar.zst", "sha256": "57ac7ce9d3dd32c1277ee7295daf5ad7b5ecc929e65b31f11b1e7b94cd355ed1" }, - "cpython-3.9.16-linux-i686-gnu": { + "cpython-3.9.16-macos-aarch64-none": { "name": "cpython", - "arch": "i686", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 9, - "patch": 16, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-i686-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "e2a0226165550492e895369ee1b69a515f82e12cb969656012ee8e1543409661" - }, - "cpython-3.9.16-windows-i686-none": { - "name": "cpython", - "arch": "i686", - "os": "windows", + "arch": "aarch64", + "os": "macos", "libc": "none", "major": 3, "minor": 9, "patch": 16, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-i686-pc-windows-msvc-shared-pgo-full.tar.zst", - "sha256": "d7994b5febb375bb131d028f98f4902ba308913c77095457ccd159b521e20c52" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "c86ed2bf3ff290af10f96183c53e2b29e954abb520806fbe01d3ef2f9d809a75" }, - "cpython-3.9.16-linux-ppc64le-gnu": { + "cpython-3.9.16-linux-powerpc64le-gnu": { "name": "cpython", - "arch": "ppc64le", + "arch": "powerpc64le", "os": "linux", "libc": "gnu", "major": 3, @@ -3574,16 +2364,27 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-ppc64le-unknown-linux-gnu-debug-full.tar.zst", "sha256": "8b2e7ddc6feb116dfa6829cfc478be90a374dc5ce123a98bc77e86d0e93e917d" }, - "cpython-3.9.16-darwin-x86_64-none": { + "cpython-3.9.16-linux-x86-gnu": { "name": "cpython", - "arch": "x86_64", - "os": "darwin", + "arch": "x86", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 16, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-i686-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "e2a0226165550492e895369ee1b69a515f82e12cb969656012ee8e1543409661" + }, + "cpython-3.9.16-windows-x86-none": { + "name": "cpython", + "arch": "x86", + "os": "windows", "libc": "none", "major": 3, "minor": 9, "patch": 16, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "5809626ca7907c8ea397341f3d5eafb280ed5b19cc5622e57b14d9b4362eba50" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + "sha256": "d7994b5febb375bb131d028f98f4902ba308913c77095457ccd159b521e20c52" }, "cpython-3.9.16-linux-x86_64-gnu": { "name": "cpython", @@ -3607,6 +2408,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-x86_64-unknown-linux-musl-lto-full.tar.zst", "sha256": "c397f292021b33531248ad8fede24ef6249cc6172347b2017f92b4a71845b8ed" }, + "cpython-3.9.16-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 16, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "5809626ca7907c8ea397341f3d5eafb280ed5b19cc5622e57b14d9b4362eba50" + }, "cpython-3.9.16-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -3618,86 +2430,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "199c821505e287c004c3796ba9ac4bd129d7793e1d833e9a7672ed03bdb397d4" }, - "cpython-3.9.16-linux-x86_64_v2-gnu": { + "cpython-3.9.15-linux-aarch64-gnu": { "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 9, - "patch": 16, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "35dcaff7a658b8c6bf5ffe1c4e2db95df5d647d28b4880a90f09bf3a70f42e8b" - }, - "cpython-3.9.16-linux-x86_64_v2-musl": { - "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 9, - "patch": 16, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-x86_64_v2-unknown-linux-musl-lto-full.tar.zst", - "sha256": "73234f487b6be3c38a0f13200282d85866a248ed2ff8194b609dcf319af5e8d4" - }, - "cpython-3.9.16-linux-x86_64_v3-gnu": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 9, - "patch": 16, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "4c00f35b9218e19065ba9e06c123505f056173a4f9b7eeb72ef768d0dfe8f867" - }, - "cpython-3.9.16-linux-x86_64_v3-musl": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 9, - "patch": 16, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-x86_64_v3-unknown-linux-musl-lto-full.tar.zst", - "sha256": "9bdf930d1b0ecd4b3e98a2b787aa25300ada62a42c20ac4d4c3bd923486e342b" - }, - "cpython-3.9.16-linux-x86_64_v4-gnu": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 9, - "patch": 16, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "b61ccbfd13415891a9120eaaa37ad5e3f6fac3bfad6123c951c955dbaafec6c3" - }, - "cpython-3.9.16-linux-x86_64_v4-musl": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 9, - "patch": 16, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-x86_64_v4-unknown-linux-musl-lto-full.tar.zst", - "sha256": "ffc2f51ce20eefaecc9d70daca4e73c051233006cd3d7a2f8dfca1189ed5584f" - }, - "cpython-3.9.15-darwin-arm64-none": { - "name": "cpython", - "arch": "arm64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 9, - "patch": 15, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "1799b97619572ad595cd6d309bbcc57606138a57f4e90af04e04ee31d187e22f" - }, - "cpython-3.9.15-linux-arm64-gnu": { - "name": "cpython", - "arch": "arm64", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, @@ -3706,9 +2441,20 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-aarch64-unknown-linux-gnu-debug-full.tar.zst", "sha256": "0da1f081313b088c1381206e698e70fffdffc01e1b2ce284145c24ee5f5b4cbb" }, - "cpython-3.9.15-linux-i686-gnu": { + "cpython-3.9.15-macos-aarch64-none": { "name": "cpython", - "arch": "i686", + "arch": "aarch64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 15, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "1799b97619572ad595cd6d309bbcc57606138a57f4e90af04e04ee31d187e22f" + }, + "cpython-3.9.15-linux-x86-gnu": { + "name": "cpython", + "arch": "x86", "os": "linux", "libc": "gnu", "major": 3, @@ -3717,9 +2463,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-i686-unknown-linux-gnu-debug-full.tar.zst", "sha256": "cbc6a14835022d89f4ca6042a06c4959d74d4bbb58e70bdbe0fe8d2928934922" }, - "cpython-3.9.15-windows-i686-none": { + "cpython-3.9.15-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -3728,17 +2474,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-i686-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "a5ad2a6ace97d458ad7b2857fba519c5c332362442d88e2b23ed818f243b8a78" }, - "cpython-3.9.15-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 9, - "patch": 15, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "50fd795eac55c4485e2fefbb8e7b365461817733c45becb50a7480a243e6000e" - }, "cpython-3.9.15-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -3761,6 +2496,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-x86_64-unknown-linux-musl-lto-full.tar.zst", "sha256": "4597f0009cfb52e748a57badab28edf84a263390b777c182b18c36d666a01440" }, + "cpython-3.9.15-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 15, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "50fd795eac55c4485e2fefbb8e7b365461817733c45becb50a7480a243e6000e" + }, "cpython-3.9.15-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -3772,86 +2518,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "d0f3ce1748a51779eedf155aea617c39426e3f7bfd93b4876cb172576b6e8bda" }, - "cpython-3.9.15-linux-x86_64_v2-gnu": { + "cpython-3.9.14-linux-aarch64-gnu": { "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 9, - "patch": 15, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "6222695583b32845fbbf17ec4a43981126ce0d6bb08b47a8c8853396e0f5e9f7" - }, - "cpython-3.9.15-linux-x86_64_v2-musl": { - "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 9, - "patch": 15, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-x86_64_v2-unknown-linux-musl-lto-full.tar.zst", - "sha256": "440a4cffe00f2989628d1ff5ff0f2102998a328b3a0f605ea2fe110f93597598" - }, - "cpython-3.9.15-linux-x86_64_v3-gnu": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 9, - "patch": 15, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "b5fbe25175f4338d6f5bb6f13ea6d714a4373a69b4d193c5037e8424ce5f53fb" - }, - "cpython-3.9.15-linux-x86_64_v3-musl": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 9, - "patch": 15, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-x86_64_v3-unknown-linux-musl-lto-full.tar.zst", - "sha256": "1289f63b4c3cc8a104e891b541a29642306c4bce7d0c82f571effbf52d5e2c50" - }, - "cpython-3.9.15-linux-x86_64_v4-gnu": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 9, - "patch": 15, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "5bc44523a504d220e9af9acaa141ba9ac67e2fe998817cbca99b1bcc9a85d3cd" - }, - "cpython-3.9.15-linux-x86_64_v4-musl": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 9, - "patch": 15, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-x86_64_v4-unknown-linux-musl-lto-full.tar.zst", - "sha256": "77217f3033392b7bb17201d57b19259cd608d1f357d4f3ce2165e1a89bef1b14" - }, - "cpython-3.9.14-darwin-arm64-none": { - "name": "cpython", - "arch": "arm64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 9, - "patch": 14, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "6b9d2ff724aff88a4d0790c86f2e5d17037736f35a796e71732624191ddd6e38" - }, - "cpython-3.9.14-linux-arm64-gnu": { - "name": "cpython", - "arch": "arm64", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, @@ -3860,9 +2529,20 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-aarch64-unknown-linux-gnu-debug-full.tar.zst", "sha256": "3020c743e4742d6e0e5d27fcb166c694bf1d9565369b2eaee9d68434304aebd2" }, - "cpython-3.9.14-linux-i686-gnu": { + "cpython-3.9.14-macos-aarch64-none": { "name": "cpython", - "arch": "i686", + "arch": "aarch64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 14, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "6b9d2ff724aff88a4d0790c86f2e5d17037736f35a796e71732624191ddd6e38" + }, + "cpython-3.9.14-linux-x86-gnu": { + "name": "cpython", + "arch": "x86", "os": "linux", "libc": "gnu", "major": 3, @@ -3871,9 +2551,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-i686-unknown-linux-gnu-debug-full.tar.zst", "sha256": "83a11c4f3d1c0ec39119bd0513a8684b59b68c3989cf1e5042d7417d4770c904" }, - "cpython-3.9.14-windows-i686-none": { + "cpython-3.9.14-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -3882,17 +2562,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-i686-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "fae990eb312314102408cb0c0453dae670f0eb468f4cbf3e72327ceaa1276b46" }, - "cpython-3.9.14-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 9, - "patch": 14, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "186155e19b63da3248347415f888fbcf982c7587f6f927922ca243ae3f23ed2f" - }, "cpython-3.9.14-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -3915,6 +2584,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-x86_64-unknown-linux-musl-lto-full.tar.zst", "sha256": "5638c12d47eb81adf96615cea8a5a61e8414c3ac03a8b570d30ae9998cb6d030" }, + "cpython-3.9.14-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 14, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "186155e19b63da3248347415f888fbcf982c7587f6f927922ca243ae3f23ed2f" + }, "cpython-3.9.14-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -3926,86 +2606,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "49f27a3a18b4c2d765b0656c6529378a20b3e37fdb0aca9490576ff7a67243a9" }, - "cpython-3.9.14-linux-x86_64_v2-gnu": { + "cpython-3.9.13-linux-aarch64-gnu": { "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 9, - "patch": 14, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "a78cb80ca372ea98aca28d33a220b3fe91761c6dadaf92f646b82f3b569822c1" - }, - "cpython-3.9.14-linux-x86_64_v2-musl": { - "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 9, - "patch": 14, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-x86_64_v2-unknown-linux-musl-lto-full.tar.zst", - "sha256": "d20c9ff5f3474a80948cc4533a799573e5e3c62cd0477224f5e1ec1da656c6be" - }, - "cpython-3.9.14-linux-x86_64_v3-gnu": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 9, - "patch": 14, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "53d3c637264a57317b304a88ddd418463fe0b37b874495e3ae55940b5c62b363" - }, - "cpython-3.9.14-linux-x86_64_v3-musl": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 9, - "patch": 14, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-x86_64_v3-unknown-linux-musl-lto-full.tar.zst", - "sha256": "3ad497929e6e14596a18f4dfd09d48fb4bbefff23f06e5ca5b4d9f79ac31f646" - }, - "cpython-3.9.14-linux-x86_64_v4-gnu": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 9, - "patch": 14, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "92e5a9fd569c0b1c161958e2e1d3198cb4e27b524d02cf3008b260dba658961f" - }, - "cpython-3.9.14-linux-x86_64_v4-musl": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 9, - "patch": 14, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-x86_64_v4-unknown-linux-musl-lto-full.tar.zst", - "sha256": "35681efee0142c0873cbf27d5e54eddb13e61439a563b358a02fed9909011d0e" - }, - "cpython-3.9.13-darwin-arm64-none": { - "name": "cpython", - "arch": "arm64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 9, - "patch": 13, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "8612e9328663c0747d1eae36b218d11c2fbc53c39ec7512c7ad6b1b57374a5dc" - }, - "cpython-3.9.13-linux-arm64-gnu": { - "name": "cpython", - "arch": "arm64", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, @@ -4014,9 +2617,20 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-aarch64-unknown-linux-gnu-debug-full.tar.zst", "sha256": "8c706ebb2c8970da4fbec95b0520b4632309bc6a3e115cf309e38f181b553d14" }, - "cpython-3.9.13-linux-i686-gnu": { + "cpython-3.9.13-macos-aarch64-none": { "name": "cpython", - "arch": "i686", + "arch": "aarch64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 13, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "8612e9328663c0747d1eae36b218d11c2fbc53c39ec7512c7ad6b1b57374a5dc" + }, + "cpython-3.9.13-linux-x86-gnu": { + "name": "cpython", + "arch": "x86", "os": "linux", "libc": "gnu", "major": 3, @@ -4025,9 +2639,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-i686-unknown-linux-gnu-debug-full.tar.zst", "sha256": "7d33637b48c45acf8805d5460895dca29bf2740fd2cf502fde6c6a00637db6b5" }, - "cpython-3.9.13-windows-i686-none": { + "cpython-3.9.13-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -4036,17 +2650,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-i686-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "3860abee418825c6a33f76fe88773fb05eb4bc724d246f1af063106d9ea3f999" }, - "cpython-3.9.13-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 9, - "patch": 13, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "16d21a6e62c19c574a4a225961e80966449095a8eb2c4150905e30d4e807cf86" - }, "cpython-3.9.13-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -4069,6 +2672,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-x86_64-unknown-linux-musl-lto-full.tar.zst", "sha256": "c7e48545a8291fe1be909c4454b5c48df0ee4e69e2b5e13b6144b4199c31f895" }, + "cpython-3.9.13-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 13, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "16d21a6e62c19c574a4a225961e80966449095a8eb2c4150905e30d4e807cf86" + }, "cpython-3.9.13-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -4080,86 +2694,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "6ef2b164cae483c61da30fb6d245762b8d6d91346d66cb421989d6d1462e5a48" }, - "cpython-3.9.13-linux-x86_64_v2-gnu": { + "cpython-3.9.12-linux-aarch64-gnu": { "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 9, - "patch": 13, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "37ace8159aa2f5157abea2c0d854434f21e6676b90eeaf58d313778f64540ca7" - }, - "cpython-3.9.13-linux-x86_64_v2-musl": { - "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 9, - "patch": 13, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-x86_64_v2-unknown-linux-musl-lto-full.tar.zst", - "sha256": "0648910f5f0341f192da23a6e58aedbfdbd0ee17d41207fcaf25c957f295dfa7" - }, - "cpython-3.9.13-linux-x86_64_v3-gnu": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 9, - "patch": 13, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "59a24171f794139b41d47b3e734c82d02bc11956a4c092a3d28d94a5d0ab7f54" - }, - "cpython-3.9.13-linux-x86_64_v3-musl": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 9, - "patch": 13, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-x86_64_v3-unknown-linux-musl-lto-full.tar.zst", - "sha256": "b6f756062f9df0791e0148f0d37c62c3fd13b49e8fc8b18805dcac3a010c67ef" - }, - "cpython-3.9.13-linux-x86_64_v4-gnu": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 9, - "patch": 13, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "920fd94d4352495a804d4ee725764ae7499b29c5f1c15d11cc4380a873498dbf" - }, - "cpython-3.9.13-linux-x86_64_v4-musl": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 9, - "patch": 13, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-x86_64_v4-unknown-linux-musl-lto-full.tar.zst", - "sha256": "e2a41aab57dc2c0f61c02702510722456fb06947b620fef0e5ad2c649d4701fa" - }, - "cpython-3.9.12-darwin-arm64-none": { - "name": "cpython", - "arch": "arm64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 9, - "patch": 12, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "b3d09b3c12295e893ee8f2cb60e8af94d8a21fc5c65016282925220f5270b85b" - }, - "cpython-3.9.12-linux-arm64-gnu": { - "name": "cpython", - "arch": "arm64", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, @@ -4168,9 +2705,20 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-aarch64-unknown-linux-gnu-debug-full.tar.zst", "sha256": "202ef64e43570f0843ff5895fd9c1a2c36a96b48d52842fa95842d7d11025b20" }, - "cpython-3.9.12-linux-i686-gnu": { + "cpython-3.9.12-macos-aarch64-none": { "name": "cpython", - "arch": "i686", + "arch": "aarch64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 12, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "b3d09b3c12295e893ee8f2cb60e8af94d8a21fc5c65016282925220f5270b85b" + }, + "cpython-3.9.12-linux-x86-gnu": { + "name": "cpython", + "arch": "x86", "os": "linux", "libc": "gnu", "major": 3, @@ -4179,9 +2727,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-i686-unknown-linux-gnu-debug-full.tar.zst", "sha256": "e52fdbe61dea847323cd6e81142d16a571dca9c0bcde3bfe5ae75a8d3d1a3bf4" }, - "cpython-3.9.12-windows-i686-none": { + "cpython-3.9.12-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -4190,17 +2738,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-i686-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "361b8fa66d6b5d5623fd5e64af29cf220a693ba86d031bf7ce2b61e1ea50f568" }, - "cpython-3.9.12-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 9, - "patch": 12, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "825970ae30ae7a30a5b039aa25f1b965e2d1fe046e196e61fa2a3af8fef8c5d9" - }, "cpython-3.9.12-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -4223,6 +2760,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-x86_64-unknown-linux-musl-lto-full.tar.zst", "sha256": "eb122ab2bf0b2d71926984bc7cf5fef65b415abfe01a0974ed6c1a2502fac764" }, + "cpython-3.9.12-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 12, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "825970ae30ae7a30a5b039aa25f1b965e2d1fe046e196e61fa2a3af8fef8c5d9" + }, "cpython-3.9.12-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -4234,86 +2782,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "c49f8b07e9c4dcfd7a5b55c131e882a4ebdf9f37fef1c7820c3ce9eb23bab8ab" }, - "cpython-3.9.12-linux-x86_64_v2-gnu": { + "cpython-3.9.11-linux-aarch64-gnu": { "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 9, - "patch": 12, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "53dadc107ce8d1ba5f94ca87edbeef485c8a07b2c0e942a99a7c7e163fafb6f4" - }, - "cpython-3.9.12-linux-x86_64_v2-musl": { - "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 9, - "patch": 12, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-x86_64_v2-unknown-linux-musl-lto-full.tar.zst", - "sha256": "347357fa079cdc17b6becd521e64911661c6263e64f161721cf0db39466ee4c3" - }, - "cpython-3.9.12-linux-x86_64_v3-gnu": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 9, - "patch": 12, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "e7ba8a790a3194a0847ef0b09b8dd8542b9c2ca689b2f4ee4ecb771a44ea41f2" - }, - "cpython-3.9.12-linux-x86_64_v3-musl": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 9, - "patch": 12, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-x86_64_v3-unknown-linux-musl-lto-full.tar.zst", - "sha256": "06011e3ea1f3265c72a15d133a76a3d308377228ac6392206348312e76c321d9" - }, - "cpython-3.9.12-linux-x86_64_v4-gnu": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 9, - "patch": 12, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "afdd26e418ab5a2e5e10b42eb98c79bafd7c198363f70aad8628266b7f3532ab" - }, - "cpython-3.9.12-linux-x86_64_v4-musl": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 9, - "patch": 12, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-x86_64_v4-unknown-linux-musl-lto-full.tar.zst", - "sha256": "281509ccab077369f69cdd88c6d82488dfa65a626350cc7290ca13954f305b4c" - }, - "cpython-3.9.11-darwin-arm64-none": { - "name": "cpython", - "arch": "arm64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 9, - "patch": 11, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "6d9f20607a20e2cc5ad1428f7366832dc68403fc15f2e4f195817187e7b6dbbf" - }, - "cpython-3.9.11-linux-arm64-gnu": { - "name": "cpython", - "arch": "arm64", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, @@ -4322,9 +2793,20 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-aarch64-unknown-linux-gnu-debug-full.tar.zst", "sha256": "e1f3ae07a28a687f8602fb4d29a1b72cc5e113c61dc6769d0d85081ab3e09c71" }, - "cpython-3.9.11-linux-i686-gnu": { + "cpython-3.9.11-macos-aarch64-none": { "name": "cpython", - "arch": "i686", + "arch": "aarch64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 11, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "6d9f20607a20e2cc5ad1428f7366832dc68403fc15f2e4f195817187e7b6dbbf" + }, + "cpython-3.9.11-linux-x86-gnu": { + "name": "cpython", + "arch": "x86", "os": "linux", "libc": "gnu", "major": 3, @@ -4333,9 +2815,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-i686-unknown-linux-gnu-debug-full.tar.zst", "sha256": "0be0a5f524c68d521be2417565ca43f3125b1845f996d6d62266aa431e673f93" }, - "cpython-3.9.11-windows-i686-none": { + "cpython-3.9.11-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -4344,17 +2826,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-i686-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "f06338422e7e3ad25d0cd61864bdb36d565d46440dd363cbb98821d388ed377a" }, - "cpython-3.9.11-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 9, - "patch": 11, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "35e649618e7e602778e72b91c9c50c97d01a0c3509d16225a1f41dd0fd6575f0" - }, "cpython-3.9.11-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -4377,6 +2848,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-x86_64-unknown-linux-musl-lto-full.tar.zst", "sha256": "d83eb5c897120e32287cb6fe5c24dd2dcae00878b3f9d7002590d468bd5de0f1" }, + "cpython-3.9.11-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 11, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "35e649618e7e602778e72b91c9c50c97d01a0c3509d16225a1f41dd0fd6575f0" + }, "cpython-3.9.11-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -4388,86 +2870,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "1fe3c519d43737dc7743aec43f72735e1429c79e06e3901b21bad67b642f1a10" }, - "cpython-3.9.11-linux-x86_64_v2-gnu": { + "cpython-3.9.10-linux-aarch64-gnu": { "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 9, - "patch": 11, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "7d7a745a3d6eade5fd7352b444c4c91ce562a2d188716d6995155c2ed5d1e337" - }, - "cpython-3.9.11-linux-x86_64_v2-musl": { - "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 9, - "patch": 11, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-x86_64_v2-unknown-linux-musl-lto-full.tar.zst", - "sha256": "6ce18f9b6808aece6e45f2859cd12e0420c2a3372b9a9130503d8f5689d68f89" - }, - "cpython-3.9.11-linux-x86_64_v3-gnu": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 9, - "patch": 11, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "ea43a4a473ebf99af4f7b9ae1d00e3bd6c5a2a329096e5657590ed5d823903dd" - }, - "cpython-3.9.11-linux-x86_64_v3-musl": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 9, - "patch": 11, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-x86_64_v3-unknown-linux-musl-lto-full.tar.zst", - "sha256": "48452b551065ae3040967cc47c36fe7eb42ea4f8be07c3c19a47ceab9a5957ea" - }, - "cpython-3.9.11-linux-x86_64_v4-gnu": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 9, - "patch": 11, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "a1e7c4554d200d4fa50dc43cda6db020df99f75bb105dd7c44139c44d5040fae" - }, - "cpython-3.9.11-linux-x86_64_v4-musl": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 9, - "patch": 11, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-x86_64_v4-unknown-linux-musl-lto-full.tar.zst", - "sha256": "395a2c3b64446af61100ccf8fdce7dfa3cff91fd72ca0b0a1147a5aac5f7aa9c" - }, - "cpython-3.9.10-darwin-arm64-none": { - "name": "cpython", - "arch": "arm64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 9, - "patch": 10, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "ba1b63600ed8d9f3b8d739657bd8e7f5ca167de29a1a58d04b2cd9940b289464" - }, - "cpython-3.9.10-linux-arm64-gnu": { - "name": "cpython", - "arch": "arm64", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, @@ -4476,9 +2881,20 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-aarch64-unknown-linux-gnu-debug-full.tar.zst", "sha256": "8bf7ac2cd5825b8fde0a6e535266a57c97e82fd5a97877940920b403ca5e53d7" }, - "cpython-3.9.10-linux-i686-gnu": { + "cpython-3.9.10-macos-aarch64-none": { "name": "cpython", - "arch": "i686", + "arch": "aarch64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 10, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "ba1b63600ed8d9f3b8d739657bd8e7f5ca167de29a1a58d04b2cd9940b289464" + }, + "cpython-3.9.10-linux-x86-gnu": { + "name": "cpython", + "arch": "x86", "os": "linux", "libc": "gnu", "major": 3, @@ -4487,9 +2903,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-i686-unknown-linux-gnu-debug-full.tar.zst", "sha256": "3e3bf4d3e71a2131e6c064d1e5019f58cb9c58fdceae4b76b26ac978a6d49aad" }, - "cpython-3.9.10-windows-i686-none": { + "cpython-3.9.10-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -4498,17 +2914,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-i686-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "7f3ca15f89775f76a32e6ea9b2c9778ebf0cde753c5973d4493959e75dd92488" }, - "cpython-3.9.10-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 9, - "patch": 10, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "ef2f090ff920708b4b9aa5d6adf0dc930c09a4bf638d71e6883091f9e629193d" - }, "cpython-3.9.10-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -4531,6 +2936,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-x86_64-unknown-linux-musl-lto-full.tar.zst", "sha256": "2744b817f249c0563b844cddd5aba4cc2fd449489b8bd59980d7a31de3a4ece1" }, + "cpython-3.9.10-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 10, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "ef2f090ff920708b4b9aa5d6adf0dc930c09a4bf638d71e6883091f9e629193d" + }, "cpython-3.9.10-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -4542,86 +2958,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "56b2738599131d03b39b914ea0597862fd9096e5e64816bf19466bf026e74f0c" }, - "cpython-3.9.10-linux-x86_64_v2-gnu": { + "cpython-3.9.7-linux-aarch64-gnu": { "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 9, - "patch": 10, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "8317251d7480b9fc8b4f0b76dbb91b339bc55760cc92715e66f91a3055396497" - }, - "cpython-3.9.10-linux-x86_64_v2-musl": { - "name": "cpython", - "arch": "x86_64_v2", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 9, - "patch": 10, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-x86_64_v2-unknown-linux-musl-lto-full.tar.zst", - "sha256": "8aca72895482f0730ac20f829fd47ce004bb0f52fc99fdd500cf22a2b1288d9a" - }, - "cpython-3.9.10-linux-x86_64_v3-gnu": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 9, - "patch": 10, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "c2a8ef2fd0f2c033371e931c2588444c5872b3c1fe7c787e0139e9ee03177ea6" - }, - "cpython-3.9.10-linux-x86_64_v3-musl": { - "name": "cpython", - "arch": "x86_64_v3", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 9, - "patch": 10, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-x86_64_v3-unknown-linux-musl-lto-full.tar.zst", - "sha256": "c6d9c35ac9e3553eb1f56a955c24487f1910c58827efee846b38ee8dbfef6390" - }, - "cpython-3.9.10-linux-x86_64_v4-gnu": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 9, - "patch": 10, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "162da56c07f3bec783c3c202b274c89ab115dae82ab7783b04404c7e315b819f" - }, - "cpython-3.9.10-linux-x86_64_v4-musl": { - "name": "cpython", - "arch": "x86_64_v4", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 9, - "patch": 10, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-x86_64_v4-unknown-linux-musl-lto-full.tar.zst", - "sha256": "e9c51af9f3e684349a8b2f3422849520d64e1a6b8c3546367fe0e239c2ff7399" - }, - "cpython-3.9.7-darwin-arm64-none": { - "name": "cpython", - "arch": "arm64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 9, - "patch": 7, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.9.7-aarch64-apple-darwin-pgo%2Blto-20211017T1616.tar.zst", - "sha256": null - }, - "cpython-3.9.7-linux-arm64-gnu": { - "name": "cpython", - "arch": "arm64", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, @@ -4630,9 +2969,20 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.9.7-aarch64-unknown-linux-gnu-debug-20211017T1616.tar.zst", "sha256": null }, - "cpython-3.9.7-linux-i686-gnu": { + "cpython-3.9.7-macos-aarch64-none": { "name": "cpython", - "arch": "i686", + "arch": "aarch64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 7, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.9.7-aarch64-apple-darwin-pgo%2Blto-20211017T1616.tar.zst", + "sha256": null + }, + "cpython-3.9.7-linux-x86-gnu": { + "name": "cpython", + "arch": "x86", "os": "linux", "libc": "gnu", "major": 3, @@ -4641,9 +2991,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.9.7-i686-unknown-linux-gnu-debug-20211017T1616.tar.zst", "sha256": null }, - "cpython-3.9.7-windows-i686-none": { + "cpython-3.9.7-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -4652,17 +3002,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.9.7-i686-pc-windows-msvc-shared-pgo-20211017T1616.tar.zst", "sha256": null }, - "cpython-3.9.7-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 9, - "patch": 7, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.9.7-x86_64-apple-darwin-pgo%2Blto-20211017T1616.tar.zst", - "sha256": null - }, "cpython-3.9.7-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -4685,6 +3024,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.9.7-x86_64-unknown-linux-musl-lto-20211017T1616.tar.zst", "sha256": null }, + "cpython-3.9.7-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 7, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.9.7-x86_64-apple-darwin-pgo%2Blto-20211017T1616.tar.zst", + "sha256": null + }, "cpython-3.9.7-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -4696,20 +3046,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.9.7-x86_64-pc-windows-msvc-shared-pgo-20211017T1616.tar.zst", "sha256": null }, - "cpython-3.9.6-darwin-arm64-none": { + "cpython-3.9.6-linux-aarch64-gnu": { "name": "cpython", - "arch": "arm64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 9, - "patch": 6, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.9.6-aarch64-apple-darwin-pgo%2Blto-20210724T1424.tar.zst", - "sha256": null - }, - "cpython-3.9.6-linux-arm64-gnu": { - "name": "cpython", - "arch": "arm64", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, @@ -4718,9 +3057,20 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.9.6-aarch64-unknown-linux-gnu-debug-20210724T1424.tar.zst", "sha256": null }, - "cpython-3.9.6-linux-i686-gnu": { + "cpython-3.9.6-macos-aarch64-none": { "name": "cpython", - "arch": "i686", + "arch": "aarch64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 6, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.9.6-aarch64-apple-darwin-pgo%2Blto-20210724T1424.tar.zst", + "sha256": null + }, + "cpython-3.9.6-linux-x86-gnu": { + "name": "cpython", + "arch": "x86", "os": "linux", "libc": "gnu", "major": 3, @@ -4729,9 +3079,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.9.6-i686-unknown-linux-gnu-debug-20210724T1424.tar.zst", "sha256": null }, - "cpython-3.9.6-windows-i686-none": { + "cpython-3.9.6-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -4740,17 +3090,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.9.6-i686-pc-windows-msvc-shared-pgo-20210724T1424.tar.zst", "sha256": null }, - "cpython-3.9.6-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 9, - "patch": 6, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.9.6-x86_64-apple-darwin-pgo%2Blto-20210724T1424.tar.zst", - "sha256": null - }, "cpython-3.9.6-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -4773,6 +3112,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.9.6-x86_64-unknown-linux-musl-lto-20210724T1424.tar.zst", "sha256": null }, + "cpython-3.9.6-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 6, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.9.6-x86_64-apple-darwin-pgo%2Blto-20210724T1424.tar.zst", + "sha256": null + }, "cpython-3.9.6-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -4784,10 +3134,10 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.9.6-x86_64-pc-windows-msvc-shared-pgo-20210724T1424.tar.zst", "sha256": null }, - "cpython-3.9.5-darwin-arm64-none": { + "cpython-3.9.5-macos-aarch64-none": { "name": "cpython", - "arch": "arm64", - "os": "darwin", + "arch": "aarch64", + "os": "macos", "libc": "none", "major": 3, "minor": 9, @@ -4795,9 +3145,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.9.5-aarch64-apple-darwin-pgo%2Blto-20210506T0943.tar.zst", "sha256": null }, - "cpython-3.9.5-linux-i686-gnu": { + "cpython-3.9.5-linux-x86-gnu": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "linux", "libc": "gnu", "major": 3, @@ -4806,9 +3156,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.9.5-i686-unknown-linux-gnu-debug-20210506T0943.tar.zst", "sha256": null }, - "cpython-3.9.5-windows-i686-none": { + "cpython-3.9.5-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -4817,17 +3167,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.9.5-i686-pc-windows-msvc-shared-pgo-20210506T0943.tar.zst", "sha256": null }, - "cpython-3.9.5-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 9, - "patch": 5, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.9.5-x86_64-apple-darwin-pgo%2Blto-20210506T0943.tar.zst", - "sha256": null - }, "cpython-3.9.5-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -4850,6 +3189,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.9.5-x86_64-unknown-linux-musl-lto-20210506T0943.tar.zst", "sha256": null }, + "cpython-3.9.5-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 5, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.9.5-x86_64-apple-darwin-pgo%2Blto-20210506T0943.tar.zst", + "sha256": null + }, "cpython-3.9.5-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -4861,10 +3211,10 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.9.5-x86_64-pc-windows-msvc-shared-pgo-20210506T0943.tar.zst", "sha256": null }, - "cpython-3.9.4-darwin-arm64-none": { + "cpython-3.9.4-macos-aarch64-none": { "name": "cpython", - "arch": "arm64", - "os": "darwin", + "arch": "aarch64", + "os": "macos", "libc": "none", "major": 3, "minor": 9, @@ -4872,9 +3222,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.9.4-aarch64-apple-darwin-pgo%2Blto-20210414T1515.tar.zst", "sha256": null }, - "cpython-3.9.4-linux-i686-gnu": { + "cpython-3.9.4-linux-x86-gnu": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "linux", "libc": "gnu", "major": 3, @@ -4883,9 +3233,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.9.4-i686-unknown-linux-gnu-debug-20210414T1515.tar.zst", "sha256": null }, - "cpython-3.9.4-windows-i686-none": { + "cpython-3.9.4-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -4894,17 +3244,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.9.4-i686-pc-windows-msvc-shared-pgo-20210414T1515.tar.zst", "sha256": null }, - "cpython-3.9.4-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 9, - "patch": 4, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.9.4-x86_64-apple-darwin-pgo%2Blto-20210414T1515.tar.zst", - "sha256": null - }, "cpython-3.9.4-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -4927,6 +3266,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.9.4-x86_64-unknown-linux-musl-lto-20210414T1515.tar.zst", "sha256": null }, + "cpython-3.9.4-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 4, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.9.4-x86_64-apple-darwin-pgo%2Blto-20210414T1515.tar.zst", + "sha256": null + }, "cpython-3.9.4-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -4938,10 +3288,10 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.9.4-x86_64-pc-windows-msvc-shared-pgo-20210414T1515.tar.zst", "sha256": null }, - "cpython-3.9.3-darwin-arm64-none": { + "cpython-3.9.3-macos-aarch64-none": { "name": "cpython", - "arch": "arm64", - "os": "darwin", + "arch": "aarch64", + "os": "macos", "libc": "none", "major": 3, "minor": 9, @@ -4949,9 +3299,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210414/cpython-3.9.3-aarch64-apple-darwin-pgo%2Blto-20210413T2055.tar.zst", "sha256": null }, - "cpython-3.9.3-windows-i686-none": { + "cpython-3.9.3-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -4960,17 +3310,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210414/cpython-3.9.3-i686-pc-windows-msvc-shared-pgo-20210413T2055.tar.zst", "sha256": null }, - "cpython-3.9.3-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 9, - "patch": 3, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210414/cpython-3.9.3-x86_64-apple-darwin-pgo%2Blto-20210413T2055.tar.zst", - "sha256": null - }, "cpython-3.9.3-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -4993,6 +3332,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210414/cpython-3.9.3-x86_64-unknown-linux-musl-lto-20210413T2055.tar.zst", "sha256": null }, + "cpython-3.9.3-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 3, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210414/cpython-3.9.3-x86_64-apple-darwin-pgo%2Blto-20210413T2055.tar.zst", + "sha256": null + }, "cpython-3.9.3-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -5004,10 +3354,10 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210414/cpython-3.9.3-x86_64-pc-windows-msvc-shared-pgo-20210413T2055.tar.zst", "sha256": null }, - "cpython-3.9.2-darwin-arm64-none": { + "cpython-3.9.2-macos-aarch64-none": { "name": "cpython", - "arch": "arm64", - "os": "darwin", + "arch": "aarch64", + "os": "macos", "libc": "none", "major": 3, "minor": 9, @@ -5015,9 +3365,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.9.2-aarch64-apple-darwin-pgo%2Blto-20210327T1202.tar.zst", "sha256": null }, - "cpython-3.9.2-linux-i686-gnu": { + "cpython-3.9.2-linux-x86-gnu": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "linux", "libc": "gnu", "major": 3, @@ -5026,9 +3376,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.9.2-i686-unknown-linux-gnu-debug-20210327T1202.tar.zst", "sha256": null }, - "cpython-3.9.2-windows-i686-none": { + "cpython-3.9.2-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -5037,17 +3387,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.9.2-i686-pc-windows-msvc-shared-pgo-20210327T1202.tar.zst", "sha256": null }, - "cpython-3.9.2-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 9, - "patch": 2, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.9.2-x86_64-apple-darwin-pgo%2Blto-20210327T1202.tar.zst", - "sha256": null - }, "cpython-3.9.2-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -5070,6 +3409,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.9.2-x86_64-unknown-linux-musl-lto-20210327T1202.tar.zst", "sha256": null }, + "cpython-3.9.2-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 2, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.9.2-x86_64-apple-darwin-pgo%2Blto-20210327T1202.tar.zst", + "sha256": null + }, "cpython-3.9.2-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -5081,9 +3431,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.9.2-x86_64-pc-windows-msvc-shared-pgo-20210327T1202.tar.zst", "sha256": null }, - "cpython-3.9.1-windows-i686-none": { + "cpython-3.9.1-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -5092,17 +3442,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210103/cpython-3.9.1-i686-pc-windows-msvc-shared-pgo-20210103T1125.tar.zst", "sha256": null }, - "cpython-3.9.1-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 9, - "patch": 1, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210103/cpython-3.9.1-x86_64-apple-darwin-pgo-20210103T1125.tar.zst", - "sha256": null - }, "cpython-3.9.1-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -5125,6 +3464,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210103/cpython-3.9.1-x86_64-unknown-linux-musl-debug-20210103T1125.tar.zst", "sha256": null }, + "cpython-3.9.1-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 1, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210103/cpython-3.9.1-x86_64-apple-darwin-pgo-20210103T1125.tar.zst", + "sha256": null + }, "cpython-3.9.1-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -5136,9 +3486,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210103/cpython-3.9.1-x86_64-pc-windows-msvc-shared-pgo-20210103T1125.tar.zst", "sha256": null }, - "cpython-3.9.0-windows-i686-none": { + "cpython-3.9.0-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -5147,17 +3497,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20201020/cpython-3.9.0-i686-pc-windows-msvc-shared-pgo-20201021T0245.tar.zst", "sha256": null }, - "cpython-3.9.0-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 9, - "patch": 0, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20201020/cpython-3.9.0-x86_64-apple-darwin-pgo-20201020T0626.tar.zst", - "sha256": null - }, "cpython-3.9.0-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -5180,6 +3519,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20201020/cpython-3.9.0-x86_64-unknown-linux-musl-debug-20201020T0627.tar.zst", "sha256": null }, + "cpython-3.9.0-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 0, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20201020/cpython-3.9.0-x86_64-apple-darwin-pgo-20201020T0626.tar.zst", + "sha256": null + }, "cpython-3.9.0-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -5191,49 +3541,38 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20201020/cpython-3.9.0-x86_64-pc-windows-msvc-shared-pgo-20201021T0245.tar.zst", "sha256": null }, - "cpython-3.8.18-darwin-arm64-none": { + "cpython-3.8.18-linux-aarch64-gnu": { "name": "cpython", - "arch": "arm64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 8, - "patch": 18, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.8.18%2B20240107-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "f426349265897fb3715f19f474f45e17406d77701eb1b60953f9b32e51c779b9" - }, - "cpython-3.8.18-linux-arm64-gnu": { - "name": "cpython", - "arch": "arm64", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, "minor": 8, "patch": 18, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.8.18%2B20240107-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "8aeb623f50866c9ee0260471a664e048b31836ce8793490895d2f7b5b5792e84" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "6d71175a090950c2063680f250b8799ab39eb139aa1721c853d8950aadd1d4e2" }, - "cpython-3.8.18-windows-i686-none": { + "cpython-3.8.18-macos-aarch64-none": { "name": "cpython", - "arch": "i686", + "arch": "aarch64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 8, + "patch": 18, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "c732c068cddcd6a008c1d6d8e35802f5bdc7323bd2eb64e77210d3d5fe4740c2" + }, + "cpython-3.8.18-windows-x86-none": { + "name": "cpython", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, "minor": 8, "patch": 18, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.8.18%2B20240107-i686-pc-windows-msvc-shared-pgo-full.tar.zst", - "sha256": "875983fccf91310805164528150adfde1ac63564d0c3967d48086c6fdb9f568b" - }, - "cpython-3.8.18-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 8, - "patch": 18, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.8.18%2B20240107-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "bfcd4a61998e105a78dbac2b68f1f264cd7bedc5ef11f89ec10911f23b445616" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + "sha256": "9f94c7b54b97116cd308e73cda0b7a7b7fff4515932c5cbba18eeae9ec798351" }, "cpython-3.8.18-linux-x86_64-gnu": { "name": "cpython", @@ -5243,8 +3582,8 @@ "major": 3, "minor": 8, "patch": 18, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.8.18%2B20240107-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "ff02848c574ccc581d21433f20eef333faf06f4fcd35bf2c6264553bec3f1643" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "189ae3b8249c57217e3253f9fc89857e088763cf2107a3f22ab2ac2398f41a65" }, "cpython-3.8.18-linux-x86_64-musl": { "name": "cpython", @@ -5254,8 +3593,19 @@ "major": 3, "minor": 8, "patch": 18, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.8.18%2B20240107-x86_64-unknown-linux-musl-lto-full.tar.zst", - "sha256": "a0f8f26137b9971bfa7fc657b55362dd23a69d9df6d39e35bdae211834350f62" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-x86_64-unknown-linux-musl-lto-full.tar.zst", + "sha256": "fa1bf64cf52d830e7b4bba486c447ee955af644d167df7c42afd169c5dc71d6a" + }, + "cpython-3.8.18-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 8, + "patch": 18, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "4d4b65dd821ce13dcf6dfea3ad5c2d4c3d3a8c2b7dd49fc35c1d79f66238e89b" }, "cpython-3.8.18-windows-x86_64-none": { "name": "cpython", @@ -5265,23 +3615,12 @@ "major": 3, "minor": 8, "patch": 18, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.8.18%2B20240107-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", - "sha256": "0675bf51ad66c149c311e8da4a358b0e0fc28801770163d8053d9aadf6bdb556" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + "sha256": "c63abd9365a13196eb9f65db864f95b85c1f90b770d218c1acd104e6b48a99d3" }, - "cpython-3.8.17-darwin-arm64-none": { + "cpython-3.8.17-linux-aarch64-gnu": { "name": "cpython", - "arch": "arm64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 8, - "patch": 17, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "d08a542bed35fc74ac6e8f6884c8aa29a77ff2f4ed04a06dcf91578dea622f9a" - }, - "cpython-3.8.17-linux-arm64-gnu": { - "name": "cpython", - "arch": "arm64", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, @@ -5290,9 +3629,20 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-aarch64-unknown-linux-gnu-debug-full.tar.zst", "sha256": "eaee5a0b79cc28943e19df54f314634795aee43a6670ce99c0306893a18fa784" }, - "cpython-3.8.17-linux-i686-gnu": { + "cpython-3.8.17-macos-aarch64-none": { "name": "cpython", - "arch": "i686", + "arch": "aarch64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 8, + "patch": 17, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "d08a542bed35fc74ac6e8f6884c8aa29a77ff2f4ed04a06dcf91578dea622f9a" + }, + "cpython-3.8.17-linux-x86-gnu": { + "name": "cpython", + "arch": "x86", "os": "linux", "libc": "gnu", "major": 3, @@ -5301,9 +3651,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-i686-unknown-linux-gnu-debug-full.tar.zst", "sha256": "61ac08680c022f180a32dc82d84548aeb92c7194a489e3b3c532dc48f999d757" }, - "cpython-3.8.17-windows-i686-none": { + "cpython-3.8.17-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -5312,17 +3662,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-i686-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "0931d8ca0e060c6ac1dfcf6bb9b6dea0ac3a9d95daf7906a88128045f4464bf8" }, - "cpython-3.8.17-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 8, - "patch": 17, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "2c4925f5cf37d498e0d8cfe7b10591cc5f0cd80d2582f566b12006e6f96958b1" - }, "cpython-3.8.17-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -5345,6 +3684,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-x86_64-unknown-linux-musl-lto-full.tar.zst", "sha256": "a316ba0b1f425b04c8dfd7a8a18a05d72ae5852732d401b16d7439bdf25caec3" }, + "cpython-3.8.17-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 8, + "patch": 17, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "2c4925f5cf37d498e0d8cfe7b10591cc5f0cd80d2582f566b12006e6f96958b1" + }, "cpython-3.8.17-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -5356,20 +3706,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "68c7d03de5283c4812f2706c797b2139999a28cec647bc662d1459a922059318" }, - "cpython-3.8.16-darwin-arm64-none": { + "cpython-3.8.16-linux-aarch64-gnu": { "name": "cpython", - "arch": "arm64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 8, - "patch": 16, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.8.16%2B20230726-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "bfc91d0a1d6d6dfaa5a31c925aa6adae82bd1ae5eb17813a9f0a50bf9d3e6305" - }, - "cpython-3.8.16-linux-arm64-gnu": { - "name": "cpython", - "arch": "arm64", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, @@ -5378,9 +3717,20 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.8.16%2B20230726-aarch64-unknown-linux-gnu-debug-full.tar.zst", "sha256": "423d43d93e2fe33b41ad66d35426f16541f09fee9d7272ae5decf5474ebbc225" }, - "cpython-3.8.16-linux-i686-gnu": { + "cpython-3.8.16-macos-aarch64-none": { "name": "cpython", - "arch": "i686", + "arch": "aarch64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 8, + "patch": 16, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.8.16%2B20230726-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "bfc91d0a1d6d6dfaa5a31c925aa6adae82bd1ae5eb17813a9f0a50bf9d3e6305" + }, + "cpython-3.8.16-linux-x86-gnu": { + "name": "cpython", + "arch": "x86", "os": "linux", "libc": "gnu", "major": 3, @@ -5389,9 +3739,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.8.16%2B20230726-i686-unknown-linux-gnu-debug-full.tar.zst", "sha256": "9aa3e559130a47c33ee2b67f6ca69e2f10d8f70c1fd1e2871763b892372a6d9e" }, - "cpython-3.8.16-windows-i686-none": { + "cpython-3.8.16-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -5400,17 +3750,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.8.16%2B20230726-i686-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "5de953621402c11cc7db65ba15d45779e838d7ce78e7aa8d43c7d78fff177f13" }, - "cpython-3.8.16-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 8, - "patch": 16, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.8.16%2B20230726-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "21c0f4a0fa6ee518b9f2f1901c9667e3baf45d9f84235408b7ca50499d19f56d" - }, "cpython-3.8.16-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -5433,6 +3772,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.8.16%2B20230726-x86_64-unknown-linux-musl-lto-full.tar.zst", "sha256": "f7d46196b44d12a26209ac74061200aac478b96c253eea93a0b9734efa642779" }, + "cpython-3.8.16-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 8, + "patch": 16, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.8.16%2B20230726-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "21c0f4a0fa6ee518b9f2f1901c9667e3baf45d9f84235408b7ca50499d19f56d" + }, "cpython-3.8.16-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -5444,20 +3794,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.8.16%2B20230726-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "6316713c2dcb30127b38ced249fa9608830a33459580b71275a935aaa8cd5d5f" }, - "cpython-3.8.15-darwin-arm64-none": { + "cpython-3.8.15-linux-aarch64-gnu": { "name": "cpython", - "arch": "arm64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 8, - "patch": 15, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.8.15%2B20221106-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "fc0f944e6f01ed649f79c873af1c317db61d2136b82081b4d7cbb7755f878035" - }, - "cpython-3.8.15-linux-arm64-gnu": { - "name": "cpython", - "arch": "arm64", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, @@ -5466,9 +3805,20 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.8.15%2B20221106-aarch64-unknown-linux-gnu-debug-full.tar.zst", "sha256": "2e80025eda686c14a9a0618ced40043c1d577a754b904fd7a382cd41abf9ca00" }, - "cpython-3.8.15-linux-i686-gnu": { + "cpython-3.8.15-macos-aarch64-none": { "name": "cpython", - "arch": "i686", + "arch": "aarch64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 8, + "patch": 15, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.8.15%2B20221106-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "fc0f944e6f01ed649f79c873af1c317db61d2136b82081b4d7cbb7755f878035" + }, + "cpython-3.8.15-linux-x86-gnu": { + "name": "cpython", + "arch": "x86", "os": "linux", "libc": "gnu", "major": 3, @@ -5477,9 +3827,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.8.15%2B20221106-i686-unknown-linux-gnu-debug-full.tar.zst", "sha256": "b8436415ea9bd9970fb766f791a14b0e14ce6351fc4604eb158f1425e8bb4a33" }, - "cpython-3.8.15-windows-i686-none": { + "cpython-3.8.15-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -5488,17 +3838,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.8.15%2B20221106-i686-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "98bb2315c3567316c30b060d613c8d6067b368b64f08ef8fe6196341637c1d78" }, - "cpython-3.8.15-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 8, - "patch": 15, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.8.15%2B20221106-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "e4fd2fa2255295fbdcfadb8b48014fa80810305eccb246d355880aabb45cbe93" - }, "cpython-3.8.15-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -5521,6 +3860,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.8.15%2B20221106-x86_64-unknown-linux-musl-lto-full.tar.zst", "sha256": "231b35d3c2cff0372d17cea7ff5168c0684a920b94a912ffc965c2518cacb694" }, + "cpython-3.8.15-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 8, + "patch": 15, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.8.15%2B20221106-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "e4fd2fa2255295fbdcfadb8b48014fa80810305eccb246d355880aabb45cbe93" + }, "cpython-3.8.15-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -5532,20 +3882,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.8.15%2B20221106-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "59beac5610e6da0848ebaccd72f91f6aaaeed65ef59606d006af909e9e79beba" }, - "cpython-3.8.14-darwin-arm64-none": { + "cpython-3.8.14-linux-aarch64-gnu": { "name": "cpython", - "arch": "arm64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 8, - "patch": 14, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.8.14%2B20221002-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "d17a3fcc161345efa2ec0b4ab9c9ed6c139d29128f2e34bb636338a484aa7b72" - }, - "cpython-3.8.14-linux-arm64-gnu": { - "name": "cpython", - "arch": "arm64", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, @@ -5554,9 +3893,20 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.8.14%2B20221002-aarch64-unknown-linux-gnu-debug-full.tar.zst", "sha256": "a14d8b5cbd8e1ca45cbcb49f4bf0b0440dc86eb95b7c3da3c463a704a3b4593c" }, - "cpython-3.8.14-linux-i686-gnu": { + "cpython-3.8.14-macos-aarch64-none": { "name": "cpython", - "arch": "i686", + "arch": "aarch64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 8, + "patch": 14, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.8.14%2B20221002-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "d17a3fcc161345efa2ec0b4ab9c9ed6c139d29128f2e34bb636338a484aa7b72" + }, + "cpython-3.8.14-linux-x86-gnu": { + "name": "cpython", + "arch": "x86", "os": "linux", "libc": "gnu", "major": 3, @@ -5565,9 +3915,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.8.14%2B20221002-i686-unknown-linux-gnu-debug-full.tar.zst", "sha256": "631bb90fe8f2965d03400b268de90fe155ce51961296360d6578b7151aa9ef4c" }, - "cpython-3.8.14-windows-i686-none": { + "cpython-3.8.14-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -5576,17 +3926,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.8.14%2B20221002-i686-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "e43f7a5044eac91e95df59fd08bf96f13245898876fc2afd90a081cfcd847e35" }, - "cpython-3.8.14-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 8, - "patch": 14, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.8.14%2B20221002-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "62edfea77b42e87ca2d85c482319211cd2dd68d55ba85c99f1834f7b64a60133" - }, "cpython-3.8.14-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -5609,6 +3948,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.8.14%2B20221002-x86_64-unknown-linux-musl-lto-full.tar.zst", "sha256": "c6da442aaea160179a9379b297ccb3ba09b825fc27d84577fc28e62911451e7d" }, + "cpython-3.8.14-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 8, + "patch": 14, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.8.14%2B20221002-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "62edfea77b42e87ca2d85c482319211cd2dd68d55ba85c99f1834f7b64a60133" + }, "cpython-3.8.14-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -5620,20 +3970,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.8.14%2B20221002-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "6986b3e6edf7b37f96ea940b7ccba7b767ed3ea9b3faec2a2a60e5b2c4443314" }, - "cpython-3.8.13-darwin-arm64-none": { + "cpython-3.8.13-linux-aarch64-gnu": { "name": "cpython", - "arch": "arm64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 8, - "patch": 13, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.8.13%2B20220802-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "a204e5f9e1566bdc170b163300a29fc9580d5c65cd6e896caf6500cd64471373" - }, - "cpython-3.8.13-linux-arm64-gnu": { - "name": "cpython", - "arch": "arm64", + "arch": "aarch64", "os": "linux", "libc": "gnu", "major": 3, @@ -5642,9 +3981,20 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.8.13%2B20220802-aarch64-unknown-linux-gnu-debug-full.tar.zst", "sha256": "3a927205db4686c182b5e8f3fc7fd7d82ec8f61c70d5b2bfddd9673c7ddc07ba" }, - "cpython-3.8.13-linux-i686-gnu": { + "cpython-3.8.13-macos-aarch64-none": { "name": "cpython", - "arch": "i686", + "arch": "aarch64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 8, + "patch": 13, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.8.13%2B20220802-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "a204e5f9e1566bdc170b163300a29fc9580d5c65cd6e896caf6500cd64471373" + }, + "cpython-3.8.13-linux-x86-gnu": { + "name": "cpython", + "arch": "x86", "os": "linux", "libc": "gnu", "major": 3, @@ -5653,9 +4003,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.8.13%2B20220802-i686-unknown-linux-gnu-debug-full.tar.zst", "sha256": "6daf0405beae6d127a2dcae61d51a719236b861b4cabc220727e48547fd6f045" }, - "cpython-3.8.13-windows-i686-none": { + "cpython-3.8.13-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -5664,17 +4014,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.8.13%2B20220802-i686-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "5630739d1c6fcfbf90311d236c5e46314fc4b439364429bee12d0ffc95e134fb" }, - "cpython-3.8.13-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 8, - "patch": 13, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.8.13%2B20220802-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "f706a62de8582bf84b8b693c993314cd786f3e78639892cfd9a7283a526696f9" - }, "cpython-3.8.13-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -5697,6 +4036,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.8.13%2B20220802-x86_64-unknown-linux-musl-lto-full.tar.zst", "sha256": "410f3223021d1b439cf8e4da699f868adada2066e354d88a00b5f365dc66c4bf" }, + "cpython-3.8.13-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 8, + "patch": 13, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.8.13%2B20220802-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "f706a62de8582bf84b8b693c993314cd786f3e78639892cfd9a7283a526696f9" + }, "cpython-3.8.13-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -5708,10 +4058,10 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.8.13%2B20220802-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "c36b703b8b806a047ba71e5e85734ac78d204d3a2b7ebc2efcdc7d4af6f6c263" }, - "cpython-3.8.12-darwin-arm64-none": { + "cpython-3.8.12-macos-aarch64-none": { "name": "cpython", - "arch": "arm64", - "os": "darwin", + "arch": "aarch64", + "os": "macos", "libc": "none", "major": 3, "minor": 8, @@ -5719,9 +4069,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.8.12%2B20220227-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", "sha256": "386f667f8d49b6c34aee1910cdc0b5b41883f9406f98e7d59a3753990b1cdbac" }, - "cpython-3.8.12-linux-i686-gnu": { + "cpython-3.8.12-linux-x86-gnu": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "linux", "libc": "gnu", "major": 3, @@ -5730,9 +4080,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.8.12%2B20220227-i686-unknown-linux-gnu-debug-full.tar.zst", "sha256": "7cfac9a57e262be3e889036d7fc570293e6d3d74411ee23e1fa9aa470d387e6a" }, - "cpython-3.8.12-windows-i686-none": { + "cpython-3.8.12-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -5741,17 +4091,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.8.12%2B20220227-i686-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "3e2e6c7de78b1924aad37904fed7bfbac6efa2bef05348e9be92180b2f2b1ae1" }, - "cpython-3.8.12-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 8, - "patch": 12, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.8.12%2B20220227-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", - "sha256": "cf614d96e2001d526061b3ce0569c79057fd0074ace472ff4f5f601262e08cdb" - }, "cpython-3.8.12-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -5774,6 +4113,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.8.12%2B20220227-x86_64-unknown-linux-musl-lto-full.tar.zst", "sha256": "3d958e3f984637d8ca4a90a2e068737b268f87fc615121a6f1808cd46ccacc48" }, + "cpython-3.8.12-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 8, + "patch": 12, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.8.12%2B20220227-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + "sha256": "cf614d96e2001d526061b3ce0569c79057fd0074ace472ff4f5f601262e08cdb" + }, "cpython-3.8.12-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -5785,9 +4135,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.8.12%2B20220227-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", "sha256": "33f278416ba8074f2ca6d7f8c17b311b60537c9e6431fd47948784c2a78ea227" }, - "cpython-3.8.11-linux-i686-gnu": { + "cpython-3.8.11-linux-x86-gnu": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "linux", "libc": "gnu", "major": 3, @@ -5796,9 +4146,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.8.11-i686-unknown-linux-gnu-debug-20210724T1424.tar.zst", "sha256": null }, - "cpython-3.8.11-windows-i686-none": { + "cpython-3.8.11-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -5807,17 +4157,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.8.11-i686-pc-windows-msvc-shared-pgo-20210724T1424.tar.zst", "sha256": null }, - "cpython-3.8.11-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 8, - "patch": 11, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.8.11-x86_64-apple-darwin-pgo%2Blto-20210724T1424.tar.zst", - "sha256": null - }, "cpython-3.8.11-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -5840,6 +4179,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.8.11-x86_64-unknown-linux-musl-lto-20210724T1424.tar.zst", "sha256": null }, + "cpython-3.8.11-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 8, + "patch": 11, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.8.11-x86_64-apple-darwin-pgo%2Blto-20210724T1424.tar.zst", + "sha256": null + }, "cpython-3.8.11-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -5851,9 +4201,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.8.11-x86_64-pc-windows-msvc-shared-pgo-20210724T1424.tar.zst", "sha256": null }, - "cpython-3.8.10-linux-i686-gnu": { + "cpython-3.8.10-linux-x86-gnu": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "linux", "libc": "gnu", "major": 3, @@ -5862,9 +4212,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.8.10-i686-unknown-linux-gnu-debug-20210506T0943.tar.zst", "sha256": null }, - "cpython-3.8.10-windows-i686-none": { + "cpython-3.8.10-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -5873,17 +4223,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.8.10-i686-pc-windows-msvc-shared-pgo-20210506T0943.tar.zst", "sha256": null }, - "cpython-3.8.10-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 8, - "patch": 10, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.8.10-x86_64-apple-darwin-pgo%2Blto-20210506T0943.tar.zst", - "sha256": null - }, "cpython-3.8.10-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -5906,6 +4245,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.8.10-x86_64-unknown-linux-musl-lto-20210506T0943.tar.zst", "sha256": null }, + "cpython-3.8.10-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 8, + "patch": 10, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.8.10-x86_64-apple-darwin-pgo%2Blto-20210506T0943.tar.zst", + "sha256": null + }, "cpython-3.8.10-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -5917,9 +4267,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.8.10-x86_64-pc-windows-msvc-shared-pgo-20210506T0943.tar.zst", "sha256": null }, - "cpython-3.8.9-linux-i686-gnu": { + "cpython-3.8.9-linux-x86-gnu": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "linux", "libc": "gnu", "major": 3, @@ -5928,9 +4278,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.8.9-i686-unknown-linux-gnu-debug-20210414T1515.tar.zst", "sha256": null }, - "cpython-3.8.9-windows-i686-none": { + "cpython-3.8.9-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -5939,17 +4289,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.8.9-i686-pc-windows-msvc-shared-pgo-20210414T1515.tar.zst", "sha256": null }, - "cpython-3.8.9-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 8, - "patch": 9, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.8.9-x86_64-apple-darwin-pgo%2Blto-20210414T1515.tar.zst", - "sha256": null - }, "cpython-3.8.9-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -5972,6 +4311,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.8.9-x86_64-unknown-linux-musl-lto-20210414T1515.tar.zst", "sha256": null }, + "cpython-3.8.9-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 8, + "patch": 9, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.8.9-x86_64-apple-darwin-pgo%2Blto-20210414T1515.tar.zst", + "sha256": null + }, "cpython-3.8.9-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -5983,9 +4333,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.8.9-x86_64-pc-windows-msvc-shared-pgo-20210414T1515.tar.zst", "sha256": null }, - "cpython-3.8.8-linux-i686-gnu": { + "cpython-3.8.8-linux-x86-gnu": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "linux", "libc": "gnu", "major": 3, @@ -5994,9 +4344,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.8.8-i686-unknown-linux-gnu-debug-20210327T1202.tar.zst", "sha256": null }, - "cpython-3.8.8-windows-i686-none": { + "cpython-3.8.8-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -6005,17 +4355,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.8.8-i686-pc-windows-msvc-shared-pgo-20210327T1202.tar.zst", "sha256": null }, - "cpython-3.8.8-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 8, - "patch": 8, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.8.8-x86_64-apple-darwin-pgo%2Blto-20210327T1202.tar.zst", - "sha256": null - }, "cpython-3.8.8-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -6038,6 +4377,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.8.8-x86_64-unknown-linux-musl-lto-20210327T1202.tar.zst", "sha256": null }, + "cpython-3.8.8-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 8, + "patch": 8, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.8.8-x86_64-apple-darwin-pgo%2Blto-20210327T1202.tar.zst", + "sha256": null + }, "cpython-3.8.8-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -6049,9 +4399,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.8.8-x86_64-pc-windows-msvc-shared-pgo-20210327T1202.tar.zst", "sha256": null }, - "cpython-3.8.7-windows-i686-none": { + "cpython-3.8.7-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -6060,17 +4410,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210103/cpython-3.8.7-i686-pc-windows-msvc-shared-pgo-20210103T1125.tar.zst", "sha256": null }, - "cpython-3.8.7-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 8, - "patch": 7, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210103/cpython-3.8.7-x86_64-apple-darwin-pgo-20210103T1125.tar.zst", - "sha256": null - }, "cpython-3.8.7-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -6093,6 +4432,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210103/cpython-3.8.7-x86_64-unknown-linux-musl-debug-20210103T1125.tar.zst", "sha256": null }, + "cpython-3.8.7-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 8, + "patch": 7, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210103/cpython-3.8.7-x86_64-apple-darwin-pgo-20210103T1125.tar.zst", + "sha256": null + }, "cpython-3.8.7-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -6104,9 +4454,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210103/cpython-3.8.7-x86_64-pc-windows-msvc-shared-pgo-20210103T1125.tar.zst", "sha256": null }, - "cpython-3.8.6-windows-i686-none": { + "cpython-3.8.6-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -6115,17 +4465,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20201020/cpython-3.8.6-i686-pc-windows-msvc-shared-pgo-20201021T0233.tar.zst", "sha256": null }, - "cpython-3.8.6-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 8, - "patch": 6, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20201020/cpython-3.8.6-x86_64-apple-darwin-pgo-20201020T0626.tar.zst", - "sha256": null - }, "cpython-3.8.6-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -6148,6 +4487,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20201020/cpython-3.8.6-x86_64-unknown-linux-musl-debug-20201020T0627.tar.zst", "sha256": null }, + "cpython-3.8.6-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 8, + "patch": 6, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20201020/cpython-3.8.6-x86_64-apple-darwin-pgo-20201020T0626.tar.zst", + "sha256": null + }, "cpython-3.8.6-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -6159,9 +4509,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20201020/cpython-3.8.6-x86_64-pc-windows-msvc-shared-pgo-20201021T0232.tar.zst", "sha256": null }, - "cpython-3.8.5-windows-i686-none": { + "cpython-3.8.5-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -6170,17 +4520,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200830/cpython-3.8.5-i686-pc-windows-msvc-shared-pgo-20200830T2311.tar.zst", "sha256": null }, - "cpython-3.8.5-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 8, - "patch": 5, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200823/cpython-3.8.5-x86_64-apple-darwin-pgo-20200823T2228.tar.zst", - "sha256": null - }, "cpython-3.8.5-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -6203,6 +4542,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200822/cpython-3.8.5-x86_64-unknown-linux-musl-debug-20200823T0036.tar.zst", "sha256": null }, + "cpython-3.8.5-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 8, + "patch": 5, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200823/cpython-3.8.5-x86_64-apple-darwin-pgo-20200823T2228.tar.zst", + "sha256": null + }, "cpython-3.8.5-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -6214,9 +4564,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200830/cpython-3.8.5-x86_64-pc-windows-msvc-shared-pgo-20200830T2254.tar.zst", "sha256": null }, - "cpython-3.8.3-windows-i686-none": { + "cpython-3.8.3-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -6225,17 +4575,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200517/cpython-3.8.3-i686-pc-windows-msvc-shared-pgo-20200518T0154.tar.zst", "sha256": null }, - "cpython-3.8.3-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 8, - "patch": 3, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200530/cpython-3.8.3-x86_64-apple-darwin-pgo-20200530T1845.tar.zst", - "sha256": null - }, "cpython-3.8.3-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -6258,6 +4597,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200517/cpython-3.8.3-x86_64-unknown-linux-musl-debug-20200518T0040.tar.zst", "sha256": null }, + "cpython-3.8.3-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 8, + "patch": 3, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200530/cpython-3.8.3-x86_64-apple-darwin-pgo-20200530T1845.tar.zst", + "sha256": null + }, "cpython-3.8.3-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -6269,9 +4619,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200517/cpython-3.8.3-x86_64-pc-windows-msvc-shared-pgo-20200517T2207.tar.zst", "sha256": null }, - "cpython-3.8.2-windows-i686-none": { + "cpython-3.8.2-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -6280,17 +4630,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200418/cpython-3.8.2-i686-pc-windows-msvc-shared-pgo-20200418T2315.tar.zst", "sha256": null }, - "cpython-3.8.2-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 8, - "patch": 2, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200418/cpython-3.8.2-x86_64-apple-darwin-pgo-20200418T2238.tar.zst", - "sha256": null - }, "cpython-3.8.2-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -6302,6 +4641,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200418/cpython-3.8.2-x86_64-unknown-linux-gnu-debug-20200418T2305.tar.zst", "sha256": null }, + "cpython-3.8.2-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 8, + "patch": 2, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200418/cpython-3.8.2-x86_64-apple-darwin-pgo-20200418T2238.tar.zst", + "sha256": null + }, "cpython-3.8.2-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -6313,9 +4663,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200418/cpython-3.8.2-x86_64-pc-windows-msvc-shared-pgo-20200418T2315.tar.zst", "sha256": null }, - "cpython-3.7.9-windows-i686-none": { + "cpython-3.7.9-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -6324,17 +4674,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200822/cpython-3.7.9-i686-pc-windows-msvc-shared-pgo-20200823T0159.tar.zst", "sha256": null }, - "cpython-3.7.9-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 7, - "patch": 9, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200823/cpython-3.7.9-x86_64-apple-darwin-pgo-20200823T2228.tar.zst", - "sha256": null - }, "cpython-3.7.9-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -6357,6 +4696,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200822/cpython-3.7.9-x86_64-unknown-linux-musl-debug-20200823T0036.tar.zst", "sha256": null }, + "cpython-3.7.9-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 7, + "patch": 9, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200823/cpython-3.7.9-x86_64-apple-darwin-pgo-20200823T2228.tar.zst", + "sha256": null + }, "cpython-3.7.9-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -6368,9 +4718,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200822/cpython-3.7.9-x86_64-pc-windows-msvc-shared-pgo-20200823T0118.tar.zst", "sha256": null }, - "cpython-3.7.7-windows-i686-none": { + "cpython-3.7.7-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -6379,28 +4729,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200517/cpython-3.7.7-i686-pc-windows-msvc-shared-pgo-20200517T2153.tar.zst", "sha256": null }, - "cpython-3.7.7-shared-windows-none": { - "name": "cpython", - "arch": "windows", - "os": "shared", - "libc": "none", - "major": 3, - "minor": 7, - "patch": 7, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200408/cpython-3.7.7-windows-amd64-shared-20200409T0108.tar.zst", - "sha256": null - }, - "cpython-3.7.7-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 7, - "patch": 7, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200530/cpython-3.7.7-x86_64-apple-darwin-pgo-20200530T1845.tar.zst", - "sha256": null - }, "cpython-3.7.7-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -6423,6 +4751,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200517/cpython-3.7.7-x86_64-unknown-linux-musl-debug-20200518T0040.tar.zst", "sha256": null }, + "cpython-3.7.7-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 7, + "patch": 7, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200530/cpython-3.7.7-x86_64-apple-darwin-pgo-20200530T1845.tar.zst", + "sha256": null + }, "cpython-3.7.7-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -6434,9 +4773,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200517/cpython-3.7.7-x86_64-pc-windows-msvc-shared-pgo-20200517T2128.tar.zst", "sha256": null }, - "cpython-3.7.6-windows-i686-none": { + "cpython-3.7.6-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -6445,28 +4784,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200216/cpython-3.7.6-windows-x86-shared-pgo-20200217T0110.tar.zst", "sha256": null }, - "cpython-3.7.6-shared-windows-none": { - "name": "cpython", - "arch": "windows", - "os": "shared", - "libc": "none", - "major": 3, - "minor": 7, - "patch": 6, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200216/cpython-3.7.6-windows-amd64-shared-20200216T2324.tar.zst", - "sha256": null - }, - "cpython-3.7.6-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 7, - "patch": 6, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200216/cpython-3.7.6-macos-20200216T2344.tar.zst", - "sha256": null - }, "cpython-3.7.6-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -6489,6 +4806,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200217/cpython-3.7.6-linux64-musl-20200218T0557.tar.zst", "sha256": null }, + "cpython-3.7.6-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 7, + "patch": 6, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200216/cpython-3.7.6-macos-20200216T2344.tar.zst", + "sha256": null + }, "cpython-3.7.6-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -6500,9 +4828,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200216/cpython-3.7.6-windows-amd64-shared-pgo-20200217T0022.tar.zst", "sha256": null }, - "cpython-3.7.5-windows-i686-none": { + "cpython-3.7.5-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -6511,17 +4839,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20191025/cpython-3.7.5-windows-x86-20191025T0549.tar.zst", "sha256": null }, - "cpython-3.7.5-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 7, - "patch": 5, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20191025/cpython-3.7.5-macos-20191026T0535.tar.zst", - "sha256": null - }, "cpython-3.7.5-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -6544,6 +4861,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20191025/cpython-3.7.5-linux64-musl-20191026T0603.tar.zst", "sha256": null }, + "cpython-3.7.5-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 7, + "patch": 5, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20191025/cpython-3.7.5-macos-20191026T0535.tar.zst", + "sha256": null + }, "cpython-3.7.5-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -6555,9 +4883,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20191025/cpython-3.7.5-windows-amd64-20191025T0540.tar.zst", "sha256": null }, - "cpython-3.7.4-windows-i686-none": { + "cpython-3.7.4-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -6566,17 +4894,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20190816/cpython-3.7.4-windows-x86-20190817T0235.tar.zst", "sha256": null }, - "cpython-3.7.4-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 7, - "patch": 4, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20190816/cpython-3.7.4-macos-20190817T0220.tar.zst", - "sha256": null - }, "cpython-3.7.4-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -6599,6 +4916,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20190816/cpython-3.7.4-linux64-musl-20190817T0227.tar.zst", "sha256": null }, + "cpython-3.7.4-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 7, + "patch": 4, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20190816/cpython-3.7.4-macos-20190817T0220.tar.zst", + "sha256": null + }, "cpython-3.7.4-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", @@ -6610,9 +4938,9 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20190816/cpython-3.7.4-windows-amd64-20190817T0227.tar.zst", "sha256": null }, - "cpython-3.7.3-windows-i686-none": { + "cpython-3.7.3-windows-x86-none": { "name": "cpython", - "arch": "i686", + "arch": "x86", "os": "windows", "libc": "none", "major": 3, @@ -6621,17 +4949,6 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20190617/cpython-3.7.3-windows-x86-20190709T0348.tar.zst", "sha256": null }, - "cpython-3.7.3-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 7, - "patch": 3, - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20190617/cpython-3.7.3-macos-20190618T0523.tar.zst", - "sha256": null - }, "cpython-3.7.3-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", @@ -6654,6 +4971,17 @@ "url": "https://github.com/indygreg/python-build-standalone/releases/download/20190617/cpython-3.7.3-linux64-musl-20190618T0400.tar.zst", "sha256": null }, + "cpython-3.7.3-macos-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "macos", + "libc": "none", + "major": 3, + "minor": 7, + "patch": 3, + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20190617/cpython-3.7.3-macos-20190618T0523.tar.zst", + "sha256": null + }, "cpython-3.7.3-windows-x86_64-none": { "name": "cpython", "arch": "x86_64", diff --git a/crates/uv-toolchain/src/downloads.rs b/crates/uv-toolchain/src/downloads.rs new file mode 100644 index 000000000..ae1adfe59 --- /dev/null +++ b/crates/uv-toolchain/src/downloads.rs @@ -0,0 +1,438 @@ +use std::fmt::{self, Display}; +use std::io; +use std::path::{Path, PathBuf}; +use std::str::FromStr; + +use crate::PythonVersion; +use thiserror::Error; +use uv_client::BetterReqwestError; + +use futures::TryStreamExt; + +use tokio_util::compat::FuturesAsyncReadCompatExt; +use tracing::debug; +use url::Url; +use uv_fs::Simplified; + +#[derive(Error, Debug)] +pub enum Error { + #[error("operating system not supported: {0}")] + OsNotSupported(String), + #[error("architecture not supported: {0}")] + ArchNotSupported(String), + #[error("libc type could not be detected")] + LibcNotDetected(), + #[error("invalid python version: {0}")] + InvalidPythonVersion(String), + #[error("download failed")] + NetworkError(#[from] BetterReqwestError), + #[error("download failed")] + NetworkMiddlewareError(#[source] anyhow::Error), + #[error(transparent)] + ExtractError(#[from] uv_extract::Error), + #[error("invalid download url")] + InvalidUrl(#[from] url::ParseError), + #[error("failed to create download directory")] + DownloadDirError(#[source] io::Error), + #[error("failed to copy to: {0}", to.user_display())] + CopyError { + to: PathBuf, + #[source] + err: io::Error, + }, + #[error("failed to read toolchain directory: {0}", dir.user_display())] + ReadError { + dir: PathBuf, + #[source] + err: io::Error, + }, +} + +#[derive(Debug, PartialEq)] +pub struct PythonDownload { + key: &'static str, + implementation: ImplementationName, + arch: Arch, + os: Os, + libc: Libc, + major: u8, + minor: u8, + patch: u8, + url: &'static str, + sha256: Option<&'static str>, +} + +#[derive(Debug)] +pub struct PythonDownloadRequest { + version: Option, + implementation: Option, + arch: Option, + os: Option, + libc: Option, +} + +impl PythonDownloadRequest { + pub fn new( + version: Option, + implementation: Option, + arch: Option, + os: Option, + libc: Option, + ) -> Self { + Self { + version, + implementation, + arch, + os, + libc, + } + } + + #[must_use] + pub fn with_implementation(mut self, implementation: ImplementationName) -> Self { + self.implementation = Some(implementation); + self + } + + #[must_use] + pub fn with_arch(mut self, arch: Arch) -> Self { + self.arch = Some(arch); + self + } + + #[must_use] + pub fn with_os(mut self, os: Os) -> Self { + self.os = Some(os); + self + } + + #[must_use] + pub fn with_libc(mut self, libc: Libc) -> Self { + self.libc = Some(libc); + self + } + + pub fn fill(mut self) -> Result { + if self.implementation.is_none() { + self.implementation = Some(ImplementationName::Cpython); + } + if self.arch.is_none() { + self.arch = Some(Arch::from_env()?); + } + if self.os.is_none() { + self.os = Some(Os::from_env()?); + } + if self.libc.is_none() { + self.libc = Some(Libc::from_env()?); + } + Ok(self) + } +} + +impl FromStr for PythonDownloadRequest { + type Err = Error; + + fn from_str(s: &str) -> Result { + // TOOD(zanieb): Implement parsing of additional request parts + let version = PythonVersion::from_str(s).map_err(Error::InvalidPythonVersion)?; + Ok(Self::new(Some(version), None, None, None, None)) + } +} + +#[derive(Debug, PartialEq)] +pub enum Libc { + Gnu, + Musl, + None, +} + +#[derive(Debug, PartialEq)] +pub enum ImplementationName { + Cpython, +} +#[derive(Debug, PartialEq)] +pub struct Platform { + os: Os, + arch: Arch, + libc: Libc, +} + +include!("python_versions.inc"); + +pub enum DownloadResult { + AlreadyAvailable(PathBuf), + Fetched(PathBuf), +} + +impl PythonDownload { + /// Return the [`PythonDownload`] corresponding to the key, if it exists. + pub fn from_key(key: &str) -> Option<&PythonDownload> { + PYTHON_DOWNLOADS.iter().find(|&value| value.key == key) + } + + pub fn from_request(request: &PythonDownloadRequest) -> Option<&'static PythonDownload> { + for download in PYTHON_DOWNLOADS { + if let Some(arch) = &request.arch { + if download.arch != *arch { + continue; + } + } + if let Some(os) = &request.os { + if download.os != *os { + continue; + } + } + if let Some(implementation) = &request.implementation { + if download.implementation != *implementation { + continue; + } + } + if let Some(version) = &request.version { + if download.major != version.major() { + continue; + } + if download.minor != version.minor() { + continue; + } + if let Some(patch) = version.patch() { + if download.patch != patch { + continue; + } + } + } + return Some(download); + } + None + } + + pub fn url(&self) -> &str { + self.url + } + + pub fn sha256(&self) -> Option<&str> { + self.sha256 + } + + /// Download and extract + pub async fn fetch( + &self, + client: &uv_client::BaseClient, + path: &Path, + ) -> Result { + let url = Url::parse(self.url)?; + let path = path.join(self.key).clone(); + + // If it already exists, return it + if path.is_dir() { + return Ok(DownloadResult::AlreadyAvailable(path)); + } + + let filename = url.path_segments().unwrap().last().unwrap(); + let response = client.get(url.clone()).send().await?; + + // Ensure the request was successful. + response.error_for_status_ref()?; + + // Download and extract into a temporary directory. + let temp_dir = tempfile::tempdir().map_err(Error::DownloadDirError)?; + + debug!( + "Downloading {url} to temporary location {}", + temp_dir.path().display() + ); + let reader = response + .bytes_stream() + .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err)) + .into_async_read(); + + debug!("Extracting {filename}"); + uv_extract::stream::archive(reader.compat(), filename, temp_dir.path()).await?; + + // Extract the top-level directory. + let extracted = match uv_extract::strip_component(temp_dir.path()) { + Ok(top_level) => top_level, + Err(uv_extract::Error::NonSingularArchive(_)) => temp_dir.into_path(), + Err(err) => return Err(err.into()), + }; + + // Persist it to the target + debug!("Moving {} to {}", extracted.display(), path.user_display()); + fs_err::tokio::rename(extracted, &path) + .await + .map_err(|err| Error::CopyError { + to: path.clone(), + err, + })?; + + Ok(DownloadResult::Fetched(path)) + } + + pub fn python_version(&self) -> PythonVersion { + PythonVersion::from_str(&format!("{}.{}.{}", self.major, self.minor, self.patch)) + .expect("Python downloads should always have valid versions") + } +} + +impl Platform { + pub fn new(os: Os, arch: Arch, libc: Libc) -> Self { + Self { os, arch, libc } + } + pub fn from_env() -> Result { + Ok(Self::new( + Os::from_env()?, + Arch::from_env()?, + Libc::from_env()?, + )) + } +} + +/// All supported operating systems. +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum Os { + Windows, + Linux, + Macos, + FreeBsd, + NetBsd, + OpenBsd, + Dragonfly, + Illumos, + Haiku, +} + +impl fmt::Display for Os { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Self::Windows => write!(f, "Windows"), + Self::Macos => write!(f, "MacOS"), + Self::FreeBsd => write!(f, "FreeBSD"), + Self::NetBsd => write!(f, "NetBSD"), + Self::Linux => write!(f, "Linux"), + Self::OpenBsd => write!(f, "OpenBSD"), + Self::Dragonfly => write!(f, "DragonFly"), + Self::Illumos => write!(f, "Illumos"), + Self::Haiku => write!(f, "Haiku"), + } + } +} + +impl Os { + pub(crate) fn from_env() -> Result { + Self::from_str(std::env::consts::OS) + } +} + +impl FromStr for Os { + type Err = Error; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "windows" => Ok(Self::Windows), + "linux" => Ok(Self::Linux), + "macos" => Ok(Self::Macos), + "freebsd" => Ok(Self::FreeBsd), + "netbsd" => Ok(Self::NetBsd), + "openbsd" => Ok(Self::OpenBsd), + "dragonfly" => Ok(Self::Dragonfly), + "illumos" => Ok(Self::Illumos), + "haiku" => Ok(Self::Haiku), + _ => Err(Error::OsNotSupported(s.to_string())), + } + } +} + +/// All supported CPU architectures +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum Arch { + Aarch64, + Armv6L, + Armv7L, + Powerpc64Le, + Powerpc64, + X86, + X86_64, + S390X, +} + +impl fmt::Display for Arch { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Self::Aarch64 => write!(f, "aarch64"), + Self::Armv6L => write!(f, "armv6l"), + Self::Armv7L => write!(f, "armv7l"), + Self::Powerpc64Le => write!(f, "ppc64le"), + Self::Powerpc64 => write!(f, "ppc64"), + Self::X86 => write!(f, "i686"), + Self::X86_64 => write!(f, "x86_64"), + Self::S390X => write!(f, "s390x"), + } + } +} + +impl FromStr for Arch { + type Err = Error; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "aarch64" | "arm64" => Ok(Self::Aarch64), + "armv6l" => Ok(Self::Armv6L), + "armv7l" => Ok(Self::Armv7L), + "powerpc64le" | "ppc64le" => Ok(Self::Powerpc64Le), + "powerpc64" | "ppc64" => Ok(Self::Powerpc64), + "x86" | "i686" | "i386" => Ok(Self::X86), + "x86_64" | "amd64" => Ok(Self::X86_64), + "s390x" => Ok(Self::S390X), + _ => Err(Error::ArchNotSupported(s.to_string())), + } + } +} + +impl Arch { + pub(crate) fn from_env() -> Result { + Self::from_str(std::env::consts::ARCH) + } +} + +impl Libc { + pub(crate) fn from_env() -> Result { + // TODO(zanieb): Perform this lookup + match std::env::consts::OS { + "linux" => Ok(Libc::Gnu), + "windows" | "macos" => Ok(Libc::None), + _ => Err(Error::LibcNotDetected()), + } + } +} + +impl fmt::Display for Libc { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Libc::Gnu => f.write_str("gnu"), + Libc::None => f.write_str("none"), + Libc::Musl => f.write_str("musl"), + } + } +} + +impl From for Error { + fn from(error: reqwest::Error) -> Self { + Self::NetworkError(BetterReqwestError::from(error)) + } +} + +impl From for Error { + fn from(error: reqwest_middleware::Error) -> Self { + match error { + reqwest_middleware::Error::Middleware(error) => Self::NetworkMiddlewareError(error), + reqwest_middleware::Error::Reqwest(error) => { + Self::NetworkError(BetterReqwestError::from(error)) + } + } + } +} + +impl Display for PythonDownload { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.key) + } +} diff --git a/crates/uv-toolchain/src/find.rs b/crates/uv-toolchain/src/find.rs new file mode 100644 index 000000000..69022bae6 --- /dev/null +++ b/crates/uv-toolchain/src/find.rs @@ -0,0 +1,110 @@ +use std::collections::BTreeSet; +use std::ffi::OsStr; +use std::path::{Path, PathBuf}; + +use crate::downloads::{Arch, Error, Libc, Os}; +use crate::python_version::PythonVersion; + +use once_cell::sync::Lazy; + +/// The directory where Python toolchains we install are stored. +pub static TOOLCHAIN_DIRECTORY: Lazy = Lazy::new(|| { + std::env::var_os("UV_BOOTSTRAP_DIR").map_or( + Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap()) + .parent() + .expect("CARGO_MANIFEST_DIR should be nested in workspace") + .parent() + .expect("CARGO_MANIFEST_DIR should be doubly nested in workspace") + .join("bin"), + PathBuf::from, + ) +}); + +/// An installed Python toolchain. +#[derive(Debug, Clone)] +pub struct Toolchain { + /// The path to the top-level directory of the installed toolchain. + path: PathBuf, +} + +impl Toolchain { + pub fn executable(&self) -> PathBuf { + if cfg!(windows) { + self.path.join("install").join("python.exe") + } else if cfg!(unix) { + self.path.join("install").join("bin").join("python3") + } else { + unimplemented!("Only Windows and Unix systems are supported.") + } + } +} + +/// Return the toolchains that satisfy the given Python version on this platform. +/// +/// ## Errors +/// +/// - The platform metadata cannot be read +/// - A directory in the toolchain directory cannot be read +pub fn toolchains_for_version(version: &PythonVersion) -> Result, Error> { + let platform_key = platform_key_from_env()?; + + // TODO(zanieb): Consider returning an iterator instead of a `Vec` + // Note we need to collect paths regardless for sorting by version. + + let toolchain_dirs = match fs_err::read_dir(TOOLCHAIN_DIRECTORY.to_path_buf()) { + Ok(toolchain_dirs) => { + // Collect sorted directory paths; `read_dir` is not stable across platforms + let directories: BTreeSet<_> = toolchain_dirs + .filter_map(|read_dir| match read_dir { + Ok(entry) => match entry.file_type() { + Ok(file_type) => file_type.is_dir().then_some(Ok(entry.path())), + Err(err) => Some(Err(err)), + }, + Err(err) => Some(Err(err)), + }) + .collect::>() + .map_err(|err| Error::ReadError { + dir: TOOLCHAIN_DIRECTORY.to_path_buf(), + err, + })?; + directories + } + Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + return Ok(Vec::new()); + } + Err(err) => { + return Err(Error::ReadError { + dir: TOOLCHAIN_DIRECTORY.to_path_buf(), + err, + }) + } + }; + + Ok(toolchain_dirs + .into_iter() + // Sort "newer" versions of Python first + .rev() + .filter_map(|path| { + if path + .file_name() + .map(OsStr::to_string_lossy) + .is_some_and(|filename| { + filename.starts_with(&format!("cpython-{version}")) + && filename.ends_with(&platform_key) + }) + { + Some(Toolchain { path }) + } else { + None + } + }) + .collect::>()) +} + +/// Generate a platform portion of a key from the environment. +fn platform_key_from_env() -> Result { + let os = Os::from_env()?; + let arch = Arch::from_env()?; + let libc = Libc::from_env()?; + Ok(format!("{os}-{arch}-{libc}").to_lowercase()) +} diff --git a/crates/uv-toolchain/src/lib.rs b/crates/uv-toolchain/src/lib.rs new file mode 100644 index 000000000..116571ff7 --- /dev/null +++ b/crates/uv-toolchain/src/lib.rs @@ -0,0 +1,9 @@ +pub use crate::downloads::{ + DownloadResult, Error, Platform, PythonDownload, PythonDownloadRequest, +}; +pub use crate::find::{toolchains_for_version, Toolchain, TOOLCHAIN_DIRECTORY}; +pub use crate::python_version::PythonVersion; + +mod downloads; +mod find; +mod python_version; diff --git a/crates/uv-interpreter/src/python_version.rs b/crates/uv-toolchain/src/python_version.rs similarity index 92% rename from crates/uv-interpreter/src/python_version.rs rename to crates/uv-toolchain/src/python_version.rs index dffb46fbf..ba3d36385 100644 --- a/crates/uv-interpreter/src/python_version.rs +++ b/crates/uv-toolchain/src/python_version.rs @@ -5,8 +5,6 @@ use std::str::FromStr; use pep440_rs::Version; use pep508_rs::{MarkerEnvironment, StringVersion}; -use crate::Interpreter; - #[derive(Debug, Clone)] pub struct PythonVersion(StringVersion); @@ -142,18 +140,6 @@ impl PythonVersion { Self::from_str(format!("{}.{}", self.major(), self.minor()).as_str()) .expect("dropping a patch should always be valid") } - - /// Check if this Python version is satisfied by the given interpreter. - /// - /// If a patch version is present, we will require an exact match. - /// Otherwise, just the major and minor version numbers need to match. - pub fn is_satisfied_by(&self, interpreter: &Interpreter) -> bool { - if self.patch().is_some() { - self.version() == interpreter.python_version() - } else { - (self.major(), self.minor()) == interpreter.python_tuple() - } - } } #[cfg(test)] diff --git a/crates/uv-toolchain/src/python_versions.inc b/crates/uv-toolchain/src/python_versions.inc new file mode 100644 index 000000000..56c2aeac0 --- /dev/null +++ b/crates/uv-toolchain/src/python_versions.inc @@ -0,0 +1,5467 @@ +// DO NOT EDIT +// +// Generated with `crates/uv-toolchain/template-version-metadata.py` +// From template at `crates/uv-toolchain/src/python_versions.inc.mustache` + +pub(crate) const PYTHON_DOWNLOADS: &[PythonDownload] = &[ + PythonDownload { + key: "cpython-3.12.2-linux-aarch64-gnu", + major: 3, + minor: 12, + patch: 2, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("469a7fd0d0a09936c5db41b5ac83bb29d5bfeb721aa483ac92f3f7ac4d311097") + }, + PythonDownload { + key: "cpython-3.12.2-macos-aarch64-none", + major: 3, + minor: 12, + patch: 2, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("2afcc8b25c55793f6ceb0bef2e547e101f53c9e25a0fe0332320e5381a1f0fdb") + }, + PythonDownload { + key: "cpython-3.12.2-linux-powerpc64le-gnu", + major: 3, + minor: 12, + patch: 2, + implementation: ImplementationName::Cpython, + arch: Arch::Powerpc64Le, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("1d70476fb9013cc93e787417680b34629b510e6e2145cf48bb2f0fe887f7a4d8") + }, + PythonDownload { + key: "cpython-3.12.2-linux-s390x-gnu", + major: 3, + minor: 12, + patch: 2, + implementation: ImplementationName::Cpython, + arch: Arch::S390X, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-s390x-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("f40b88607928b5ee34ff87c1d574c8493a1604d7a40474e1b03731184186f419") + }, + PythonDownload { + key: "cpython-3.12.2-windows-x86-none", + major: 3, + minor: 12, + patch: 2, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("ee985ae6a6a98f4d5bd19fd8c59f45235911d19b64e1dbd026261b8103f15db5") + }, + PythonDownload { + key: "cpython-3.12.2-linux-x86_64-gnu", + major: 3, + minor: 12, + patch: 2, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("15b61ed9d33b35ad014a13a68a55d8ea5ba7fb70945644747f4e53c659f2fed6") + }, + PythonDownload { + key: "cpython-3.12.2-linux-x86_64-musl", + major: 3, + minor: 12, + patch: 2, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("7ec1dc7ad8223ec5839a57d232fd3cf730987f7b0f88b2c4f15ee935bcabbaa9") + }, + PythonDownload { + key: "cpython-3.12.2-macos-x86_64-none", + major: 3, + minor: 12, + patch: 2, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("b4b4d19c36e86803aa0b4410395f5568bef28d82666efba926e44dbe06345a12") + }, + PythonDownload { + key: "cpython-3.12.2-windows-x86_64-none", + major: 3, + minor: 12, + patch: 2, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("a1daf5e8ceb23d34ea29b16b5123b06694810fe7acc5c8384426435c63bf731e") + }, + PythonDownload { + key: "cpython-3.12.1-linux-aarch64-gnu", + major: 3, + minor: 12, + patch: 1, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("9009da24f436611d0bf086b8ea62aaed1c27104af5b770ddcfc92b60db06da8c") + }, + PythonDownload { + key: "cpython-3.12.1-macos-aarch64-none", + major: 3, + minor: 12, + patch: 1, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("61e51e3490537b800fcefad718157cf775de41044e95aa538b63ab599f66f3a9") + }, + PythonDownload { + key: "cpython-3.12.1-linux-powerpc64le-gnu", + major: 3, + minor: 12, + patch: 1, + implementation: ImplementationName::Cpython, + arch: Arch::Powerpc64Le, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("b61686ce05c58c913e4fdb7e7c7105ed36d9bcdcd1a841e7f08b243f40d5cf77") + }, + PythonDownload { + key: "cpython-3.12.1-linux-s390x-gnu", + major: 3, + minor: 12, + patch: 1, + implementation: ImplementationName::Cpython, + arch: Arch::S390X, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-s390x-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("505a4fbace661a43b354a059022eb31efb406859a5f7227109ebf0f278f20503") + }, + PythonDownload { + key: "cpython-3.12.1-windows-x86-none", + major: 3, + minor: 12, + patch: 1, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("22866d35fdf58e90e75d6ba9aa78c288b452ea7041fa9bc5549eca9daa431883") + }, + PythonDownload { + key: "cpython-3.12.1-linux-x86_64-gnu", + major: 3, + minor: 12, + patch: 1, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("89ef67b617b8c9804965509b2d256f53439ceede83b5b64085315f038ad81e60") + }, + PythonDownload { + key: "cpython-3.12.1-linux-x86_64-musl", + major: 3, + minor: 12, + patch: 1, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("c4b07a02d8f0986b56e010a67132e5eeba1def4991c6c06ed184f831a484a06f") + }, + PythonDownload { + key: "cpython-3.12.1-macos-x86_64-none", + major: 3, + minor: 12, + patch: 1, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("bf2b176b0426d7b4d4909c1b19bbb25b4893f9ebdc61e32df144df2b10dcc800") + }, + PythonDownload { + key: "cpython-3.12.1-windows-x86_64-none", + major: 3, + minor: 12, + patch: 1, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("d9bc1b566250bf51818976bf98bf50e1f4c59b2503b50d29250cac5ab5ef6b38") + }, + PythonDownload { + key: "cpython-3.12.0-linux-aarch64-gnu", + major: 3, + minor: 12, + patch: 0, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("eb05c976374a9a44596ce340ab35e5461014f30202c3cbe10edcbfbe5ac4a6a1") + }, + PythonDownload { + key: "cpython-3.12.0-macos-aarch64-none", + major: 3, + minor: 12, + patch: 0, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("25fc8cd41e975d18d13bcc8f8beffa096ff8a0b86c4a737e1c6617900092c966") + }, + PythonDownload { + key: "cpython-3.12.0-linux-powerpc64le-gnu", + major: 3, + minor: 12, + patch: 0, + implementation: ImplementationName::Cpython, + arch: Arch::Powerpc64Le, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("800a89873e30e24bb1b6075f8cd718964537c5ba62bcdbefdcdae4de68ddccc4") + }, + PythonDownload { + key: "cpython-3.12.0-linux-s390x-gnu", + major: 3, + minor: 12, + patch: 0, + implementation: ImplementationName::Cpython, + arch: Arch::S390X, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-s390x-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("5b1a1effbb43df57ad014fcebf4b20089e504d89613e7b8db22d9ccb9fb00a6c") + }, + PythonDownload { + key: "cpython-3.12.0-windows-x86-none", + major: 3, + minor: 12, + patch: 0, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("465e91b6e6d0d1c40c8a4bce3642c4adcb9b75cf03fbd5fd5a33a36358249289") + }, + PythonDownload { + key: "cpython-3.12.0-linux-x86_64-gnu", + major: 3, + minor: 12, + patch: 0, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("a8c38cd2e53136c579632e2938d1b857f22e496c7dba99ad9a7ad6a67b43274a") + }, + PythonDownload { + key: "cpython-3.12.0-linux-x86_64-musl", + major: 3, + minor: 12, + patch: 0, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("91b42595cb4b69ff396e746dc492caf67b952a3ed1a367a4ace1acc965ed9cdb") + }, + PythonDownload { + key: "cpython-3.12.0-macos-x86_64-none", + major: 3, + minor: 12, + patch: 0, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("3b4781e7fd4efabe574ba0954e54c35c7d5ac4dc5b2990b40796c1c6aec67d79") + }, + PythonDownload { + key: "cpython-3.12.0-windows-x86_64-none", + major: 3, + minor: 12, + patch: 0, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("5bdff7ed56550d96f9b26a27a8c25f0cc58a03bff19e5f52bba84366183cab8b") + }, + PythonDownload { + key: "cpython-3.11.8-linux-aarch64-gnu", + major: 3, + minor: 11, + patch: 8, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("45bf082aca6b7d5e7261852720a72b92f5305e9fdb07b10f6588cb51d8f83ff2") + }, + PythonDownload { + key: "cpython-3.11.8-macos-aarch64-none", + major: 3, + minor: 11, + patch: 8, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("c0650884b929253b8688797d1955850f6e339bf0428b3d935f62ab3159f66362") + }, + PythonDownload { + key: "cpython-3.11.8-linux-powerpc64le-gnu", + major: 3, + minor: 11, + patch: 8, + implementation: ImplementationName::Cpython, + arch: Arch::Powerpc64Le, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("a9716f2eebebe03de47d6d5d603d6ff78abf5eb38f88bf7607b17fd85e74ff16") + }, + PythonDownload { + key: "cpython-3.11.8-linux-s390x-gnu", + major: 3, + minor: 11, + patch: 8, + implementation: ImplementationName::Cpython, + arch: Arch::S390X, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-s390x-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("d495830b5980ed689bd7588aa556bac9c43ff766d8a8b32e7791b8ed664b04f3") + }, + PythonDownload { + key: "cpython-3.11.8-windows-x86-none", + major: 3, + minor: 11, + patch: 8, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("c3e90962996177a027bd73dd9fd8c42a2d6ef832cda26db4ab4efc6105160537") + }, + PythonDownload { + key: "cpython-3.11.8-linux-x86_64-gnu", + major: 3, + minor: 11, + patch: 8, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("d959c43184878d564b5368ce4d753cf059600aafdf3e50280e850f94b5a4ba61") + }, + PythonDownload { + key: "cpython-3.11.8-linux-x86_64-musl", + major: 3, + minor: 11, + patch: 8, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("a03a9d8c1f770ce418716a2e8185df7b3a9e0012cdc220f9f2d24480a432650b") + }, + PythonDownload { + key: "cpython-3.11.8-macos-x86_64-none", + major: 3, + minor: 11, + patch: 8, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("54f8c8ad7313b3505e495bb093825d85eab244306ca4278836a2c7b5b74fb053") + }, + PythonDownload { + key: "cpython-3.11.8-windows-x86_64-none", + major: 3, + minor: 11, + patch: 8, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("6da82390f7ac49f6c4b19a5b8019c4ddc1eef2c5ad6a2f2d32773a27663a4e14") + }, + PythonDownload { + key: "cpython-3.11.7-linux-aarch64-gnu", + major: 3, + minor: 11, + patch: 7, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("e3a375f8f16198ccf8dbede231536544265e5b4b6b0f0df97c5b29503c5864e2") + }, + PythonDownload { + key: "cpython-3.11.7-macos-aarch64-none", + major: 3, + minor: 11, + patch: 7, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("c1f3dd13825906a5eae23ed8de9b653edb620568b2e0226eef3784eb1cce7eed") + }, + PythonDownload { + key: "cpython-3.11.7-linux-powerpc64le-gnu", + major: 3, + minor: 11, + patch: 7, + implementation: ImplementationName::Cpython, + arch: Arch::Powerpc64Le, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("016ed6470c599ea5cc4dbb9c3f3fe86be059ad4e1b6cd2df10e40b7ec6970f16") + }, + PythonDownload { + key: "cpython-3.11.7-linux-s390x-gnu", + major: 3, + minor: 11, + patch: 7, + implementation: ImplementationName::Cpython, + arch: Arch::S390X, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-s390x-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("91b33369025b7e0079f603cd2a99f9a5932daa8ded113d5090f29c075c993df7") + }, + PythonDownload { + key: "cpython-3.11.7-windows-x86-none", + major: 3, + minor: 11, + patch: 7, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("6613f1f9238d19969d8a2827deec84611cb772503207056cc9f0deb89bea48cd") + }, + PythonDownload { + key: "cpython-3.11.7-linux-x86_64-gnu", + major: 3, + minor: 11, + patch: 7, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("01bca7a2f457d4bd2b367640d9337d12b31db73d670a16500b7a751194942103") + }, + PythonDownload { + key: "cpython-3.11.7-linux-x86_64-musl", + major: 3, + minor: 11, + patch: 7, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("f387d373d64447bbba8a5657712f93b1dbdfd7246cdfe5a0493f39b83d46ec7c") + }, + PythonDownload { + key: "cpython-3.11.7-macos-x86_64-none", + major: 3, + minor: 11, + patch: 7, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("3f8caf73f2bfe22efa9666974c119727e163716e88af8ed3caa1e0ae5493de61") + }, + PythonDownload { + key: "cpython-3.11.7-windows-x86_64-none", + major: 3, + minor: 11, + patch: 7, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("89d1d8f080e5494ea57918fc5ecf3d483ffef943cd5a336e64da150cd44b4aa0") + }, + PythonDownload { + key: "cpython-3.11.6-linux-aarch64-gnu", + major: 3, + minor: 11, + patch: 6, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("d63d6eb065e60899b25853fe6bbd9f60ea6c3b12f4854adc75cb818bad55f4e9") + }, + PythonDownload { + key: "cpython-3.11.6-macos-aarch64-none", + major: 3, + minor: 11, + patch: 6, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("6e9007bcbbf51203e89c34a87ed42561630a35bc4eb04a565c92ba7159fe5826") + }, + PythonDownload { + key: "cpython-3.11.6-linux-powerpc64le-gnu", + major: 3, + minor: 11, + patch: 6, + implementation: ImplementationName::Cpython, + arch: Arch::Powerpc64Le, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("71c34db1165860a6bf458d817aef00dea96146130bf5f8bd7ee39b12892ef463") + }, + PythonDownload { + key: "cpython-3.11.6-linux-s390x-gnu", + major: 3, + minor: 11, + patch: 6, + implementation: ImplementationName::Cpython, + arch: Arch::S390X, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-s390x-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("78252aa883fed18de7bb9b146450e42dd75d78c345f56c1301bb042317a1d4f7") + }, + PythonDownload { + key: "cpython-3.11.6-windows-x86-none", + major: 3, + minor: 11, + patch: 6, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("2670731428191d4476bf260c8144ccf06f9e5f8ac6f2de1dc444ca96ab627082") + }, + PythonDownload { + key: "cpython-3.11.6-linux-x86_64-gnu", + major: 3, + minor: 11, + patch: 6, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("6e7889a15d861f1860ed84f3f5ea4586d198aa003b22556d91e180a44184dcd7") + }, + PythonDownload { + key: "cpython-3.11.6-linux-x86_64-musl", + major: 3, + minor: 11, + patch: 6, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("1b6e32ec93c5a18a03a9da9e2a3a3738d67b733df0795edcff9fd749c33ab931") + }, + PythonDownload { + key: "cpython-3.11.6-macos-x86_64-none", + major: 3, + minor: 11, + patch: 6, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("3685156e4139e89484c071ba1a1b85be0b4e302a786de5a170d3b0713863c2e8") + }, + PythonDownload { + key: "cpython-3.11.6-windows-x86_64-none", + major: 3, + minor: 11, + patch: 6, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("38d2c2fa2f9effbf486207bef7141d1b5c385ad30729ab0c976e6a852a2a9401") + }, + PythonDownload { + key: "cpython-3.11.5-linux-aarch64-gnu", + major: 3, + minor: 11, + patch: 5, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("ac4b1e91d1cb7027595bfa4667090406331b291b2e346fb74e42b7031b216787") + }, + PythonDownload { + key: "cpython-3.11.5-macos-aarch64-none", + major: 3, + minor: 11, + patch: 5, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("7bee180b764722a73c2599fbe2c3a6121cf6bbcb08cb3082851e93c43fe130e7") + }, + PythonDownload { + key: "cpython-3.11.5-linux-powerpc64le-gnu", + major: 3, + minor: 11, + patch: 5, + implementation: ImplementationName::Cpython, + arch: Arch::Powerpc64Le, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("1aee6a613385a6355bed61a9b12259a5ed16e871b5bdfe5c9fe98b46ee2bb05e") + }, + PythonDownload { + key: "cpython-3.11.5-linux-s390x-gnu", + major: 3, + minor: 11, + patch: 5, + implementation: ImplementationName::Cpython, + arch: Arch::S390X, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-s390x-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("b0819032ec336d6e1d9e9bfdba546bf854a7b7248f8720a6d07da72c4ac927e5") + }, + PythonDownload { + key: "cpython-3.11.5-linux-x86-gnu", + major: 3, + minor: 11, + patch: 5, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-i686-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("75d27b399b323c25d8250fda9857e388bf1b03ba1eb7925ec23cf12042a63a88") + }, + PythonDownload { + key: "cpython-3.11.5-windows-x86-none", + major: 3, + minor: 11, + patch: 5, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("c9ffe9c2c88685ce3064f734cbdfede0a07de7d826fada58f8045f3bd8f81a9d") + }, + PythonDownload { + key: "cpython-3.11.5-linux-x86_64-gnu", + major: 3, + minor: 11, + patch: 5, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("93ee095b53de5a74af18e612f55095fcf3118c3c0a87eb6344d8eaca396bfb2d") + }, + PythonDownload { + key: "cpython-3.11.5-linux-x86_64-musl", + major: 3, + minor: 11, + patch: 5, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("9dcf19ee54fb936cb9fd0f02fd655e790663534bc12e142e460c1b30a0b54dbd") + }, + PythonDownload { + key: "cpython-3.11.5-macos-x86_64-none", + major: 3, + minor: 11, + patch: 5, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("e43d70a49919641ca2939a5a9107b13d5fef8c13af0f511a33a94bb6af2044f0") + }, + PythonDownload { + key: "cpython-3.11.5-windows-x86_64-none", + major: 3, + minor: 11, + patch: 5, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("6e4d20e6d498f9edeb3c28cb9541ad20f675f16da350b078e40a9dcfd93cdc3d") + }, + PythonDownload { + key: "cpython-3.11.4-linux-aarch64-gnu", + major: 3, + minor: 11, + patch: 4, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("37cf00439b57adf7ffef4a349d62dcf09739ba67b670e903b00b25f81fbb8a68") + }, + PythonDownload { + key: "cpython-3.11.4-macos-aarch64-none", + major: 3, + minor: 11, + patch: 4, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("988d476c806f71a3233ff4266eda166a5d28cf83ba306ac88b4220554fc83e8c") + }, + PythonDownload { + key: "cpython-3.11.4-linux-powerpc64le-gnu", + major: 3, + minor: 11, + patch: 4, + implementation: ImplementationName::Cpython, + arch: Arch::Powerpc64Le, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("b9f76fd226bfcbc6a8769934b17323ca3b563f1c24660582fcccfa6d0c7146af") + }, + PythonDownload { + key: "cpython-3.11.4-linux-s390x-gnu", + major: 3, + minor: 11, + patch: 4, + implementation: ImplementationName::Cpython, + arch: Arch::S390X, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-s390x-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("8ef6b5fa86b4abf51865b346b7cf8df36e474ed308869fc0ac3fe82de39194a4") + }, + PythonDownload { + key: "cpython-3.11.4-linux-x86-gnu", + major: 3, + minor: 11, + patch: 4, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-i686-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("a9051364b5c2e28205f8484cae03d16c86b45df5d117324e846d0f5e870fe9fb") + }, + PythonDownload { + key: "cpython-3.11.4-windows-x86-none", + major: 3, + minor: 11, + patch: 4, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("0d22f43c5bb3f27ff2f9e8c60b0d7abd391bb2cac1790b0960970ff5580f6e9a") + }, + PythonDownload { + key: "cpython-3.11.4-linux-x86_64-gnu", + major: 3, + minor: 11, + patch: 4, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("1b5fdeb2dc56c30843e7350f1684178755fae91666a0a987e5eb39074c42a052") + }, + PythonDownload { + key: "cpython-3.11.4-linux-x86_64-musl", + major: 3, + minor: 11, + patch: 4, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("fc2ea02ced875c90b8d025b409d58c4f045df8ba951bfa2b8b0a3cfe11c3b41c") + }, + PythonDownload { + key: "cpython-3.11.4-macos-x86_64-none", + major: 3, + minor: 11, + patch: 4, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("6d9765785316c7f1c07def71b413c92c84302f798b30ee09e2e0b5da28353a51") + }, + PythonDownload { + key: "cpython-3.11.4-windows-x86_64-none", + major: 3, + minor: 11, + patch: 4, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("1692d795d6199b2261161ae54250009ffad0317929302903f6f2c773befd4d76") + }, + PythonDownload { + key: "cpython-3.11.3-linux-aarch64-gnu", + major: 3, + minor: 11, + patch: 3, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("991521082b0347878ba855c4986d77cc805c22ef75159bc95dd24bfd80275e27") + }, + PythonDownload { + key: "cpython-3.11.3-macos-aarch64-none", + major: 3, + minor: 11, + patch: 3, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("cd296d628ceebf55a78c7f6a7aed379eba9dbd72045d002e1c2c85af0d6f5049") + }, + PythonDownload { + key: "cpython-3.11.3-linux-powerpc64le-gnu", + major: 3, + minor: 11, + patch: 3, + implementation: ImplementationName::Cpython, + arch: Arch::Powerpc64Le, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("241d583be3ecc34d76fafa0d186cb504ce5625eb2c0e895dc4f4073a649e5c73") + }, + PythonDownload { + key: "cpython-3.11.3-linux-x86-gnu", + major: 3, + minor: 11, + patch: 3, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-i686-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("7bd694eb848328e96f524ded0f9b9eca6230d71fce3cd49b335a5c33450f3e04") + }, + PythonDownload { + key: "cpython-3.11.3-windows-x86-none", + major: 3, + minor: 11, + patch: 3, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("877c90ef778a526aa25ab417034f5e70728ac14e5eb1fa5cfd741f531203a3fc") + }, + PythonDownload { + key: "cpython-3.11.3-linux-x86_64-gnu", + major: 3, + minor: 11, + patch: 3, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("4f1192179e1f62e69b8b45f7f699e6f0100fb0b8a39aad7a48472794d0c24bd4") + }, + PythonDownload { + key: "cpython-3.11.3-linux-x86_64-musl", + major: 3, + minor: 11, + patch: 3, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("8c5adef5bc627f39e93b920af86ef740e917aa698530ff727978d446a07bbd8b") + }, + PythonDownload { + key: "cpython-3.11.3-macos-x86_64-none", + major: 3, + minor: 11, + patch: 3, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("2fbb31a8bc6663e2d31d3054319b51a29b1915c03222a94b9d563233e11d1bef") + }, + PythonDownload { + key: "cpython-3.11.3-windows-x86_64-none", + major: 3, + minor: 11, + patch: 3, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("9d27e607fb1cb2d766e17f27853013d8c0f0b09ac53127aaff03ec89ab13370d") + }, + PythonDownload { + key: "cpython-3.11.1-linux-aarch64-gnu", + major: 3, + minor: 11, + patch: 1, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("8fe27d850c02aa7bb34088fad5b48df90b4b841f40e1472243b8ab9da8776e40") + }, + PythonDownload { + key: "cpython-3.11.1-macos-aarch64-none", + major: 3, + minor: 11, + patch: 1, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("da187194cc351d827232b1d2d85b2855d7e25a4ada3e47bc34b4f87b1d989be5") + }, + PythonDownload { + key: "cpython-3.11.1-linux-x86-gnu", + major: 3, + minor: 11, + patch: 1, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-i686-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("7986ebe82c07ecd2eb94fd1b3c9ebbb2366db2360e38f29ae0543e857551d0bf") + }, + PythonDownload { + key: "cpython-3.11.1-windows-x86-none", + major: 3, + minor: 11, + patch: 1, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("b062ac2c72a85510fb9300675bd5c716baede21e9482ef6335247b4aa006584c") + }, + PythonDownload { + key: "cpython-3.11.1-linux-x86_64-gnu", + major: 3, + minor: 11, + patch: 1, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("b5bf700afc77588d853832d10b74ba793811cbec41b02ebc2c39a8b9987aacdd") + }, + PythonDownload { + key: "cpython-3.11.1-linux-x86_64-musl", + major: 3, + minor: 11, + patch: 1, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("ec5da5b428f6d91d96cde2621c0380f67bb96e4257d2628bc70b50e75ec5f629") + }, + PythonDownload { + key: "cpython-3.11.1-macos-x86_64-none", + major: 3, + minor: 11, + patch: 1, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("0eb61be53ee13cf75a30b8a164ef513a2c7995b25b118a3a503245d46231b13a") + }, + PythonDownload { + key: "cpython-3.11.1-windows-x86_64-none", + major: 3, + minor: 11, + patch: 1, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("f5c46fffda7d7894b975af728f739b02d1cec50fd4a3ea49f69de9ceaae74b17") + }, + PythonDownload { + key: "cpython-3.10.13-linux-aarch64-gnu", + major: 3, + minor: 10, + patch: 13, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("06a53040504e1e2fdcb32dc0d61b123bea76725b5c14031c8f64e28f52ae5a5f") + }, + PythonDownload { + key: "cpython-3.10.13-macos-aarch64-none", + major: 3, + minor: 10, + patch: 13, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("57b83a4aa32bdbe7611f1290313ef24f2574dff5fa59181c0ccb26c14c688b73") + }, + PythonDownload { + key: "cpython-3.10.13-linux-powerpc64le-gnu", + major: 3, + minor: 10, + patch: 13, + implementation: ImplementationName::Cpython, + arch: Arch::Powerpc64Le, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("cab4c8756445d1d1987c7c94d3bcf323684e44fb9070329d8287d4c38e155711") + }, + PythonDownload { + key: "cpython-3.10.13-linux-s390x-gnu", + major: 3, + minor: 10, + patch: 13, + implementation: ImplementationName::Cpython, + arch: Arch::S390X, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-s390x-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("fe46914541126297c7a8636845c2e7188868eaa617bb6e293871fca4a5cb63f7") + }, + PythonDownload { + key: "cpython-3.10.13-linux-x86-gnu", + major: 3, + minor: 10, + patch: 13, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.10.13%2B20230826-i686-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("08a3a1ff61b7ed2c87db7a9f88630781d98fabc2efb499f38ae0ead05973eb56") + }, + PythonDownload { + key: "cpython-3.10.13-windows-x86-none", + major: 3, + minor: 10, + patch: 13, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("c8b99dcf267c574fdfbdf4e9d63ec7a4aa4608565fee3fba0b2f73843b9713b2") + }, + PythonDownload { + key: "cpython-3.10.13-linux-x86_64-gnu", + major: 3, + minor: 10, + patch: 13, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("41b20e9d87f57d27f608685b714a57eea81c9e079aa647d59837ec6659536626") + }, + PythonDownload { + key: "cpython-3.10.13-linux-x86_64-musl", + major: 3, + minor: 10, + patch: 13, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("fd18e6039be25bf23d13caf5140569df71d61312b823b715b3c788747fec48e9") + }, + PythonDownload { + key: "cpython-3.10.13-macos-x86_64-none", + major: 3, + minor: 10, + patch: 13, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("a41c1e28e2a646bac69e023873d40a43c5958d251c6adfa83d5811a7cb034c7a") + }, + PythonDownload { + key: "cpython-3.10.13-windows-x86_64-none", + major: 3, + minor: 10, + patch: 13, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("6a2c8f37509556e5d463b1f437cdf7772ebd84cdf183c258d783e64bb3109505") + }, + PythonDownload { + key: "cpython-3.10.12-linux-aarch64-gnu", + major: 3, + minor: 10, + patch: 12, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("e30f2b4fd9bd79b9122e2975f3c17c9ddd727f8326b2e246378e81f7ecc7d74f") + }, + PythonDownload { + key: "cpython-3.10.12-macos-aarch64-none", + major: 3, + minor: 10, + patch: 12, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("a7d0cadbe867cc53dd47d7327244154157a7cca02edb88cf3bb760a4f91d4e44") + }, + PythonDownload { + key: "cpython-3.10.12-linux-powerpc64le-gnu", + major: 3, + minor: 10, + patch: 12, + implementation: ImplementationName::Cpython, + arch: Arch::Powerpc64Le, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("c318050fa91d84d447f5c8a5887a44f1cc8dd34d4c1d357cd755407d46ed1b21") + }, + PythonDownload { + key: "cpython-3.10.12-linux-s390x-gnu", + major: 3, + minor: 10, + patch: 12, + implementation: ImplementationName::Cpython, + arch: Arch::S390X, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-s390x-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("756579b52acb9b13b162ac901e56ff311def443e69d7f7259a91198b76a30ecb") + }, + PythonDownload { + key: "cpython-3.10.12-linux-x86-gnu", + major: 3, + minor: 10, + patch: 12, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-i686-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("89c83fcdfd41c67e2dd2a037982556c657dc55fc1938c6f6cdcd5ffa614c1fb3") + }, + PythonDownload { + key: "cpython-3.10.12-windows-x86-none", + major: 3, + minor: 10, + patch: 12, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("0743b9976f20b06d9cf12de9d1b2dfe06b13f76978275e9dac73a275624bde2c") + }, + PythonDownload { + key: "cpython-3.10.12-linux-x86_64-gnu", + major: 3, + minor: 10, + patch: 12, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("fb7354fcee7b17dd0793ebd3f6f1fc8b7b205332afcf8d700cc1119f2dc33ff7") + }, + PythonDownload { + key: "cpython-3.10.12-linux-x86_64-musl", + major: 3, + minor: 10, + patch: 12, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("b343cbe7c41b7698b568ea5252328cdccb213100efa71da8d3db6e21afd9f6cf") + }, + PythonDownload { + key: "cpython-3.10.12-macos-x86_64-none", + major: 3, + minor: 10, + patch: 12, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("f1fa448384dd48033825e56ee6b5afc76c5dd67dcf2b73b61d2b252ae2e87bca") + }, + PythonDownload { + key: "cpython-3.10.12-windows-x86_64-none", + major: 3, + minor: 10, + patch: 12, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("cb6e7c84d9e369a0ee76c9ea73d415a113ba9982db58f44e6bab5414838d35f3") + }, + PythonDownload { + key: "cpython-3.10.11-linux-aarch64-gnu", + major: 3, + minor: 10, + patch: 11, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("a5271cc014f2ce2ab54a0789556c15b84668e2afcc530512818c4b87c6a94483") + }, + PythonDownload { + key: "cpython-3.10.11-macos-aarch64-none", + major: 3, + minor: 10, + patch: 11, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("da9c8a3cd04485fd397387ea2fa56f3cac71827aafb51d8438b2868f86eb345b") + }, + PythonDownload { + key: "cpython-3.10.11-linux-powerpc64le-gnu", + major: 3, + minor: 10, + patch: 11, + implementation: ImplementationName::Cpython, + arch: Arch::Powerpc64Le, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("ac32e3788109ff0cc536a6108072d9203217df744cf56d3a4ab0b19857d8e244") + }, + PythonDownload { + key: "cpython-3.10.11-linux-x86-gnu", + major: 3, + minor: 10, + patch: 11, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-i686-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("9304d6eeef48bd246a2959ebc76b20dbb2c6a81aa1d214f4471cb273c11717f2") + }, + PythonDownload { + key: "cpython-3.10.11-windows-x86-none", + major: 3, + minor: 10, + patch: 11, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("60e76e136ab23b891ed1212e58bd11a73a19cd9fd884ec1c5653ca1c159d674e") + }, + PythonDownload { + key: "cpython-3.10.11-linux-x86_64-gnu", + major: 3, + minor: 10, + patch: 11, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("544e5020f71ad1525dbc92b08e429cc1e1e11866c48c07d91e99f531b9ba68b0") + }, + PythonDownload { + key: "cpython-3.10.11-linux-x86_64-musl", + major: 3, + minor: 10, + patch: 11, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("7918188e01a266915dd0945711e274d45c8d7fb540d48240e13c4fd96f43afbb") + }, + PythonDownload { + key: "cpython-3.10.11-macos-x86_64-none", + major: 3, + minor: 10, + patch: 11, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("e84c12aa0285235eed365971ceedf040f4d8014f5342d371e138a4da9e4e9b7c") + }, + PythonDownload { + key: "cpython-3.10.11-windows-x86_64-none", + major: 3, + minor: 10, + patch: 11, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("9b4dc4a335b6122ce783bc80f5015b683e3ab1a56054751c5df494db0521da67") + }, + PythonDownload { + key: "cpython-3.10.9-linux-aarch64-gnu", + major: 3, + minor: 10, + patch: 9, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("2c0996dd1fe35314e06e042081b24fb53f3b7b361c3e1b94a6ed659c275ca069") + }, + PythonDownload { + key: "cpython-3.10.9-macos-aarch64-none", + major: 3, + minor: 10, + patch: 9, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("2508b8d4b725bb45c3e03d2ddd2b8441f1a74677cb6bd6076e692c0923135ded") + }, + PythonDownload { + key: "cpython-3.10.9-linux-x86-gnu", + major: 3, + minor: 10, + patch: 9, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-i686-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("f8c3a63620f412c4a9ccfb6e2435a96a55775550c81a452d164caa6d03a6a1da") + }, + PythonDownload { + key: "cpython-3.10.9-windows-x86-none", + major: 3, + minor: 10, + patch: 9, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("3d79cfd229ec12b678bbfd79c30fb4cbad9950d6bfb29741d2315b11839998b4") + }, + PythonDownload { + key: "cpython-3.10.9-linux-x86_64-gnu", + major: 3, + minor: 10, + patch: 9, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("a90a45ba7afcbd1df9aef96a614acbb210607299ac74dadbb6bd66af22be34db") + }, + PythonDownload { + key: "cpython-3.10.9-linux-x86_64-musl", + major: 3, + minor: 10, + patch: 9, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("1310f187a73b00164ec4ca34e643841c5c34cbb93fe0b3a3f9504e5ea5001ec7") + }, + PythonDownload { + key: "cpython-3.10.9-macos-x86_64-none", + major: 3, + minor: 10, + patch: 9, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("1153b4d3b03cf1e1d8ec93c098160586f665fcc2d162c0812140a716a688df58") + }, + PythonDownload { + key: "cpython-3.10.9-windows-x86_64-none", + major: 3, + minor: 10, + patch: 9, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("4cfa6299a78a3959102c461d126e4869616f0a49c60b44220c000fc9aecddd78") + }, + PythonDownload { + key: "cpython-3.10.8-linux-aarch64-gnu", + major: 3, + minor: 10, + patch: 8, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("879e76260be226512693e37a28cc3a6670b5ee270a4440e4b04a7b415dba451c") + }, + PythonDownload { + key: "cpython-3.10.8-macos-aarch64-none", + major: 3, + minor: 10, + patch: 8, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("f8ba5f87153a17717e900ff7bba20e2eefe8a53a5bd3c78f9f6922d6d910912d") + }, + PythonDownload { + key: "cpython-3.10.8-linux-x86-gnu", + major: 3, + minor: 10, + patch: 8, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-i686-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("ab434eccffeec4f6f51af017e4eed69d4f1ea55f48c5b89b8a8779df3fa799df") + }, + PythonDownload { + key: "cpython-3.10.8-windows-x86-none", + major: 3, + minor: 10, + patch: 8, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("7547ea172f7fa3d7619855f28780da9feb615b6cb52c5c64d34f65b542799fee") + }, + PythonDownload { + key: "cpython-3.10.8-linux-x86_64-gnu", + major: 3, + minor: 10, + patch: 8, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("c86182951a82e761588476a0155afe99ae4ae1030e4a8e1e8bcb8e1d42f6327c") + }, + PythonDownload { + key: "cpython-3.10.8-linux-x86_64-musl", + major: 3, + minor: 10, + patch: 8, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("bb87e933afcfd2e8de045e5a691feff1fb8fb06a09315b37d187762fddfc4546") + }, + PythonDownload { + key: "cpython-3.10.8-macos-x86_64-none", + major: 3, + minor: 10, + patch: 8, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("a18f81ecc7da0779be960ad35c561a834866c0e6d1310a4f742fddfd6163753f") + }, + PythonDownload { + key: "cpython-3.10.8-windows-x86_64-none", + major: 3, + minor: 10, + patch: 8, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("ab40f9584be896c697c5fca351ab82d7b55f01b8eb0494f0a15a67562e49161a") + }, + PythonDownload { + key: "cpython-3.10.7-linux-aarch64-gnu", + major: 3, + minor: 10, + patch: 7, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("9f346729b523e860194635eb67c9f6bc8f12728ba7ddfe4fd80f2e6d685781e3") + }, + PythonDownload { + key: "cpython-3.10.7-macos-aarch64-none", + major: 3, + minor: 10, + patch: 7, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("9f44cf63441a90f4ec99a032a2bda43971ae7964822daa0ee730a9cba15d50da") + }, + PythonDownload { + key: "cpython-3.10.7-linux-x86-gnu", + major: 3, + minor: 10, + patch: 7, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-i686-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("a79816c50abeb2752530f68b4d7d95b6f48392f44a9a7f135b91807d76872972") + }, + PythonDownload { + key: "cpython-3.10.7-windows-x86-none", + major: 3, + minor: 10, + patch: 7, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("323532701cb468199d6f14031b991f945d4bbf986ca818185e17e132d3763bdf") + }, + PythonDownload { + key: "cpython-3.10.7-linux-x86_64-gnu", + major: 3, + minor: 10, + patch: 7, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("ce3fe27e6ca3a0e75a7f4f3b6568cd1bf967230a67e73393e94a23380dddaf10") + }, + PythonDownload { + key: "cpython-3.10.7-linux-x86_64-musl", + major: 3, + minor: 10, + patch: 7, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("7f2c933d23c0f38cf145c2d6c65b5cf53bb589690d394fd4c01b2230c23c2bff") + }, + PythonDownload { + key: "cpython-3.10.7-macos-x86_64-none", + major: 3, + minor: 10, + patch: 7, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("e03e28dc9fe55ea5ca06fece8f2f2a16646b217d28c0cd09ebcd512f444fdc90") + }, + PythonDownload { + key: "cpython-3.10.7-windows-x86_64-none", + major: 3, + minor: 10, + patch: 7, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("5363974e6ee6c91dbd6bc3533e38b02a26abc2ff1c9a095912f237b916be22d3") + }, + PythonDownload { + key: "cpython-3.10.6-linux-aarch64-gnu", + major: 3, + minor: 10, + patch: 6, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("edc1c9742b824caebbc5cb224c8990aa8658b81593fd9219accf3efa3e849501") + }, + PythonDownload { + key: "cpython-3.10.6-macos-aarch64-none", + major: 3, + minor: 10, + patch: 6, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("159230851a69cf5cab80318bce48674244d7c6304de81f44c22ff0abdf895cfa") + }, + PythonDownload { + key: "cpython-3.10.6-linux-x86-gnu", + major: 3, + minor: 10, + patch: 6, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-i686-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("07fa4f5499b8885d1eea49caf5476d76305ab73494b7398dfd22c14093859e4f") + }, + PythonDownload { + key: "cpython-3.10.6-windows-x86-none", + major: 3, + minor: 10, + patch: 6, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("8d9a259e15d5a1be48ef13cd5627d7f6c15eadf41a3539e99ed1deee668c075e") + }, + PythonDownload { + key: "cpython-3.10.6-linux-x86_64-gnu", + major: 3, + minor: 10, + patch: 6, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("407e5951e39f5652b32b72b715c4aa772dd8c2da1065161c58c30a1f976dd1b2") + }, + PythonDownload { + key: "cpython-3.10.6-linux-x86_64-musl", + major: 3, + minor: 10, + patch: 6, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("f859a72da0bb2f1261f8cebdac931b05b59474c7cb65cee8e85c34fc014dd452") + }, + PythonDownload { + key: "cpython-3.10.6-macos-x86_64-none", + major: 3, + minor: 10, + patch: 6, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("9405499573a7aa8b67d070d096ded4f3e571f18c2b34762606ecc8025290b122") + }, + PythonDownload { + key: "cpython-3.10.6-windows-x86_64-none", + major: 3, + minor: 10, + patch: 6, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("01dc349721594b1bb5b582651f81479a24352f718fdf6279101caa0f377b160a") + }, + PythonDownload { + key: "cpython-3.10.5-linux-aarch64-gnu", + major: 3, + minor: 10, + patch: 5, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("9fa6970a3d0a5dc26c4ed272bb1836d1f1f7a8f4b9d67f634d0262ff8c1fed0b") + }, + PythonDownload { + key: "cpython-3.10.5-macos-aarch64-none", + major: 3, + minor: 10, + patch: 5, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("f68d25dbe9daa96187fa9e05dd8969f46685547fecf1861a99af898f96a5379e") + }, + PythonDownload { + key: "cpython-3.10.5-linux-x86-gnu", + major: 3, + minor: 10, + patch: 5, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-i686-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("63fcfc425adabc034c851dadfb499de3083fd7758582191c12162ad2471256b0") + }, + PythonDownload { + key: "cpython-3.10.5-windows-x86-none", + major: 3, + minor: 10, + patch: 5, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("e201192f0aa73904bc5a5f43d1ce4c9fb243dfe02138e690676713fe02c7d662") + }, + PythonDownload { + key: "cpython-3.10.5-linux-x86_64-gnu", + major: 3, + minor: 10, + patch: 5, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("f8dfb83885d1cbc82febfa613258c1f6954ea88ef43ed7dc710d6df20efecdab") + }, + PythonDownload { + key: "cpython-3.10.5-linux-x86_64-musl", + major: 3, + minor: 10, + patch: 5, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("3682e0add14a3bac654afe467a84981628b0c7ebdccd4ebf26dfaa916238e2fe") + }, + PythonDownload { + key: "cpython-3.10.5-macos-x86_64-none", + major: 3, + minor: 10, + patch: 5, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("5e372e6738a733532aa985730d9a47ee4c77b7c706e91ef61d37aacbb2e54845") + }, + PythonDownload { + key: "cpython-3.10.5-windows-x86_64-none", + major: 3, + minor: 10, + patch: 5, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("cff35feefe423d4282e9a3e1bb756d0acbb2f776b1ada82c44c71ac3e1491448") + }, + PythonDownload { + key: "cpython-3.10.4-linux-aarch64-gnu", + major: 3, + minor: 10, + patch: 4, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("092369e9d170c4c1074e1b305accb74f9486e6185d2e3f3f971869ff89538d3e") + }, + PythonDownload { + key: "cpython-3.10.4-macos-aarch64-none", + major: 3, + minor: 10, + patch: 4, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("c404f226195d79933b1e0a3ec88f0b79d35c873de592e223e11008f3a37f83d6") + }, + PythonDownload { + key: "cpython-3.10.4-linux-x86-gnu", + major: 3, + minor: 10, + patch: 4, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-i686-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("ba940a74a7434fe78d81aed9fb1e5ccdc3d97191a2db35716fc94e3b6604ace0") + }, + PythonDownload { + key: "cpython-3.10.4-windows-x86-none", + major: 3, + minor: 10, + patch: 4, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("c37a47e46de93473916f700a790cb43515f00745fba6790004e2731ec934f4d3") + }, + PythonDownload { + key: "cpython-3.10.4-linux-x86_64-gnu", + major: 3, + minor: 10, + patch: 4, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("7699f76ef89b436b452eacdbab508da3cd94146ba29b099f5cb6e250afba3210") + }, + PythonDownload { + key: "cpython-3.10.4-linux-x86_64-musl", + major: 3, + minor: 10, + patch: 4, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("8b8b97f7746a3deca91ada408025457ced34f582dad2114b33ce6fec9cf35b28") + }, + PythonDownload { + key: "cpython-3.10.4-macos-x86_64-none", + major: 3, + minor: 10, + patch: 4, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("e447f00fe53168d18cbfe110645dbf33982a17580b9e4424a411f9245d99cd21") + }, + PythonDownload { + key: "cpython-3.10.4-windows-x86_64-none", + major: 3, + minor: 10, + patch: 4, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("d636dc1bcca74dd9c6e3b26f7c081b3e229336e8378fe554bf8ba65fe780a2ac") + }, + PythonDownload { + key: "cpython-3.10.3-linux-aarch64-gnu", + major: 3, + minor: 10, + patch: 3, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("101284d27578438da200be1f6b9a1ba621432c5549fa5517797ec320bf75e3d5") + }, + PythonDownload { + key: "cpython-3.10.3-macos-aarch64-none", + major: 3, + minor: 10, + patch: 3, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("b1abefd0fc66922cf9749e4d5ceb97df4d3cfad0cd9cdc4bd04262a68d565698") + }, + PythonDownload { + key: "cpython-3.10.3-linux-x86-gnu", + major: 3, + minor: 10, + patch: 3, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-i686-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("43c1cd6e203bfba1a2eeb96cd2a15ce0ebde0e72ecc9555934116459347a9c28") + }, + PythonDownload { + key: "cpython-3.10.3-windows-x86-none", + major: 3, + minor: 10, + patch: 3, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("fbc0924a138937fe435fcdb20b0c6241290558e07f158e5578bd91cc8acef469") + }, + PythonDownload { + key: "cpython-3.10.3-linux-x86_64-gnu", + major: 3, + minor: 10, + patch: 3, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("04760d869234ee8f801feb08edc042a6965320f6c0a7aedf92ec35501fef3b21") + }, + PythonDownload { + key: "cpython-3.10.3-linux-x86_64-musl", + major: 3, + minor: 10, + patch: 3, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("7c034d8a5787744939335ce43d64f2ddcc830a74e63773408d0c8f3c3a4e7916") + }, + PythonDownload { + key: "cpython-3.10.3-macos-x86_64-none", + major: 3, + minor: 10, + patch: 3, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("bc5d6f284b506104ff6b4e36cec84cbdb4602dfed4c6fe19971a808eb8c439ec") + }, + PythonDownload { + key: "cpython-3.10.3-windows-x86_64-none", + major: 3, + minor: 10, + patch: 3, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("72b91d26f54321ba90a86a3bbc711fa1ac31e0704fec352b36e70b0251ffb13c") + }, + PythonDownload { + key: "cpython-3.10.2-linux-aarch64-gnu", + major: 3, + minor: 10, + patch: 2, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("9936f1549f950311229465de509b35c062aa474e504c20a1d6f0f632da57e002") + }, + PythonDownload { + key: "cpython-3.10.2-macos-aarch64-none", + major: 3, + minor: 10, + patch: 2, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("1ef939fd471a9d346a7bc43d2c16fb483ddc4f98af6dad7f08a009e299977a1a") + }, + PythonDownload { + key: "cpython-3.10.2-linux-x86-gnu", + major: 3, + minor: 10, + patch: 2, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-i686-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("9be2a667f29ed048165cfb3f5dbe61703fd3e5956f8f517ae098740ac8411c0b") + }, + PythonDownload { + key: "cpython-3.10.2-windows-x86-none", + major: 3, + minor: 10, + patch: 2, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("698b09b1b8321a4dc43d62f6230b62adcd0df018b2bcf5f1b4a7ce53dcf23bcc") + }, + PythonDownload { + key: "cpython-3.10.2-linux-x86_64-gnu", + major: 3, + minor: 10, + patch: 2, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("d22d85f60b2ef982b747adda2d1bde4a32c23c3d8f652c00ce44526750859e4e") + }, + PythonDownload { + key: "cpython-3.10.2-linux-x86_64-musl", + major: 3, + minor: 10, + patch: 2, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("df246cf27db346081935d33ce0344a185d1f08b04a4500eb1e21d4d922ee7eb4") + }, + PythonDownload { + key: "cpython-3.10.2-macos-x86_64-none", + major: 3, + minor: 10, + patch: 2, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("bacf720c13ab67685a384f1417e9c2420972d88f29c8b7c26e72874177f2d120") + }, + PythonDownload { + key: "cpython-3.10.2-windows-x86_64-none", + major: 3, + minor: 10, + patch: 2, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("7397e78a4fbe429144adc1f33af942bdd5175184e082ac88f3023b3a740dd1a0") + }, + PythonDownload { + key: "cpython-3.10.0-linux-aarch64-gnu", + major: 3, + minor: 10, + patch: 0, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.10.0-aarch64-unknown-linux-gnu-debug-20211017T1616.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.10.0-macos-aarch64-none", + major: 3, + minor: 10, + patch: 0, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.10.0-aarch64-apple-darwin-pgo%2Blto-20211017T1616.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.10.0-linux-x86-gnu", + major: 3, + minor: 10, + patch: 0, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.10.0-i686-unknown-linux-gnu-debug-20211017T1616.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.10.0-windows-x86-none", + major: 3, + minor: 10, + patch: 0, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.10.0-i686-pc-windows-msvc-shared-pgo-20211017T1616.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.10.0-linux-x86_64-gnu", + major: 3, + minor: 10, + patch: 0, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.10.0-x86_64-unknown-linux-gnu-debug-20211017T1616.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.10.0-linux-x86_64-musl", + major: 3, + minor: 10, + patch: 0, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.10.0-x86_64-unknown-linux-musl-lto-20211017T1616.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.10.0-macos-x86_64-none", + major: 3, + minor: 10, + patch: 0, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.10.0-x86_64-apple-darwin-pgo%2Blto-20211017T1616.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.10.0-windows-x86_64-none", + major: 3, + minor: 10, + patch: 0, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.10.0-x86_64-pc-windows-msvc-shared-pgo-20211017T1616.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.18-linux-aarch64-gnu", + major: 3, + minor: 9, + patch: 18, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("d27efd4609a3e15ff901040529d5689be99f2ebfe5132ab980d066d775068265") + }, + PythonDownload { + key: "cpython-3.9.18-macos-aarch64-none", + major: 3, + minor: 9, + patch: 18, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("579f9b68bbb3a915cbab9682e4d3c253bc96b0556b8a860982c49c25c61f974a") + }, + PythonDownload { + key: "cpython-3.9.18-linux-powerpc64le-gnu", + major: 3, + minor: 9, + patch: 18, + implementation: ImplementationName::Cpython, + arch: Arch::Powerpc64Le, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("c138eef19229351226a11e752230b8aa9d499ba9720f9f0574fa3260ccacb99b") + }, + PythonDownload { + key: "cpython-3.9.18-linux-s390x-gnu", + major: 3, + minor: 9, + patch: 18, + implementation: ImplementationName::Cpython, + arch: Arch::S390X, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-s390x-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("dd05eff699ce5a7eee545bc05e4869c4e64ee02bf0c70691bcee215604c6b393") + }, + PythonDownload { + key: "cpython-3.9.18-linux-x86-gnu", + major: 3, + minor: 9, + patch: 18, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.9.18%2B20230826-i686-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("7faf8fdfbad04e0356a9d52c9b8be4d40ffef85c9ab3e312c45bd64997ef8aa9") + }, + PythonDownload { + key: "cpython-3.9.18-windows-x86-none", + major: 3, + minor: 9, + patch: 18, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("212d413ab6f854f588cf368fdd2aa140bb7c7ee930e3f7ac1002cba1e50e9685") + }, + PythonDownload { + key: "cpython-3.9.18-linux-x86_64-gnu", + major: 3, + minor: 9, + patch: 18, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("e1fa92798fab6f3b44a48f24b8e284660c34738d560681b206f0deb0616465f9") + }, + PythonDownload { + key: "cpython-3.9.18-linux-x86_64-musl", + major: 3, + minor: 9, + patch: 18, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("8bf88ae2100e609902d98ec775468e3a41a834f6528e632d6d971f5f75340336") + }, + PythonDownload { + key: "cpython-3.9.18-macos-x86_64-none", + major: 3, + minor: 9, + patch: 18, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("146537b9b4a1baa672eed94373e149ca1ee339c4df121e8916d8436265e5245e") + }, + PythonDownload { + key: "cpython-3.9.18-windows-x86_64-none", + major: 3, + minor: 9, + patch: 18, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("924ed4f375ef73c73a725ef18ec6a72726456673d5a116f132f60860a25dd674") + }, + PythonDownload { + key: "cpython-3.9.17-linux-aarch64-gnu", + major: 3, + minor: 9, + patch: 17, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("1f6c43d92ba9f4e15149cf5db6ecde11e05eee92c070a085e44f46c559520257") + }, + PythonDownload { + key: "cpython-3.9.17-macos-aarch64-none", + major: 3, + minor: 9, + patch: 17, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("2902e2a0add6d584999fa27896b721a359f7308404e936e80b01b07aa06e8f5e") + }, + PythonDownload { + key: "cpython-3.9.17-linux-powerpc64le-gnu", + major: 3, + minor: 9, + patch: 17, + implementation: ImplementationName::Cpython, + arch: Arch::Powerpc64Le, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("bcb0ec31342df52b4555be309080a9c3224e7ff60a6291e34337ddfddef111cf") + }, + PythonDownload { + key: "cpython-3.9.17-linux-s390x-gnu", + major: 3, + minor: 9, + patch: 17, + implementation: ImplementationName::Cpython, + arch: Arch::S390X, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-s390x-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("bf8c846c1a4e52355d4ae294f4e1da9587d5415467eb6890bdf0f5a4c8cda396") + }, + PythonDownload { + key: "cpython-3.9.17-linux-x86-gnu", + major: 3, + minor: 9, + patch: 17, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-i686-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("1a9b7edc16683410c27bc5b4b1761143bef7831a1ad172e7e3581c152c6837a2") + }, + PythonDownload { + key: "cpython-3.9.17-windows-x86-none", + major: 3, + minor: 9, + patch: 17, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("ffac27bfb8bdf615d0fc6cbbe0becaa65b6ae73feec417919601497fce2be0ab") + }, + PythonDownload { + key: "cpython-3.9.17-linux-x86_64-gnu", + major: 3, + minor: 9, + patch: 17, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("649fff6048f4cb9e64a85eaf8e720eb4c3257e27e7c4ee46f75bfa48c18c6826") + }, + PythonDownload { + key: "cpython-3.9.17-linux-x86_64-musl", + major: 3, + minor: 9, + patch: 17, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("8496473a97e1dd43bf96fc1cf19f02f305608ef6a783e0112274e0ae01df4f2a") + }, + PythonDownload { + key: "cpython-3.9.17-macos-x86_64-none", + major: 3, + minor: 9, + patch: 17, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("ba04f9813b78b61d60a27857949403a1b1dd8ac053e1f1aff72fe2689c238d3c") + }, + PythonDownload { + key: "cpython-3.9.17-windows-x86_64-none", + major: 3, + minor: 9, + patch: 17, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("209983b8227e4755197dfed4f6887e45b6a133f61e7eb913c0a934b0d0c3e00f") + }, + PythonDownload { + key: "cpython-3.9.16-linux-aarch64-gnu", + major: 3, + minor: 9, + patch: 16, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("57ac7ce9d3dd32c1277ee7295daf5ad7b5ecc929e65b31f11b1e7b94cd355ed1") + }, + PythonDownload { + key: "cpython-3.9.16-macos-aarch64-none", + major: 3, + minor: 9, + patch: 16, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("c86ed2bf3ff290af10f96183c53e2b29e954abb520806fbe01d3ef2f9d809a75") + }, + PythonDownload { + key: "cpython-3.9.16-linux-powerpc64le-gnu", + major: 3, + minor: 9, + patch: 16, + implementation: ImplementationName::Cpython, + arch: Arch::Powerpc64Le, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("8b2e7ddc6feb116dfa6829cfc478be90a374dc5ce123a98bc77e86d0e93e917d") + }, + PythonDownload { + key: "cpython-3.9.16-linux-x86-gnu", + major: 3, + minor: 9, + patch: 16, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-i686-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("e2a0226165550492e895369ee1b69a515f82e12cb969656012ee8e1543409661") + }, + PythonDownload { + key: "cpython-3.9.16-windows-x86-none", + major: 3, + minor: 9, + patch: 16, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("d7994b5febb375bb131d028f98f4902ba308913c77095457ccd159b521e20c52") + }, + PythonDownload { + key: "cpython-3.9.16-linux-x86_64-gnu", + major: 3, + minor: 9, + patch: 16, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("cdc1290b9bdb2f74a6c48ab24531919551128e39773365c6f3e17668216275a0") + }, + PythonDownload { + key: "cpython-3.9.16-linux-x86_64-musl", + major: 3, + minor: 9, + patch: 16, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("c397f292021b33531248ad8fede24ef6249cc6172347b2017f92b4a71845b8ed") + }, + PythonDownload { + key: "cpython-3.9.16-macos-x86_64-none", + major: 3, + minor: 9, + patch: 16, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("5809626ca7907c8ea397341f3d5eafb280ed5b19cc5622e57b14d9b4362eba50") + }, + PythonDownload { + key: "cpython-3.9.16-windows-x86_64-none", + major: 3, + minor: 9, + patch: 16, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("199c821505e287c004c3796ba9ac4bd129d7793e1d833e9a7672ed03bdb397d4") + }, + PythonDownload { + key: "cpython-3.9.15-linux-aarch64-gnu", + major: 3, + minor: 9, + patch: 15, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("0da1f081313b088c1381206e698e70fffdffc01e1b2ce284145c24ee5f5b4cbb") + }, + PythonDownload { + key: "cpython-3.9.15-macos-aarch64-none", + major: 3, + minor: 9, + patch: 15, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("1799b97619572ad595cd6d309bbcc57606138a57f4e90af04e04ee31d187e22f") + }, + PythonDownload { + key: "cpython-3.9.15-linux-x86-gnu", + major: 3, + minor: 9, + patch: 15, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-i686-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("cbc6a14835022d89f4ca6042a06c4959d74d4bbb58e70bdbe0fe8d2928934922") + }, + PythonDownload { + key: "cpython-3.9.15-windows-x86-none", + major: 3, + minor: 9, + patch: 15, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("a5ad2a6ace97d458ad7b2857fba519c5c332362442d88e2b23ed818f243b8a78") + }, + PythonDownload { + key: "cpython-3.9.15-linux-x86_64-gnu", + major: 3, + minor: 9, + patch: 15, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("6a08761bb725b8d3a92144f81628febeab8b12326ca264ffe28255fa67c7bf17") + }, + PythonDownload { + key: "cpython-3.9.15-linux-x86_64-musl", + major: 3, + minor: 9, + patch: 15, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("4597f0009cfb52e748a57badab28edf84a263390b777c182b18c36d666a01440") + }, + PythonDownload { + key: "cpython-3.9.15-macos-x86_64-none", + major: 3, + minor: 9, + patch: 15, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("50fd795eac55c4485e2fefbb8e7b365461817733c45becb50a7480a243e6000e") + }, + PythonDownload { + key: "cpython-3.9.15-windows-x86_64-none", + major: 3, + minor: 9, + patch: 15, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("d0f3ce1748a51779eedf155aea617c39426e3f7bfd93b4876cb172576b6e8bda") + }, + PythonDownload { + key: "cpython-3.9.14-linux-aarch64-gnu", + major: 3, + minor: 9, + patch: 14, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("3020c743e4742d6e0e5d27fcb166c694bf1d9565369b2eaee9d68434304aebd2") + }, + PythonDownload { + key: "cpython-3.9.14-macos-aarch64-none", + major: 3, + minor: 9, + patch: 14, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("6b9d2ff724aff88a4d0790c86f2e5d17037736f35a796e71732624191ddd6e38") + }, + PythonDownload { + key: "cpython-3.9.14-linux-x86-gnu", + major: 3, + minor: 9, + patch: 14, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-i686-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("83a11c4f3d1c0ec39119bd0513a8684b59b68c3989cf1e5042d7417d4770c904") + }, + PythonDownload { + key: "cpython-3.9.14-windows-x86-none", + major: 3, + minor: 9, + patch: 14, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("fae990eb312314102408cb0c0453dae670f0eb468f4cbf3e72327ceaa1276b46") + }, + PythonDownload { + key: "cpython-3.9.14-linux-x86_64-gnu", + major: 3, + minor: 9, + patch: 14, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("54529c0a8ffe621f5c9c6bdd22968cac9d3207cbd5dcd9c07bbe61140c49937e") + }, + PythonDownload { + key: "cpython-3.9.14-linux-x86_64-musl", + major: 3, + minor: 9, + patch: 14, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("5638c12d47eb81adf96615cea8a5a61e8414c3ac03a8b570d30ae9998cb6d030") + }, + PythonDownload { + key: "cpython-3.9.14-macos-x86_64-none", + major: 3, + minor: 9, + patch: 14, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("186155e19b63da3248347415f888fbcf982c7587f6f927922ca243ae3f23ed2f") + }, + PythonDownload { + key: "cpython-3.9.14-windows-x86_64-none", + major: 3, + minor: 9, + patch: 14, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("49f27a3a18b4c2d765b0656c6529378a20b3e37fdb0aca9490576ff7a67243a9") + }, + PythonDownload { + key: "cpython-3.9.13-linux-aarch64-gnu", + major: 3, + minor: 9, + patch: 13, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("8c706ebb2c8970da4fbec95b0520b4632309bc6a3e115cf309e38f181b553d14") + }, + PythonDownload { + key: "cpython-3.9.13-macos-aarch64-none", + major: 3, + minor: 9, + patch: 13, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("8612e9328663c0747d1eae36b218d11c2fbc53c39ec7512c7ad6b1b57374a5dc") + }, + PythonDownload { + key: "cpython-3.9.13-linux-x86-gnu", + major: 3, + minor: 9, + patch: 13, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-i686-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("7d33637b48c45acf8805d5460895dca29bf2740fd2cf502fde6c6a00637db6b5") + }, + PythonDownload { + key: "cpython-3.9.13-windows-x86-none", + major: 3, + minor: 9, + patch: 13, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("3860abee418825c6a33f76fe88773fb05eb4bc724d246f1af063106d9ea3f999") + }, + PythonDownload { + key: "cpython-3.9.13-linux-x86_64-gnu", + major: 3, + minor: 9, + patch: 13, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("352d00a4630d0665387bcb158aec3f6c7fc5a4d14d65ac26e1b826e20611222f") + }, + PythonDownload { + key: "cpython-3.9.13-linux-x86_64-musl", + major: 3, + minor: 9, + patch: 13, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("c7e48545a8291fe1be909c4454b5c48df0ee4e69e2b5e13b6144b4199c31f895") + }, + PythonDownload { + key: "cpython-3.9.13-macos-x86_64-none", + major: 3, + minor: 9, + patch: 13, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("16d21a6e62c19c574a4a225961e80966449095a8eb2c4150905e30d4e807cf86") + }, + PythonDownload { + key: "cpython-3.9.13-windows-x86_64-none", + major: 3, + minor: 9, + patch: 13, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("6ef2b164cae483c61da30fb6d245762b8d6d91346d66cb421989d6d1462e5a48") + }, + PythonDownload { + key: "cpython-3.9.12-linux-aarch64-gnu", + major: 3, + minor: 9, + patch: 12, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("202ef64e43570f0843ff5895fd9c1a2c36a96b48d52842fa95842d7d11025b20") + }, + PythonDownload { + key: "cpython-3.9.12-macos-aarch64-none", + major: 3, + minor: 9, + patch: 12, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("b3d09b3c12295e893ee8f2cb60e8af94d8a21fc5c65016282925220f5270b85b") + }, + PythonDownload { + key: "cpython-3.9.12-linux-x86-gnu", + major: 3, + minor: 9, + patch: 12, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-i686-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("e52fdbe61dea847323cd6e81142d16a571dca9c0bcde3bfe5ae75a8d3d1a3bf4") + }, + PythonDownload { + key: "cpython-3.9.12-windows-x86-none", + major: 3, + minor: 9, + patch: 12, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("361b8fa66d6b5d5623fd5e64af29cf220a693ba86d031bf7ce2b61e1ea50f568") + }, + PythonDownload { + key: "cpython-3.9.12-linux-x86_64-gnu", + major: 3, + minor: 9, + patch: 12, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("7290ac14e43749afdb37d3c9690f300f5f0786f19982e8960566ecdc3e42c3eb") + }, + PythonDownload { + key: "cpython-3.9.12-linux-x86_64-musl", + major: 3, + minor: 9, + patch: 12, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("eb122ab2bf0b2d71926984bc7cf5fef65b415abfe01a0974ed6c1a2502fac764") + }, + PythonDownload { + key: "cpython-3.9.12-macos-x86_64-none", + major: 3, + minor: 9, + patch: 12, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("825970ae30ae7a30a5b039aa25f1b965e2d1fe046e196e61fa2a3af8fef8c5d9") + }, + PythonDownload { + key: "cpython-3.9.12-windows-x86_64-none", + major: 3, + minor: 9, + patch: 12, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("c49f8b07e9c4dcfd7a5b55c131e882a4ebdf9f37fef1c7820c3ce9eb23bab8ab") + }, + PythonDownload { + key: "cpython-3.9.11-linux-aarch64-gnu", + major: 3, + minor: 9, + patch: 11, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("e1f3ae07a28a687f8602fb4d29a1b72cc5e113c61dc6769d0d85081ab3e09c71") + }, + PythonDownload { + key: "cpython-3.9.11-macos-aarch64-none", + major: 3, + minor: 9, + patch: 11, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("6d9f20607a20e2cc5ad1428f7366832dc68403fc15f2e4f195817187e7b6dbbf") + }, + PythonDownload { + key: "cpython-3.9.11-linux-x86-gnu", + major: 3, + minor: 9, + patch: 11, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-i686-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("0be0a5f524c68d521be2417565ca43f3125b1845f996d6d62266aa431e673f93") + }, + PythonDownload { + key: "cpython-3.9.11-windows-x86-none", + major: 3, + minor: 9, + patch: 11, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("f06338422e7e3ad25d0cd61864bdb36d565d46440dd363cbb98821d388ed377a") + }, + PythonDownload { + key: "cpython-3.9.11-linux-x86_64-gnu", + major: 3, + minor: 9, + patch: 11, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("020bcbfff16dc5ce35a898763be3d847c97df2e14dabf483a8ec88b0455ff971") + }, + PythonDownload { + key: "cpython-3.9.11-linux-x86_64-musl", + major: 3, + minor: 9, + patch: 11, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("d83eb5c897120e32287cb6fe5c24dd2dcae00878b3f9d7002590d468bd5de0f1") + }, + PythonDownload { + key: "cpython-3.9.11-macos-x86_64-none", + major: 3, + minor: 9, + patch: 11, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("35e649618e7e602778e72b91c9c50c97d01a0c3509d16225a1f41dd0fd6575f0") + }, + PythonDownload { + key: "cpython-3.9.11-windows-x86_64-none", + major: 3, + minor: 9, + patch: 11, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("1fe3c519d43737dc7743aec43f72735e1429c79e06e3901b21bad67b642f1a10") + }, + PythonDownload { + key: "cpython-3.9.10-linux-aarch64-gnu", + major: 3, + minor: 9, + patch: 10, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("8bf7ac2cd5825b8fde0a6e535266a57c97e82fd5a97877940920b403ca5e53d7") + }, + PythonDownload { + key: "cpython-3.9.10-macos-aarch64-none", + major: 3, + minor: 9, + patch: 10, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("ba1b63600ed8d9f3b8d739657bd8e7f5ca167de29a1a58d04b2cd9940b289464") + }, + PythonDownload { + key: "cpython-3.9.10-linux-x86-gnu", + major: 3, + minor: 9, + patch: 10, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-i686-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("3e3bf4d3e71a2131e6c064d1e5019f58cb9c58fdceae4b76b26ac978a6d49aad") + }, + PythonDownload { + key: "cpython-3.9.10-windows-x86-none", + major: 3, + minor: 9, + patch: 10, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("7f3ca15f89775f76a32e6ea9b2c9778ebf0cde753c5973d4493959e75dd92488") + }, + PythonDownload { + key: "cpython-3.9.10-linux-x86_64-gnu", + major: 3, + minor: 9, + patch: 10, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("d453abf741c3196ffc8470f3ea6404a3e2b55b2674a501bb79162f06122423e5") + }, + PythonDownload { + key: "cpython-3.9.10-linux-x86_64-musl", + major: 3, + minor: 9, + patch: 10, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("2744b817f249c0563b844cddd5aba4cc2fd449489b8bd59980d7a31de3a4ece1") + }, + PythonDownload { + key: "cpython-3.9.10-macos-x86_64-none", + major: 3, + minor: 9, + patch: 10, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("ef2f090ff920708b4b9aa5d6adf0dc930c09a4bf638d71e6883091f9e629193d") + }, + PythonDownload { + key: "cpython-3.9.10-windows-x86_64-none", + major: 3, + minor: 9, + patch: 10, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("56b2738599131d03b39b914ea0597862fd9096e5e64816bf19466bf026e74f0c") + }, + PythonDownload { + key: "cpython-3.9.7-linux-aarch64-gnu", + major: 3, + minor: 9, + patch: 7, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.9.7-aarch64-unknown-linux-gnu-debug-20211017T1616.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.7-macos-aarch64-none", + major: 3, + minor: 9, + patch: 7, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.9.7-aarch64-apple-darwin-pgo%2Blto-20211017T1616.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.7-linux-x86-gnu", + major: 3, + minor: 9, + patch: 7, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.9.7-i686-unknown-linux-gnu-debug-20211017T1616.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.7-windows-x86-none", + major: 3, + minor: 9, + patch: 7, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.9.7-i686-pc-windows-msvc-shared-pgo-20211017T1616.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.7-linux-x86_64-gnu", + major: 3, + minor: 9, + patch: 7, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.9.7-x86_64-unknown-linux-gnu-debug-20211017T1616.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.7-linux-x86_64-musl", + major: 3, + minor: 9, + patch: 7, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.9.7-x86_64-unknown-linux-musl-lto-20211017T1616.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.7-macos-x86_64-none", + major: 3, + minor: 9, + patch: 7, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.9.7-x86_64-apple-darwin-pgo%2Blto-20211017T1616.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.7-windows-x86_64-none", + major: 3, + minor: 9, + patch: 7, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.9.7-x86_64-pc-windows-msvc-shared-pgo-20211017T1616.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.6-linux-aarch64-gnu", + major: 3, + minor: 9, + patch: 6, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.9.6-aarch64-unknown-linux-gnu-debug-20210724T1424.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.6-macos-aarch64-none", + major: 3, + minor: 9, + patch: 6, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.9.6-aarch64-apple-darwin-pgo%2Blto-20210724T1424.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.6-linux-x86-gnu", + major: 3, + minor: 9, + patch: 6, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.9.6-i686-unknown-linux-gnu-debug-20210724T1424.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.6-windows-x86-none", + major: 3, + minor: 9, + patch: 6, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.9.6-i686-pc-windows-msvc-shared-pgo-20210724T1424.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.6-linux-x86_64-gnu", + major: 3, + minor: 9, + patch: 6, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.9.6-x86_64-unknown-linux-gnu-debug-20210724T1424.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.6-linux-x86_64-musl", + major: 3, + minor: 9, + patch: 6, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.9.6-x86_64-unknown-linux-musl-lto-20210724T1424.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.6-macos-x86_64-none", + major: 3, + minor: 9, + patch: 6, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.9.6-x86_64-apple-darwin-pgo%2Blto-20210724T1424.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.6-windows-x86_64-none", + major: 3, + minor: 9, + patch: 6, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.9.6-x86_64-pc-windows-msvc-shared-pgo-20210724T1424.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.5-macos-aarch64-none", + major: 3, + minor: 9, + patch: 5, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.9.5-aarch64-apple-darwin-pgo%2Blto-20210506T0943.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.5-linux-x86-gnu", + major: 3, + minor: 9, + patch: 5, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.9.5-i686-unknown-linux-gnu-debug-20210506T0943.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.5-windows-x86-none", + major: 3, + minor: 9, + patch: 5, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.9.5-i686-pc-windows-msvc-shared-pgo-20210506T0943.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.5-linux-x86_64-gnu", + major: 3, + minor: 9, + patch: 5, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.9.5-x86_64-unknown-linux-gnu-debug-20210506T0943.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.5-linux-x86_64-musl", + major: 3, + minor: 9, + patch: 5, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.9.5-x86_64-unknown-linux-musl-lto-20210506T0943.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.5-macos-x86_64-none", + major: 3, + minor: 9, + patch: 5, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.9.5-x86_64-apple-darwin-pgo%2Blto-20210506T0943.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.5-windows-x86_64-none", + major: 3, + minor: 9, + patch: 5, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.9.5-x86_64-pc-windows-msvc-shared-pgo-20210506T0943.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.4-macos-aarch64-none", + major: 3, + minor: 9, + patch: 4, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.9.4-aarch64-apple-darwin-pgo%2Blto-20210414T1515.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.4-linux-x86-gnu", + major: 3, + minor: 9, + patch: 4, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.9.4-i686-unknown-linux-gnu-debug-20210414T1515.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.4-windows-x86-none", + major: 3, + minor: 9, + patch: 4, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.9.4-i686-pc-windows-msvc-shared-pgo-20210414T1515.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.4-linux-x86_64-gnu", + major: 3, + minor: 9, + patch: 4, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.9.4-x86_64-unknown-linux-gnu-debug-20210414T1515.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.4-linux-x86_64-musl", + major: 3, + minor: 9, + patch: 4, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.9.4-x86_64-unknown-linux-musl-lto-20210414T1515.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.4-macos-x86_64-none", + major: 3, + minor: 9, + patch: 4, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.9.4-x86_64-apple-darwin-pgo%2Blto-20210414T1515.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.4-windows-x86_64-none", + major: 3, + minor: 9, + patch: 4, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.9.4-x86_64-pc-windows-msvc-shared-pgo-20210414T1515.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.3-macos-aarch64-none", + major: 3, + minor: 9, + patch: 3, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210414/cpython-3.9.3-aarch64-apple-darwin-pgo%2Blto-20210413T2055.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.3-windows-x86-none", + major: 3, + minor: 9, + patch: 3, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210414/cpython-3.9.3-i686-pc-windows-msvc-shared-pgo-20210413T2055.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.3-linux-x86_64-gnu", + major: 3, + minor: 9, + patch: 3, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210414/cpython-3.9.3-x86_64-unknown-linux-gnu-debug-20210413T2055.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.3-linux-x86_64-musl", + major: 3, + minor: 9, + patch: 3, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210414/cpython-3.9.3-x86_64-unknown-linux-musl-lto-20210413T2055.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.3-macos-x86_64-none", + major: 3, + minor: 9, + patch: 3, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210414/cpython-3.9.3-x86_64-apple-darwin-pgo%2Blto-20210413T2055.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.3-windows-x86_64-none", + major: 3, + minor: 9, + patch: 3, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210414/cpython-3.9.3-x86_64-pc-windows-msvc-shared-pgo-20210413T2055.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.2-macos-aarch64-none", + major: 3, + minor: 9, + patch: 2, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.9.2-aarch64-apple-darwin-pgo%2Blto-20210327T1202.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.2-linux-x86-gnu", + major: 3, + minor: 9, + patch: 2, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.9.2-i686-unknown-linux-gnu-debug-20210327T1202.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.2-windows-x86-none", + major: 3, + minor: 9, + patch: 2, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.9.2-i686-pc-windows-msvc-shared-pgo-20210327T1202.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.2-linux-x86_64-gnu", + major: 3, + minor: 9, + patch: 2, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.9.2-x86_64-unknown-linux-gnu-debug-20210327T1202.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.2-linux-x86_64-musl", + major: 3, + minor: 9, + patch: 2, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.9.2-x86_64-unknown-linux-musl-lto-20210327T1202.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.2-macos-x86_64-none", + major: 3, + minor: 9, + patch: 2, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.9.2-x86_64-apple-darwin-pgo%2Blto-20210327T1202.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.2-windows-x86_64-none", + major: 3, + minor: 9, + patch: 2, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.9.2-x86_64-pc-windows-msvc-shared-pgo-20210327T1202.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.1-windows-x86-none", + major: 3, + minor: 9, + patch: 1, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210103/cpython-3.9.1-i686-pc-windows-msvc-shared-pgo-20210103T1125.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.1-linux-x86_64-gnu", + major: 3, + minor: 9, + patch: 1, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210103/cpython-3.9.1-x86_64-unknown-linux-gnu-debug-20210103T1125.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.1-linux-x86_64-musl", + major: 3, + minor: 9, + patch: 1, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210103/cpython-3.9.1-x86_64-unknown-linux-musl-debug-20210103T1125.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.1-macos-x86_64-none", + major: 3, + minor: 9, + patch: 1, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210103/cpython-3.9.1-x86_64-apple-darwin-pgo-20210103T1125.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.1-windows-x86_64-none", + major: 3, + minor: 9, + patch: 1, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210103/cpython-3.9.1-x86_64-pc-windows-msvc-shared-pgo-20210103T1125.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.0-windows-x86-none", + major: 3, + minor: 9, + patch: 0, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20201020/cpython-3.9.0-i686-pc-windows-msvc-shared-pgo-20201021T0245.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.0-linux-x86_64-gnu", + major: 3, + minor: 9, + patch: 0, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20201020/cpython-3.9.0-x86_64-unknown-linux-gnu-debug-20201020T0627.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.0-linux-x86_64-musl", + major: 3, + minor: 9, + patch: 0, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20201020/cpython-3.9.0-x86_64-unknown-linux-musl-debug-20201020T0627.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.0-macos-x86_64-none", + major: 3, + minor: 9, + patch: 0, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20201020/cpython-3.9.0-x86_64-apple-darwin-pgo-20201020T0626.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.9.0-windows-x86_64-none", + major: 3, + minor: 9, + patch: 0, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20201020/cpython-3.9.0-x86_64-pc-windows-msvc-shared-pgo-20201021T0245.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.18-linux-aarch64-gnu", + major: 3, + minor: 8, + patch: 18, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("6d71175a090950c2063680f250b8799ab39eb139aa1721c853d8950aadd1d4e2") + }, + PythonDownload { + key: "cpython-3.8.18-macos-aarch64-none", + major: 3, + minor: 8, + patch: 18, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("c732c068cddcd6a008c1d6d8e35802f5bdc7323bd2eb64e77210d3d5fe4740c2") + }, + PythonDownload { + key: "cpython-3.8.18-windows-x86-none", + major: 3, + minor: 8, + patch: 18, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("9f94c7b54b97116cd308e73cda0b7a7b7fff4515932c5cbba18eeae9ec798351") + }, + PythonDownload { + key: "cpython-3.8.18-linux-x86_64-gnu", + major: 3, + minor: 8, + patch: 18, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("189ae3b8249c57217e3253f9fc89857e088763cf2107a3f22ab2ac2398f41a65") + }, + PythonDownload { + key: "cpython-3.8.18-linux-x86_64-musl", + major: 3, + minor: 8, + patch: 18, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("fa1bf64cf52d830e7b4bba486c447ee955af644d167df7c42afd169c5dc71d6a") + }, + PythonDownload { + key: "cpython-3.8.18-macos-x86_64-none", + major: 3, + minor: 8, + patch: 18, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("4d4b65dd821ce13dcf6dfea3ad5c2d4c3d3a8c2b7dd49fc35c1d79f66238e89b") + }, + PythonDownload { + key: "cpython-3.8.18-windows-x86_64-none", + major: 3, + minor: 8, + patch: 18, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("c63abd9365a13196eb9f65db864f95b85c1f90b770d218c1acd104e6b48a99d3") + }, + PythonDownload { + key: "cpython-3.8.17-linux-aarch64-gnu", + major: 3, + minor: 8, + patch: 17, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("eaee5a0b79cc28943e19df54f314634795aee43a6670ce99c0306893a18fa784") + }, + PythonDownload { + key: "cpython-3.8.17-macos-aarch64-none", + major: 3, + minor: 8, + patch: 17, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("d08a542bed35fc74ac6e8f6884c8aa29a77ff2f4ed04a06dcf91578dea622f9a") + }, + PythonDownload { + key: "cpython-3.8.17-linux-x86-gnu", + major: 3, + minor: 8, + patch: 17, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-i686-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("61ac08680c022f180a32dc82d84548aeb92c7194a489e3b3c532dc48f999d757") + }, + PythonDownload { + key: "cpython-3.8.17-windows-x86-none", + major: 3, + minor: 8, + patch: 17, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("0931d8ca0e060c6ac1dfcf6bb9b6dea0ac3a9d95daf7906a88128045f4464bf8") + }, + PythonDownload { + key: "cpython-3.8.17-linux-x86_64-gnu", + major: 3, + minor: 8, + patch: 17, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("f499750ab0019f36ccb4d964e222051d0d49a1d1e8dbada98abae738cf48c9dc") + }, + PythonDownload { + key: "cpython-3.8.17-linux-x86_64-musl", + major: 3, + minor: 8, + patch: 17, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("a316ba0b1f425b04c8dfd7a8a18a05d72ae5852732d401b16d7439bdf25caec3") + }, + PythonDownload { + key: "cpython-3.8.17-macos-x86_64-none", + major: 3, + minor: 8, + patch: 17, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("2c4925f5cf37d498e0d8cfe7b10591cc5f0cd80d2582f566b12006e6f96958b1") + }, + PythonDownload { + key: "cpython-3.8.17-windows-x86_64-none", + major: 3, + minor: 8, + patch: 17, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("68c7d03de5283c4812f2706c797b2139999a28cec647bc662d1459a922059318") + }, + PythonDownload { + key: "cpython-3.8.16-linux-aarch64-gnu", + major: 3, + minor: 8, + patch: 16, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.8.16%2B20230726-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("423d43d93e2fe33b41ad66d35426f16541f09fee9d7272ae5decf5474ebbc225") + }, + PythonDownload { + key: "cpython-3.8.16-macos-aarch64-none", + major: 3, + minor: 8, + patch: 16, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.8.16%2B20230726-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("bfc91d0a1d6d6dfaa5a31c925aa6adae82bd1ae5eb17813a9f0a50bf9d3e6305") + }, + PythonDownload { + key: "cpython-3.8.16-linux-x86-gnu", + major: 3, + minor: 8, + patch: 16, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.8.16%2B20230726-i686-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("9aa3e559130a47c33ee2b67f6ca69e2f10d8f70c1fd1e2871763b892372a6d9e") + }, + PythonDownload { + key: "cpython-3.8.16-windows-x86-none", + major: 3, + minor: 8, + patch: 16, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.8.16%2B20230726-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("5de953621402c11cc7db65ba15d45779e838d7ce78e7aa8d43c7d78fff177f13") + }, + PythonDownload { + key: "cpython-3.8.16-linux-x86_64-gnu", + major: 3, + minor: 8, + patch: 16, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.8.16%2B20230726-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("f12f5cb38f796ca48dc73262c05506a6f21f59d24e709ea0390b18bf71c2e1f9") + }, + PythonDownload { + key: "cpython-3.8.16-linux-x86_64-musl", + major: 3, + minor: 8, + patch: 16, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.8.16%2B20230726-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("f7d46196b44d12a26209ac74061200aac478b96c253eea93a0b9734efa642779") + }, + PythonDownload { + key: "cpython-3.8.16-macos-x86_64-none", + major: 3, + minor: 8, + patch: 16, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.8.16%2B20230726-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("21c0f4a0fa6ee518b9f2f1901c9667e3baf45d9f84235408b7ca50499d19f56d") + }, + PythonDownload { + key: "cpython-3.8.16-windows-x86_64-none", + major: 3, + minor: 8, + patch: 16, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.8.16%2B20230726-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("6316713c2dcb30127b38ced249fa9608830a33459580b71275a935aaa8cd5d5f") + }, + PythonDownload { + key: "cpython-3.8.15-linux-aarch64-gnu", + major: 3, + minor: 8, + patch: 15, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.8.15%2B20221106-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("2e80025eda686c14a9a0618ced40043c1d577a754b904fd7a382cd41abf9ca00") + }, + PythonDownload { + key: "cpython-3.8.15-macos-aarch64-none", + major: 3, + minor: 8, + patch: 15, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.8.15%2B20221106-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("fc0f944e6f01ed649f79c873af1c317db61d2136b82081b4d7cbb7755f878035") + }, + PythonDownload { + key: "cpython-3.8.15-linux-x86-gnu", + major: 3, + minor: 8, + patch: 15, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.8.15%2B20221106-i686-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("b8436415ea9bd9970fb766f791a14b0e14ce6351fc4604eb158f1425e8bb4a33") + }, + PythonDownload { + key: "cpython-3.8.15-windows-x86-none", + major: 3, + minor: 8, + patch: 15, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.8.15%2B20221106-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("98bb2315c3567316c30b060d613c8d6067b368b64f08ef8fe6196341637c1d78") + }, + PythonDownload { + key: "cpython-3.8.15-linux-x86_64-gnu", + major: 3, + minor: 8, + patch: 15, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.8.15%2B20221106-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("c3c8c23e34bddb4a2b90333ff17041f344401775d505700f1ceddb3ad9d589e0") + }, + PythonDownload { + key: "cpython-3.8.15-linux-x86_64-musl", + major: 3, + minor: 8, + patch: 15, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.8.15%2B20221106-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("231b35d3c2cff0372d17cea7ff5168c0684a920b94a912ffc965c2518cacb694") + }, + PythonDownload { + key: "cpython-3.8.15-macos-x86_64-none", + major: 3, + minor: 8, + patch: 15, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.8.15%2B20221106-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("e4fd2fa2255295fbdcfadb8b48014fa80810305eccb246d355880aabb45cbe93") + }, + PythonDownload { + key: "cpython-3.8.15-windows-x86_64-none", + major: 3, + minor: 8, + patch: 15, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.8.15%2B20221106-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("59beac5610e6da0848ebaccd72f91f6aaaeed65ef59606d006af909e9e79beba") + }, + PythonDownload { + key: "cpython-3.8.14-linux-aarch64-gnu", + major: 3, + minor: 8, + patch: 14, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.8.14%2B20221002-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("a14d8b5cbd8e1ca45cbcb49f4bf0b0440dc86eb95b7c3da3c463a704a3b4593c") + }, + PythonDownload { + key: "cpython-3.8.14-macos-aarch64-none", + major: 3, + minor: 8, + patch: 14, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.8.14%2B20221002-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("d17a3fcc161345efa2ec0b4ab9c9ed6c139d29128f2e34bb636338a484aa7b72") + }, + PythonDownload { + key: "cpython-3.8.14-linux-x86-gnu", + major: 3, + minor: 8, + patch: 14, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.8.14%2B20221002-i686-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("631bb90fe8f2965d03400b268de90fe155ce51961296360d6578b7151aa9ef4c") + }, + PythonDownload { + key: "cpython-3.8.14-windows-x86-none", + major: 3, + minor: 8, + patch: 14, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.8.14%2B20221002-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("e43f7a5044eac91e95df59fd08bf96f13245898876fc2afd90a081cfcd847e35") + }, + PythonDownload { + key: "cpython-3.8.14-linux-x86_64-gnu", + major: 3, + minor: 8, + patch: 14, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.8.14%2B20221002-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("bd1e8e09edccaab82fbd75b457205a076847d62e3354c3d9b5abe985181047fc") + }, + PythonDownload { + key: "cpython-3.8.14-linux-x86_64-musl", + major: 3, + minor: 8, + patch: 14, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.8.14%2B20221002-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("c6da442aaea160179a9379b297ccb3ba09b825fc27d84577fc28e62911451e7d") + }, + PythonDownload { + key: "cpython-3.8.14-macos-x86_64-none", + major: 3, + minor: 8, + patch: 14, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.8.14%2B20221002-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("62edfea77b42e87ca2d85c482319211cd2dd68d55ba85c99f1834f7b64a60133") + }, + PythonDownload { + key: "cpython-3.8.14-windows-x86_64-none", + major: 3, + minor: 8, + patch: 14, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.8.14%2B20221002-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("6986b3e6edf7b37f96ea940b7ccba7b767ed3ea9b3faec2a2a60e5b2c4443314") + }, + PythonDownload { + key: "cpython-3.8.13-linux-aarch64-gnu", + major: 3, + minor: 8, + patch: 13, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.8.13%2B20220802-aarch64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("3a927205db4686c182b5e8f3fc7fd7d82ec8f61c70d5b2bfddd9673c7ddc07ba") + }, + PythonDownload { + key: "cpython-3.8.13-macos-aarch64-none", + major: 3, + minor: 8, + patch: 13, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.8.13%2B20220802-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("a204e5f9e1566bdc170b163300a29fc9580d5c65cd6e896caf6500cd64471373") + }, + PythonDownload { + key: "cpython-3.8.13-linux-x86-gnu", + major: 3, + minor: 8, + patch: 13, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.8.13%2B20220802-i686-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("6daf0405beae6d127a2dcae61d51a719236b861b4cabc220727e48547fd6f045") + }, + PythonDownload { + key: "cpython-3.8.13-windows-x86-none", + major: 3, + minor: 8, + patch: 13, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.8.13%2B20220802-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("5630739d1c6fcfbf90311d236c5e46314fc4b439364429bee12d0ffc95e134fb") + }, + PythonDownload { + key: "cpython-3.8.13-linux-x86_64-gnu", + major: 3, + minor: 8, + patch: 13, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.8.13%2B20220802-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("891b5d7b0e936b98a62f65bc0b28fff61ca9002125a2fc1ebb9c72f6b0056712") + }, + PythonDownload { + key: "cpython-3.8.13-linux-x86_64-musl", + major: 3, + minor: 8, + patch: 13, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.8.13%2B20220802-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("410f3223021d1b439cf8e4da699f868adada2066e354d88a00b5f365dc66c4bf") + }, + PythonDownload { + key: "cpython-3.8.13-macos-x86_64-none", + major: 3, + minor: 8, + patch: 13, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.8.13%2B20220802-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("f706a62de8582bf84b8b693c993314cd786f3e78639892cfd9a7283a526696f9") + }, + PythonDownload { + key: "cpython-3.8.13-windows-x86_64-none", + major: 3, + minor: 8, + patch: 13, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.8.13%2B20220802-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("c36b703b8b806a047ba71e5e85734ac78d204d3a2b7ebc2efcdc7d4af6f6c263") + }, + PythonDownload { + key: "cpython-3.8.12-macos-aarch64-none", + major: 3, + minor: 8, + patch: 12, + implementation: ImplementationName::Cpython, + arch: Arch::Aarch64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.8.12%2B20220227-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("386f667f8d49b6c34aee1910cdc0b5b41883f9406f98e7d59a3753990b1cdbac") + }, + PythonDownload { + key: "cpython-3.8.12-linux-x86-gnu", + major: 3, + minor: 8, + patch: 12, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.8.12%2B20220227-i686-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("7cfac9a57e262be3e889036d7fc570293e6d3d74411ee23e1fa9aa470d387e6a") + }, + PythonDownload { + key: "cpython-3.8.12-windows-x86-none", + major: 3, + minor: 8, + patch: 12, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.8.12%2B20220227-i686-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("3e2e6c7de78b1924aad37904fed7bfbac6efa2bef05348e9be92180b2f2b1ae1") + }, + PythonDownload { + key: "cpython-3.8.12-linux-x86_64-gnu", + major: 3, + minor: 8, + patch: 12, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.8.12%2B20220227-x86_64-unknown-linux-gnu-debug-full.tar.zst", + sha256: Some("9ad20c520c291d08087e9afb4390f389d2b66c7fc97f23fffc1313ebafc5fee0") + }, + PythonDownload { + key: "cpython-3.8.12-linux-x86_64-musl", + major: 3, + minor: 8, + patch: 12, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.8.12%2B20220227-x86_64-unknown-linux-musl-lto-full.tar.zst", + sha256: Some("3d958e3f984637d8ca4a90a2e068737b268f87fc615121a6f1808cd46ccacc48") + }, + PythonDownload { + key: "cpython-3.8.12-macos-x86_64-none", + major: 3, + minor: 8, + patch: 12, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.8.12%2B20220227-x86_64-apple-darwin-pgo%2Blto-full.tar.zst", + sha256: Some("cf614d96e2001d526061b3ce0569c79057fd0074ace472ff4f5f601262e08cdb") + }, + PythonDownload { + key: "cpython-3.8.12-windows-x86_64-none", + major: 3, + minor: 8, + patch: 12, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.8.12%2B20220227-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + sha256: Some("33f278416ba8074f2ca6d7f8c17b311b60537c9e6431fd47948784c2a78ea227") + }, + PythonDownload { + key: "cpython-3.8.11-linux-x86-gnu", + major: 3, + minor: 8, + patch: 11, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.8.11-i686-unknown-linux-gnu-debug-20210724T1424.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.11-windows-x86-none", + major: 3, + minor: 8, + patch: 11, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.8.11-i686-pc-windows-msvc-shared-pgo-20210724T1424.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.11-linux-x86_64-gnu", + major: 3, + minor: 8, + patch: 11, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.8.11-x86_64-unknown-linux-gnu-debug-20210724T1424.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.11-linux-x86_64-musl", + major: 3, + minor: 8, + patch: 11, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.8.11-x86_64-unknown-linux-musl-lto-20210724T1424.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.11-macos-x86_64-none", + major: 3, + minor: 8, + patch: 11, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.8.11-x86_64-apple-darwin-pgo%2Blto-20210724T1424.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.11-windows-x86_64-none", + major: 3, + minor: 8, + patch: 11, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.8.11-x86_64-pc-windows-msvc-shared-pgo-20210724T1424.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.10-linux-x86-gnu", + major: 3, + minor: 8, + patch: 10, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.8.10-i686-unknown-linux-gnu-debug-20210506T0943.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.10-windows-x86-none", + major: 3, + minor: 8, + patch: 10, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.8.10-i686-pc-windows-msvc-shared-pgo-20210506T0943.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.10-linux-x86_64-gnu", + major: 3, + minor: 8, + patch: 10, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.8.10-x86_64-unknown-linux-gnu-debug-20210506T0943.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.10-linux-x86_64-musl", + major: 3, + minor: 8, + patch: 10, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.8.10-x86_64-unknown-linux-musl-lto-20210506T0943.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.10-macos-x86_64-none", + major: 3, + minor: 8, + patch: 10, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.8.10-x86_64-apple-darwin-pgo%2Blto-20210506T0943.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.10-windows-x86_64-none", + major: 3, + minor: 8, + patch: 10, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.8.10-x86_64-pc-windows-msvc-shared-pgo-20210506T0943.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.9-linux-x86-gnu", + major: 3, + minor: 8, + patch: 9, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.8.9-i686-unknown-linux-gnu-debug-20210414T1515.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.9-windows-x86-none", + major: 3, + minor: 8, + patch: 9, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.8.9-i686-pc-windows-msvc-shared-pgo-20210414T1515.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.9-linux-x86_64-gnu", + major: 3, + minor: 8, + patch: 9, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.8.9-x86_64-unknown-linux-gnu-debug-20210414T1515.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.9-linux-x86_64-musl", + major: 3, + minor: 8, + patch: 9, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.8.9-x86_64-unknown-linux-musl-lto-20210414T1515.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.9-macos-x86_64-none", + major: 3, + minor: 8, + patch: 9, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.8.9-x86_64-apple-darwin-pgo%2Blto-20210414T1515.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.9-windows-x86_64-none", + major: 3, + minor: 8, + patch: 9, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.8.9-x86_64-pc-windows-msvc-shared-pgo-20210414T1515.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.8-linux-x86-gnu", + major: 3, + minor: 8, + patch: 8, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.8.8-i686-unknown-linux-gnu-debug-20210327T1202.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.8-windows-x86-none", + major: 3, + minor: 8, + patch: 8, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.8.8-i686-pc-windows-msvc-shared-pgo-20210327T1202.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.8-linux-x86_64-gnu", + major: 3, + minor: 8, + patch: 8, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.8.8-x86_64-unknown-linux-gnu-debug-20210327T1202.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.8-linux-x86_64-musl", + major: 3, + minor: 8, + patch: 8, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.8.8-x86_64-unknown-linux-musl-lto-20210327T1202.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.8-macos-x86_64-none", + major: 3, + minor: 8, + patch: 8, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.8.8-x86_64-apple-darwin-pgo%2Blto-20210327T1202.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.8-windows-x86_64-none", + major: 3, + minor: 8, + patch: 8, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.8.8-x86_64-pc-windows-msvc-shared-pgo-20210327T1202.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.7-windows-x86-none", + major: 3, + minor: 8, + patch: 7, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210103/cpython-3.8.7-i686-pc-windows-msvc-shared-pgo-20210103T1125.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.7-linux-x86_64-gnu", + major: 3, + minor: 8, + patch: 7, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210103/cpython-3.8.7-x86_64-unknown-linux-gnu-debug-20210103T1125.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.7-linux-x86_64-musl", + major: 3, + minor: 8, + patch: 7, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210103/cpython-3.8.7-x86_64-unknown-linux-musl-debug-20210103T1125.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.7-macos-x86_64-none", + major: 3, + minor: 8, + patch: 7, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210103/cpython-3.8.7-x86_64-apple-darwin-pgo-20210103T1125.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.7-windows-x86_64-none", + major: 3, + minor: 8, + patch: 7, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210103/cpython-3.8.7-x86_64-pc-windows-msvc-shared-pgo-20210103T1125.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.6-windows-x86-none", + major: 3, + minor: 8, + patch: 6, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20201020/cpython-3.8.6-i686-pc-windows-msvc-shared-pgo-20201021T0233.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.6-linux-x86_64-gnu", + major: 3, + minor: 8, + patch: 6, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20201020/cpython-3.8.6-x86_64-unknown-linux-gnu-debug-20201020T0627.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.6-linux-x86_64-musl", + major: 3, + minor: 8, + patch: 6, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20201020/cpython-3.8.6-x86_64-unknown-linux-musl-debug-20201020T0627.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.6-macos-x86_64-none", + major: 3, + minor: 8, + patch: 6, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20201020/cpython-3.8.6-x86_64-apple-darwin-pgo-20201020T0626.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.6-windows-x86_64-none", + major: 3, + minor: 8, + patch: 6, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20201020/cpython-3.8.6-x86_64-pc-windows-msvc-shared-pgo-20201021T0232.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.5-windows-x86-none", + major: 3, + minor: 8, + patch: 5, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20200830/cpython-3.8.5-i686-pc-windows-msvc-shared-pgo-20200830T2311.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.5-linux-x86_64-gnu", + major: 3, + minor: 8, + patch: 5, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20200822/cpython-3.8.5-x86_64-unknown-linux-gnu-debug-20200823T0036.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.5-linux-x86_64-musl", + major: 3, + minor: 8, + patch: 5, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20200822/cpython-3.8.5-x86_64-unknown-linux-musl-debug-20200823T0036.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.5-macos-x86_64-none", + major: 3, + minor: 8, + patch: 5, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20200823/cpython-3.8.5-x86_64-apple-darwin-pgo-20200823T2228.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.5-windows-x86_64-none", + major: 3, + minor: 8, + patch: 5, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20200830/cpython-3.8.5-x86_64-pc-windows-msvc-shared-pgo-20200830T2254.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.3-windows-x86-none", + major: 3, + minor: 8, + patch: 3, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20200517/cpython-3.8.3-i686-pc-windows-msvc-shared-pgo-20200518T0154.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.3-linux-x86_64-gnu", + major: 3, + minor: 8, + patch: 3, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20200517/cpython-3.8.3-x86_64-unknown-linux-gnu-debug-20200518T0040.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.3-linux-x86_64-musl", + major: 3, + minor: 8, + patch: 3, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20200517/cpython-3.8.3-x86_64-unknown-linux-musl-debug-20200518T0040.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.3-macos-x86_64-none", + major: 3, + minor: 8, + patch: 3, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20200530/cpython-3.8.3-x86_64-apple-darwin-pgo-20200530T1845.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.3-windows-x86_64-none", + major: 3, + minor: 8, + patch: 3, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20200517/cpython-3.8.3-x86_64-pc-windows-msvc-shared-pgo-20200517T2207.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.2-windows-x86-none", + major: 3, + minor: 8, + patch: 2, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20200418/cpython-3.8.2-i686-pc-windows-msvc-shared-pgo-20200418T2315.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.2-linux-x86_64-gnu", + major: 3, + minor: 8, + patch: 2, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20200418/cpython-3.8.2-x86_64-unknown-linux-gnu-debug-20200418T2305.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.2-macos-x86_64-none", + major: 3, + minor: 8, + patch: 2, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20200418/cpython-3.8.2-x86_64-apple-darwin-pgo-20200418T2238.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.8.2-windows-x86_64-none", + major: 3, + minor: 8, + patch: 2, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20200418/cpython-3.8.2-x86_64-pc-windows-msvc-shared-pgo-20200418T2315.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.7.9-windows-x86-none", + major: 3, + minor: 7, + patch: 9, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20200822/cpython-3.7.9-i686-pc-windows-msvc-shared-pgo-20200823T0159.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.7.9-linux-x86_64-gnu", + major: 3, + minor: 7, + patch: 9, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20200822/cpython-3.7.9-x86_64-unknown-linux-gnu-debug-20200823T0036.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.7.9-linux-x86_64-musl", + major: 3, + minor: 7, + patch: 9, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20200822/cpython-3.7.9-x86_64-unknown-linux-musl-debug-20200823T0036.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.7.9-macos-x86_64-none", + major: 3, + minor: 7, + patch: 9, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20200823/cpython-3.7.9-x86_64-apple-darwin-pgo-20200823T2228.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.7.9-windows-x86_64-none", + major: 3, + minor: 7, + patch: 9, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20200822/cpython-3.7.9-x86_64-pc-windows-msvc-shared-pgo-20200823T0118.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.7.7-windows-x86-none", + major: 3, + minor: 7, + patch: 7, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20200517/cpython-3.7.7-i686-pc-windows-msvc-shared-pgo-20200517T2153.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.7.7-linux-x86_64-gnu", + major: 3, + minor: 7, + patch: 7, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20200517/cpython-3.7.7-x86_64-unknown-linux-gnu-debug-20200518T0040.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.7.7-linux-x86_64-musl", + major: 3, + minor: 7, + patch: 7, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20200517/cpython-3.7.7-x86_64-unknown-linux-musl-debug-20200518T0040.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.7.7-macos-x86_64-none", + major: 3, + minor: 7, + patch: 7, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20200530/cpython-3.7.7-x86_64-apple-darwin-pgo-20200530T1845.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.7.7-windows-x86_64-none", + major: 3, + minor: 7, + patch: 7, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20200517/cpython-3.7.7-x86_64-pc-windows-msvc-shared-pgo-20200517T2128.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.7.6-windows-x86-none", + major: 3, + minor: 7, + patch: 6, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20200216/cpython-3.7.6-windows-x86-shared-pgo-20200217T0110.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.7.6-linux-x86_64-gnu", + major: 3, + minor: 7, + patch: 6, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20200216/cpython-3.7.6-linux64-20200216T2303.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.7.6-linux-x86_64-musl", + major: 3, + minor: 7, + patch: 6, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20200217/cpython-3.7.6-linux64-musl-20200218T0557.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.7.6-macos-x86_64-none", + major: 3, + minor: 7, + patch: 6, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20200216/cpython-3.7.6-macos-20200216T2344.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.7.6-windows-x86_64-none", + major: 3, + minor: 7, + patch: 6, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20200216/cpython-3.7.6-windows-amd64-shared-pgo-20200217T0022.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.7.5-windows-x86-none", + major: 3, + minor: 7, + patch: 5, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20191025/cpython-3.7.5-windows-x86-20191025T0549.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.7.5-linux-x86_64-gnu", + major: 3, + minor: 7, + patch: 5, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20191025/cpython-3.7.5-linux64-20191025T0506.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.7.5-linux-x86_64-musl", + major: 3, + minor: 7, + patch: 5, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20191025/cpython-3.7.5-linux64-musl-20191026T0603.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.7.5-macos-x86_64-none", + major: 3, + minor: 7, + patch: 5, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20191025/cpython-3.7.5-macos-20191026T0535.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.7.5-windows-x86_64-none", + major: 3, + minor: 7, + patch: 5, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20191025/cpython-3.7.5-windows-amd64-20191025T0540.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.7.4-windows-x86-none", + major: 3, + minor: 7, + patch: 4, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20190816/cpython-3.7.4-windows-x86-20190817T0235.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.7.4-linux-x86_64-gnu", + major: 3, + minor: 7, + patch: 4, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20190816/cpython-3.7.4-linux64-20190817T0224.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.7.4-linux-x86_64-musl", + major: 3, + minor: 7, + patch: 4, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20190816/cpython-3.7.4-linux64-musl-20190817T0227.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.7.4-macos-x86_64-none", + major: 3, + minor: 7, + patch: 4, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20190816/cpython-3.7.4-macos-20190817T0220.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.7.4-windows-x86_64-none", + major: 3, + minor: 7, + patch: 4, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20190816/cpython-3.7.4-windows-amd64-20190817T0227.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.7.3-windows-x86-none", + major: 3, + minor: 7, + patch: 3, + implementation: ImplementationName::Cpython, + arch: Arch::X86, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20190617/cpython-3.7.3-windows-x86-20190709T0348.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.7.3-linux-x86_64-gnu", + major: 3, + minor: 7, + patch: 3, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20190617/cpython-3.7.3-linux64-20190618T0324.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.7.3-linux-x86_64-musl", + major: 3, + minor: 7, + patch: 3, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Musl, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20190617/cpython-3.7.3-linux64-musl-20190618T0400.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.7.3-macos-x86_64-none", + major: 3, + minor: 7, + patch: 3, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Macos, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20190617/cpython-3.7.3-macos-20190618T0523.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.7.3-windows-x86_64-none", + major: 3, + minor: 7, + patch: 3, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Windows, + libc: Libc::None, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20190617/cpython-3.7.3-windows-amd64-20190618T0516.tar.zst", + sha256: None + }, + PythonDownload { + key: "cpython-3.7.1-linux-x86_64-gnu", + major: 3, + minor: 7, + patch: 1, + implementation: ImplementationName::Cpython, + arch: Arch::X86_64, + os: Os::Linux, + libc: Libc::Gnu, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20181218/cpython-3.7.1-linux64-20181218T1905.tar.zst", + sha256: None + }, +]; diff --git a/crates/uv-toolchain/src/python_versions.inc.mustache b/crates/uv-toolchain/src/python_versions.inc.mustache new file mode 100644 index 000000000..9c81e82b3 --- /dev/null +++ b/crates/uv-toolchain/src/python_versions.inc.mustache @@ -0,0 +1,26 @@ +// DO NOT EDIT +// +// Generated with `{{generated_with}}` +// From template at `{{generated_from}}` + +pub(crate) const PYTHON_DOWNLOADS: &[PythonDownload] = &[ + {{#versions}} + PythonDownload { + key: "{{key}}", + major: {{value.major}}, + minor: {{value.minor}}, + patch: {{value.patch}}, + implementation: ImplementationName::{{value.name}}, + arch: Arch::{{value.arch}}, + os: Os::{{value.os}}, + libc: Libc::{{value.libc}}, + url: "{{value.url}}", + {{#value.sha256}} + sha256: Some("{{.}}") + {{/value.sha256}} + {{^value.sha256}} + sha256: None + {{/value.sha256}} + }, + {{/versions}} +]; diff --git a/crates/uv-toolchain/template-version-metadata.py b/crates/uv-toolchain/template-version-metadata.py new file mode 100644 index 000000000..f8d70f14d --- /dev/null +++ b/crates/uv-toolchain/template-version-metadata.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3.12 +""" +Generate static Rust code from Python version metadata. + +Generates the `python_versions.rs` file from the `python_versions.rs.mustache` template. + +Usage: + + python template-version-metadata.py +""" + +import sys +import logging +import argparse +import json +import subprocess +from pathlib import Path + +CRATE_ROOT = Path(__file__).parent +WORKSPACE_ROOT = CRATE_ROOT.parent.parent +VERSION_METADATA = CRATE_ROOT / "python-version-metadata.json" +TEMPLATE = CRATE_ROOT / "src" / "python_versions.inc.mustache" +TARGET = TEMPLATE.with_suffix("") + + +try: + import chevron_blue +except ImportError: + print( + "missing requirement `chevron-blue`", + file=sys.stderr, + ) + exit(1) + + +def prepare_value(value: dict) -> dict: + # Convert fields from snake case to camel case for enums + for key in ["arch", "os", "libc", "name"]: + value[key] = value[key].title() + return value + + +def main(): + debug = logging.getLogger().getEffectiveLevel() <= logging.DEBUG + + data = {} + data["generated_with"] = Path(__file__).relative_to(WORKSPACE_ROOT) + data["generated_from"] = TEMPLATE.relative_to(WORKSPACE_ROOT) + data["versions"] = [ + {"key": key, "value": prepare_value(value)} + for key, value in json.loads(VERSION_METADATA.read_text()).items() + ] + + # Render the template + logging.info(f"Rendering `{TEMPLATE.name}`...") + output = chevron_blue.render( + template=TEMPLATE.read_text(), data=data, no_escape=True, warn=debug + ) + + # Update the file + logging.info(f"Updating `{TARGET}`...") + TARGET.write_text(output) + subprocess.check_call( + ["rustfmt", str(TARGET)], + stderr=subprocess.STDOUT, + stdout=sys.stderr if debug else subprocess.DEVNULL, + ) + + logging.info("Done!") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Generates Rust code for Python version metadata.", + ) + parser.add_argument( + "-v", + "--verbose", + action="store_true", + help="Enable debug logging", + ) + parser.add_argument( + "-q", + "--quiet", + action="store_true", + help="Disable logging", + ) + + args = parser.parse_args() + if args.quiet: + log_level = logging.CRITICAL + elif args.verbose: + log_level = logging.DEBUG + else: + log_level = logging.INFO + + logging.basicConfig(level=log_level, format="%(message)s") + + main() diff --git a/crates/uv-types/Cargo.toml b/crates/uv-types/Cargo.toml index e6210fb49..c517c0bf0 100644 --- a/crates/uv-types/Cargo.toml +++ b/crates/uv-types/Cargo.toml @@ -15,16 +15,22 @@ workspace = true [dependencies] distribution-types = { workspace = true } once-map = { workspace = true } +pep440_rs = { workspace = true } pep508_rs = { workspace = true } +pypi-types = { workspace = true } uv-cache = { workspace = true } uv-interpreter = { workspace = true } uv-normalize = { workspace = true } +uv-configuration = { workspace = true } anyhow = { workspace = true } +clap = { workspace = true, features = ["derive"], optional = true } itertools = { workspace = true } rustc-hash = { workspace = true } serde = { workspace = true, optional = true } serde_json = { workspace = true, optional = true } +thiserror = { workspace = true } +url = { workspace = true } [features] default = [] diff --git a/crates/uv-types/src/builds.rs b/crates/uv-types/src/builds.rs new file mode 100644 index 000000000..9cc80f7e2 --- /dev/null +++ b/crates/uv-types/src/builds.rs @@ -0,0 +1,15 @@ +use uv_interpreter::PythonEnvironment; + +/// Whether to enforce build isolation when building source distributions. +#[derive(Debug, Copy, Clone)] +pub enum BuildIsolation<'a> { + Isolated, + Shared(&'a PythonEnvironment), +} + +impl<'a> BuildIsolation<'a> { + /// Returns `true` if build isolation is enforced. + pub fn is_isolated(&self) -> bool { + matches!(self, Self::Isolated) + } +} diff --git a/crates/uv-types/src/hash.rs b/crates/uv-types/src/hash.rs new file mode 100644 index 000000000..23b992b33 --- /dev/null +++ b/crates/uv-types/src/hash.rs @@ -0,0 +1,162 @@ +use std::str::FromStr; + +use rustc_hash::FxHashMap; +use url::Url; + +use distribution_types::{DistributionMetadata, HashPolicy, PackageId}; +use pep508_rs::{MarkerEnvironment, RequirementsTxtRequirement, VersionOrUrl}; +use pypi_types::{HashDigest, HashError}; +use uv_normalize::PackageName; + +#[derive(Debug, Clone)] +pub enum HashStrategy { + /// No hash policy is specified. + None, + /// Hashes should be generated (specifically, a SHA-256 hash), but not validated. + Generate, + /// 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(FxHashMap>), +} + +impl HashStrategy { + /// Return the [`HashPolicy`] for the given distribution. + pub fn get(&self, distribution: &T) -> HashPolicy { + match self { + Self::None => HashPolicy::None, + Self::Generate => HashPolicy::Generate, + Self::Validate(hashes) => HashPolicy::Validate( + hashes + .get(&distribution.package_id()) + .map(Vec::as_slice) + .unwrap_or_default(), + ), + } + } + + /// Return the [`HashPolicy`] for the given registry-based package. + pub fn get_package(&self, name: &PackageName) -> HashPolicy { + match self { + Self::None => HashPolicy::None, + Self::Generate => HashPolicy::Generate, + Self::Validate(hashes) => HashPolicy::Validate( + hashes + .get(&PackageId::from_registry(name.clone())) + .map(Vec::as_slice) + .unwrap_or_default(), + ), + } + } + + /// Return the [`HashPolicy`] for the given direct URL package. + pub fn get_url(&self, url: &Url) -> HashPolicy { + match self { + Self::None => HashPolicy::None, + Self::Generate => HashPolicy::Generate, + Self::Validate(hashes) => HashPolicy::Validate( + hashes + .get(&PackageId::from_url(url)) + .map(Vec::as_slice) + .unwrap_or_default(), + ), + } + } + + /// Returns `true` if the given registry-based package is allowed. + pub fn allows_package(&self, name: &PackageName) -> bool { + match self { + Self::None => true, + Self::Generate => true, + Self::Validate(hashes) => hashes.contains_key(&PackageId::from_registry(name.clone())), + } + } + + /// Returns `true` if the given direct URL package is allowed. + pub fn allows_url(&self, url: &Url) -> bool { + match self { + Self::None => true, + Self::Generate => true, + Self::Validate(hashes) => hashes.contains_key(&PackageId::from_url(url)), + } + } + + /// Generate the required hashes from a set of [`RequirementsTxtRequirement`] entries. + pub fn from_requirements<'a>( + requirements: impl Iterator, + markers: &MarkerEnvironment, + ) -> Result { + let mut hashes = FxHashMap::>::default(); + + // For each requirement, map from name to allowed hashes. We use the last entry for each + // package. + for (requirement, digests) in requirements { + if !requirement.evaluate_markers(markers, &[]) { + continue; + } + + // Every requirement must be either a pinned version or a direct URL. + let id = match &requirement { + RequirementsTxtRequirement::Pep508(requirement) => { + match requirement.version_or_url.as_ref() { + Some(VersionOrUrl::Url(url)) => { + // Direct URLs are always allowed. + PackageId::from_url(url) + } + Some(VersionOrUrl::VersionSpecifier(specifiers)) => { + // Must be a single specifier. + let [specifier] = specifiers.as_ref() else { + return Err(HashStrategyError::UnpinnedRequirement( + requirement.to_string(), + )); + }; + + // Must be pinned to a specific version. + if *specifier.operator() != pep440_rs::Operator::Equal { + return Err(HashStrategyError::UnpinnedRequirement( + requirement.to_string(), + )); + } + + PackageId::from_registry(requirement.name.clone()) + } + None => { + return Err(HashStrategyError::UnpinnedRequirement( + requirement.to_string(), + )) + } + } + } + RequirementsTxtRequirement::Unnamed(requirement) => { + // Direct URLs are always allowed. + PackageId::from_url(&requirement.url) + } + }; + + // Every requirement must include a hash. + if digests.is_empty() { + return Err(HashStrategyError::MissingHashes(requirement.to_string())); + } + + // Parse the hashes. + let digests = digests + .iter() + .map(|digest| HashDigest::from_str(digest)) + .collect::, _>>() + .unwrap(); + + hashes.insert(id, digests); + } + + Ok(Self::Validate(hashes)) + } +} + +#[derive(thiserror::Error, Debug)] +pub enum HashStrategyError { + #[error(transparent)] + Hash(#[from] HashError), + #[error("In `--require-hashes` mode, all requirement must have their versions pinned with `==`, but found: {0}")] + UnpinnedRequirement(String), + #[error("In `--require-hashes` mode, all requirement must have a hash, but none were provided for: {0}")] + MissingHashes(String), +} diff --git a/crates/uv-types/src/lib.rs b/crates/uv-types/src/lib.rs index 40a0edc0a..df0b6c4a8 100644 --- a/crates/uv-types/src/lib.rs +++ b/crates/uv-types/src/lib.rs @@ -1,20 +1,12 @@ //! Fundamental types shared across `uv` crates. -pub use build_options::*; -pub use config_settings::*; -pub use constraints::*; +pub use builds::*; pub use downloads::*; -pub use name_specifiers::*; -pub use overrides::*; -pub use package_options::*; +pub use hash::*; pub use requirements::*; pub use traits::*; -mod build_options; -mod config_settings; -mod constraints; +mod builds; mod downloads; -mod name_specifiers; -mod overrides; -mod package_options; +mod hash; mod requirements; mod traits; diff --git a/crates/uv-types/src/traits.rs b/crates/uv-types/src/traits.rs index 7370d2c5c..bd5498f52 100644 --- a/crates/uv-types/src/traits.rs +++ b/crates/uv-types/src/traits.rs @@ -9,7 +9,8 @@ use pep508_rs::{PackageName, Requirement}; use uv_cache::Cache; use uv_interpreter::{Interpreter, PythonEnvironment}; -use crate::{BuildIsolation, BuildKind, NoBinary, NoBuild, SetupPyStrategy}; +use crate::BuildIsolation; +use uv_configuration::{BuildKind, NoBinary, NoBuild, SetupPyStrategy}; /// Avoids cyclic crate dependencies between resolver, installer and builder. /// @@ -94,13 +95,13 @@ pub trait BuildContext: Sync { /// /// For PEP 517 builds, this calls `get_requires_for_build_wheel`. /// - /// `package_id` is for error reporting only. + /// `version_id` is for error reporting only. /// `dist` is for safety checks and may be null for editable builds. fn setup_build<'a>( &'a self, source: &'a Path, subdirectory: Option<&'a Path>, - package_id: &'a str, + version_id: &'a str, dist: Option<&'a SourceDist>, build_kind: BuildKind, ) -> impl Future> + Send + 'a; diff --git a/crates/uv-version/Cargo.toml b/crates/uv-version/Cargo.toml index 9258fe59b..47b3a0440 100644 --- a/crates/uv-version/Cargo.toml +++ b/crates/uv-version/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-version" -version = "0.1.28" +version = "0.1.31" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml index 1dba92d0a..b34b9757b 100644 --- a/crates/uv/Cargo.toml +++ b/crates/uv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv" -version = "0.1.28" +version = "0.1.31" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } @@ -31,8 +31,10 @@ uv-interpreter = { workspace = true } uv-normalize = { workspace = true } uv-requirements = { workspace = true } uv-resolver = { workspace = true, features = ["clap"] } -uv-types = { workspace = true } +uv-types = { workspace = true, features = ["clap"] } +uv-configuration = { workspace = true, features = ["clap"] } uv-virtualenv = { workspace = true } +uv-toolchain = { workspace = true } uv-warnings = { workspace = true } anstream = { workspace = true } @@ -71,19 +73,21 @@ tikv-jemallocator = { version = "0.5.4" } [dev-dependencies] assert_cmd = { version = "2.0.14" } assert_fs = { version = "1.1.0" } -base64 = { version = "0.21.7" } +base64 = { version = "0.22.0" } byteorder = { version = "1.5.0" } filetime = { version = "0.2.23" } indoc = { version = "2.0.4" } insta = { version = "1.36.1", features = ["filters", "json"] } predicates = { version = "3.0.4" } regex = { version = "1.10.3" } -reqwest = { version = "0.11.23", features = ["blocking"], default-features = false } +reqwest = { workspace = true, features = ["blocking"], default-features = false } [features] -default = ["flate2/zlib-ng", "python", "pypi", "git", "maturin"] +default = ["flate2/zlib-ng", "python", "pypi", "git", "maturin", "python-patch"] # Introduces a dependency on a local Python installation. python = [] +# Introduces a dependency on a local Python installation with specific patch versions. +python-patch = [] # Introduces a dependency on PyPI. pypi = [] # Introduces a dependency on Git. diff --git a/crates/uv/src/commands/pip_compile.rs b/crates/uv/src/commands/pip_compile.rs index 28c28978c..c14cf8906 100644 --- a/crates/uv/src/commands/pip_compile.rs +++ b/crates/uv/src/commands/pip_compile.rs @@ -14,31 +14,31 @@ use owo_colors::OwoColorize; use tempfile::tempdir_in; use tracing::debug; -use distribution_types::{IndexLocations, LocalEditable, Verbatim}; +use distribution_types::{IndexLocations, LocalEditable, LocalEditables, Verbatim}; use platform_tags::Tags; use requirements_txt::EditableRequirement; use uv_auth::{KeyringProvider, GLOBAL_AUTH_STORE}; use uv_cache::Cache; -use uv_client::{ - BaseClientBuilder, Connectivity, FlatIndex, FlatIndexClient, RegistryClientBuilder, +use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder}; +use uv_configuration::{ + ConfigSettings, Constraints, IndexStrategy, NoBinary, NoBuild, Overrides, SetupPyStrategy, + Upgrade, }; use uv_dispatch::BuildDispatch; use uv_fs::Simplified; use uv_installer::Downloader; -use uv_interpreter::{find_best_python, PythonEnvironment, PythonVersion}; +use uv_interpreter::{find_best_python, PythonEnvironment}; use uv_normalize::{ExtraName, PackageName}; use uv_requirements::{ upgrade::read_lockfile, ExtrasSpecification, LookaheadResolver, NamedRequirementsResolver, RequirementsSource, RequirementsSpecification, SourceTreeResolver, }; use uv_resolver::{ - AnnotationStyle, DependencyMode, DisplayResolutionGraph, Exclusions, InMemoryIndex, Manifest, - OptionsBuilder, PreReleaseMode, PythonRequirement, ResolutionMode, Resolver, -}; -use uv_types::{ - BuildIsolation, ConfigSettings, Constraints, EmptyInstalledPackages, InFlight, NoBinary, - NoBuild, Overrides, SetupPyStrategy, Upgrade, + AnnotationStyle, DependencyMode, DisplayResolutionGraph, Exclusions, FlatIndex, InMemoryIndex, + Manifest, OptionsBuilder, PreReleaseMode, PythonRequirement, ResolutionMode, Resolver, }; +use uv_toolchain::PythonVersion; +use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight}; use uv_warnings::warn_user; use crate::commands::reporters::{DownloadReporter, ResolverReporter}; @@ -66,7 +66,9 @@ pub(crate) async fn pip_compile( include_index_url: bool, include_find_links: bool, include_marker_expression: bool, + include_index_annotation: bool, index_locations: IndexLocations, + index_strategy: IndexStrategy, keyring_provider: KeyringProvider, setup_py: SetupPyStrategy, config_settings: ConfigSettings, @@ -196,6 +198,13 @@ pub(crate) async fn pip_compile( |python_version| Cow::Owned(python_version.markers(interpreter.markers())), ); + // Generate, but don't enforce hashes for the requirements. + let hasher = if generate_hashes { + HashStrategy::Generate + } else { + HashStrategy::None + }; + // Incorporate any index locations from the provided sources. let index_locations = index_locations.combine(index_url, extra_index_urls, find_links, no_index); @@ -210,6 +219,7 @@ pub(crate) async fn pip_compile( .native_tls(native_tls) .connectivity(connectivity) .index_urls(index_locations.index_urls()) + .index_strategy(index_strategy) .keyring_provider(keyring_provider) .markers(&markers) .platform(interpreter.platform()) @@ -218,15 +228,11 @@ pub(crate) async fn pip_compile( // Read the lockfile, if present. let preferences = read_lockfile(output_file, upgrade).await?; - // Collect constraints and overrides. - let constraints = Constraints::from_requirements(constraints); - let overrides = Overrides::from_requirements(overrides); - // Resolve the flat indexes from `--find-links`. let flat_index = { let client = FlatIndexClient::new(&client, &cache); let entries = client.fetch(index_locations.flat_index()).await?; - FlatIndex::from_entries(entries, &tags) + FlatIndex::from_entries(entries, &tags, &hasher, &no_build, &NoBinary::None) }; // Track in-flight downloads, builds, etc., across resolutions. @@ -265,6 +271,7 @@ pub(crate) async fn pip_compile( // Convert from unnamed to named requirements. let mut requirements = NamedRequirementsResolver::new( requirements, + &hasher, &build_dispatch, &client, &top_level_index, @@ -279,6 +286,7 @@ pub(crate) async fn pip_compile( SourceTreeResolver::new( source_trees, &extras, + &hasher, &build_dispatch, &client, &top_level_index, @@ -292,21 +300,34 @@ pub(crate) async fn pip_compile( requirements }; + // Resolve the overrides from the provided sources. + let overrides = NamedRequirementsResolver::new( + overrides, + &hasher, + &build_dispatch, + &client, + &top_level_index, + ) + .with_reporter(ResolverReporter::from(printer)) + .resolve() + .await?; + + // Collect constraints and overrides. + let constraints = Constraints::from_requirements(constraints); + let overrides = Overrides::from_requirements(overrides); + // Build the editables and add their requirements let editables = if editables.is_empty() { Vec::new() } else { let start = std::time::Instant::now(); - let editables: Vec = editables - .into_iter() - .map(|editable| { - let EditableRequirement { url, extras, path } = editable; - Ok(LocalEditable { url, path, extras }) - }) - .collect::>()?; + let editables = LocalEditables::from_editables(editables.into_iter().map(|editable| { + let EditableRequirement { url, extras, path } = editable; + LocalEditable { url, path, extras } + })); - let downloader = Downloader::new(&cache, &tags, &client, &build_dispatch) + let downloader = Downloader::new(&cache, &tags, &hasher, &client, &build_dispatch) .with_reporter(DownloadReporter::from(printer).with_length(editables.len() as u64)); // Build all editables. @@ -354,6 +375,7 @@ pub(crate) async fn pip_compile( &constraints, &overrides, &editables, + &hasher, &build_dispatch, &client, &top_level_index, @@ -370,7 +392,7 @@ pub(crate) async fn pip_compile( preferences, project, editables, - // Do not consider any installed packages during compilation + // Do not consider any installed packages during resolution. Exclusions::All, lookaheads, ); @@ -392,6 +414,7 @@ pub(crate) async fn pip_compile( &client, &flat_index, &top_level_index, + &hasher, &build_dispatch, &EmptyInstalledPackages, )? @@ -501,6 +524,7 @@ pub(crate) async fn pip_compile( generate_hashes, include_extras, include_annotations, + include_index_annotation, annotation_style, ) )?; diff --git a/crates/uv/src/commands/pip_install.rs b/crates/uv/src/commands/pip_install.rs index e54d64ff0..63ff8af1e 100644 --- a/crates/uv/src/commands/pip_install.rs +++ b/crates/uv/src/commands/pip_install.rs @@ -1,4 +1,5 @@ use std::fmt::Write; + use std::path::Path; use anstream::eprint; @@ -10,8 +11,8 @@ use tempfile::tempdir_in; use tracing::debug; use distribution_types::{ - DistributionMetadata, IndexLocations, InstalledMetadata, LocalDist, LocalEditable, Name, - Resolution, + DistributionMetadata, IndexLocations, InstalledMetadata, LocalDist, LocalEditable, + LocalEditables, Name, Resolution, }; use install_wheel_rs::linker::LinkMode; use pep508_rs::{MarkerEnvironment, Requirement}; @@ -21,8 +22,11 @@ use requirements_txt::EditableRequirement; use uv_auth::{KeyringProvider, GLOBAL_AUTH_STORE}; use uv_cache::Cache; use uv_client::{ - BaseClientBuilder, Connectivity, FlatIndex, FlatIndexClient, RegistryClient, - RegistryClientBuilder, + BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClient, RegistryClientBuilder, +}; +use uv_configuration::{ + ConfigSettings, Constraints, IndexStrategy, NoBinary, NoBuild, Overrides, Reinstall, + SetupPyStrategy, Upgrade, }; use uv_dispatch::BuildDispatch; use uv_fs::Simplified; @@ -34,13 +38,10 @@ use uv_requirements::{ RequirementsSpecification, SourceTreeResolver, }; use uv_resolver::{ - DependencyMode, Exclusions, InMemoryIndex, Manifest, Options, OptionsBuilder, PreReleaseMode, - Preference, ResolutionGraph, ResolutionMode, Resolver, -}; -use uv_types::{ - BuildIsolation, ConfigSettings, Constraints, InFlight, NoBinary, NoBuild, Overrides, Reinstall, - SetupPyStrategy, Upgrade, + DependencyMode, Exclusions, FlatIndex, InMemoryIndex, Manifest, Options, OptionsBuilder, + PreReleaseMode, Preference, ResolutionGraph, ResolutionMode, Resolver, }; +use uv_types::{BuildIsolation, HashStrategy, InFlight}; use uv_warnings::warn_user; use crate::commands::reporters::{DownloadReporter, InstallReporter, ResolverReporter}; @@ -61,10 +62,12 @@ pub(crate) async fn pip_install( dependency_mode: DependencyMode, upgrade: Upgrade, index_locations: IndexLocations, + index_strategy: IndexStrategy, keyring_provider: KeyringProvider, reinstall: Reinstall, link_mode: LinkMode, compile: bool, + require_hashes: bool, setup_py: SetupPyStrategy, connectivity: Connectivity, config_settings: &ConfigSettings, @@ -82,6 +85,7 @@ pub(crate) async fn pip_install( printer: Printer, ) -> Result { let start = std::time::Instant::now(); + let client_builder = BaseClientBuilder::new() .connectivity(connectivity) .native_tls(native_tls) @@ -156,6 +160,7 @@ pub(crate) async fn pip_install( if reinstall.is_none() && upgrade.is_none() && source_trees.is_empty() + && overrides.is_empty() && site_packages.satisfies(&requirements, &editables, &constraints)? { let num_requirements = requirements.len() + editables.len(); @@ -181,6 +186,19 @@ pub(crate) async fn pip_install( let tags = venv.interpreter().tags()?; let markers = venv.interpreter().markers(); + // Collect the set of required hashes. + let hasher = if require_hashes { + HashStrategy::from_requirements( + requirements + .iter() + .chain(overrides.iter()) + .map(|entry| (&entry.requirement, entry.hashes.as_slice())), + markers, + )? + } else { + HashStrategy::None + }; + // Incorporate any index locations from the provided sources. let index_locations = index_locations.combine(index_url, extra_index_urls, find_links, no_index); @@ -195,6 +213,7 @@ pub(crate) async fn pip_install( .native_tls(native_tls) .connectivity(connectivity) .index_urls(index_locations.index_urls()) + .index_strategy(index_strategy) .keyring_provider(keyring_provider) .markers(markers) .platform(interpreter.platform()) @@ -204,7 +223,7 @@ pub(crate) async fn pip_install( let flat_index = { let client = FlatIndexClient::new(&client, &cache); let entries = client.fetch(index_locations.flat_index()).await?; - FlatIndex::from_entries(entries, tags) + FlatIndex::from_entries(entries, tags, &hasher, &no_build, &no_binary) }; // Determine whether to enable build isolation. @@ -244,25 +263,44 @@ pub(crate) async fn pip_install( // Resolve the requirements from the provided sources. let requirements = { // Convert from unnamed to named requirements. - let mut requirements = - NamedRequirementsResolver::new(requirements, &resolve_dispatch, &client, &index) - .with_reporter(ResolverReporter::from(printer)) - .resolve() - .await?; + let mut requirements = NamedRequirementsResolver::new( + requirements, + &hasher, + &resolve_dispatch, + &client, + &index, + ) + .with_reporter(ResolverReporter::from(printer)) + .resolve() + .await?; // Resolve any source trees into requirements. if !source_trees.is_empty() { requirements.extend( - SourceTreeResolver::new(source_trees, extras, &resolve_dispatch, &client, &index) - .with_reporter(ResolverReporter::from(printer)) - .resolve() - .await?, + SourceTreeResolver::new( + source_trees, + extras, + &hasher, + &resolve_dispatch, + &client, + &index, + ) + .with_reporter(ResolverReporter::from(printer)) + .resolve() + .await?, ); } requirements }; + // Resolve the overrides from the provided sources. + let overrides = + NamedRequirementsResolver::new(overrides, &hasher, &resolve_dispatch, &client, &index) + .with_reporter(ResolverReporter::from(printer)) + .resolve() + .await?; + // Build all editable distributions. The editables are shared between resolution and // installation, and should live for the duration of the command. If an editable is already // installed in the environment, we'll still re-build it here. @@ -270,10 +308,11 @@ pub(crate) async fn pip_install( let editables = if editables.is_empty() { vec![] } else { - editable_wheel_dir = tempdir_in(venv.root())?; + editable_wheel_dir = tempdir_in(cache.root())?; build_editables( &editables, editable_wheel_dir.path(), + &hasher, &cache, &interpreter, tags, @@ -298,6 +337,7 @@ pub(crate) async fn pip_install( overrides, project, &editables, + &hasher, &site_packages, &reinstall, &upgrade, @@ -358,6 +398,7 @@ pub(crate) async fn pip_install( link_mode, compile, &index_locations, + &hasher, tags, &client, &in_flight, @@ -385,13 +426,13 @@ async fn read_requirements( extras: &ExtrasSpecification<'_>, client_builder: &BaseClientBuilder<'_>, ) -> Result { - // If the user requests `extras` but does not provide a pyproject toml source - if !matches!(extras, ExtrasSpecification::None) - && !requirements - .iter() - .any(|source| matches!(source, RequirementsSource::PyprojectToml(_))) - { - return Err(anyhow!("Requesting extras requires a pyproject.toml input file.").into()); + // If the user requests `extras` but does not provide a valid source (e.g., a `pyproject.toml`), + // return an error. + if !extras.is_empty() && !requirements.iter().any(RequirementsSource::allows_extras) { + return Err(anyhow!( + "Requesting extras requires a `pyproject.toml`, `setup.cfg`, or `setup.py` file." + ) + .into()); } // Read all requirements from the provided sources. @@ -404,21 +445,24 @@ async fn read_requirements( ) .await?; - // Check that all provided extras are used - if let ExtrasSpecification::Some(extras) = extras { - let mut unused_extras = extras - .iter() - .filter(|extra| !spec.extras.contains(extra)) - .collect::>(); - if !unused_extras.is_empty() { - unused_extras.sort_unstable(); - unused_extras.dedup(); - let s = if unused_extras.len() == 1 { "" } else { "s" }; - return Err(anyhow!( - "Requested extra{s} not found: {}", - unused_extras.iter().join(", ") - ) - .into()); + // If all the metadata could be statically resolved, validate that every extra was used. If we + // need to resolve metadata via PEP 517, we don't know which extras are used until much later. + if spec.source_trees.is_empty() { + if let ExtrasSpecification::Some(extras) = extras { + let mut unused_extras = extras + .iter() + .filter(|extra| !spec.extras.contains(extra)) + .collect::>(); + if !unused_extras.is_empty() { + unused_extras.sort_unstable(); + unused_extras.dedup(); + let s = if unused_extras.len() == 1 { "" } else { "s" }; + return Err(anyhow!( + "Requested extra{s} not found: {}", + unused_extras.iter().join(", ") + ) + .into()); + } } } @@ -430,6 +474,7 @@ async fn read_requirements( async fn build_editables( editables: &[EditableRequirement], editable_wheel_dir: &Path, + hasher: &HashStrategy, cache: &Cache, interpreter: &Interpreter, tags: &Tags, @@ -439,20 +484,17 @@ async fn build_editables( ) -> Result, Error> { let start = std::time::Instant::now(); - let downloader = Downloader::new(cache, tags, client, build_dispatch) + let downloader = Downloader::new(cache, tags, hasher, client, build_dispatch) .with_reporter(DownloadReporter::from(printer).with_length(editables.len() as u64)); - let editables: Vec = editables - .iter() - .map(|editable| { - let EditableRequirement { url, extras, path } = editable; - Ok(LocalEditable { - url: url.clone(), - extras: extras.clone(), - path: path.clone(), - }) - }) - .collect::>()?; + let editables = LocalEditables::from_editables(editables.iter().map(|editable| { + let EditableRequirement { url, extras, path } = editable; + LocalEditable { + url: url.clone(), + extras: extras.clone(), + path: path.clone(), + } + })); let editables: Vec<_> = downloader .build_editables(editables, editable_wheel_dir) @@ -499,6 +541,7 @@ async fn resolve( overrides: Vec, project: Option, editables: &[BuiltEditable], + hasher: &HashStrategy, site_packages: &SitePackages<'_>, reinstall: &Reinstall, upgrade: &Upgrade, @@ -545,6 +588,7 @@ async fn resolve( &constraints, &overrides, &editables, + hasher, build_dispatch, client, index, @@ -575,6 +619,7 @@ async fn resolve( client, flat_index, index, + hasher, build_dispatch, site_packages, )? @@ -593,6 +638,17 @@ async fn resolve( .dimmed() )?; + // Notify the user of any diagnostics. + for diagnostic in resolution.diagnostics() { + writeln!( + printer.stderr(), + "{}{} {}", + "warning".yellow().bold(), + ":".bold(), + diagnostic.message().bold() + )?; + } + Ok(resolution) } @@ -607,6 +663,7 @@ async fn install( link_mode: LinkMode, compile: bool, index_urls: &IndexLocations, + hasher: &HashStrategy, tags: &Tags, client: &RegistryClient, in_flight: &InFlight, @@ -634,6 +691,7 @@ async fn install( site_packages, reinstall, no_binary, + hasher, index_urls, cache, venv, @@ -686,7 +744,7 @@ async fn install( } else { let start = std::time::Instant::now(); - let downloader = Downloader::new(cache, tags, client, build_dispatch) + let downloader = Downloader::new(cache, tags, hasher, client, build_dispatch) .with_reporter(DownloadReporter::from(printer).with_length(remote.len() as u64)); let wheels = downloader @@ -1002,6 +1060,9 @@ enum Error { #[error(transparent)] Platform(#[from] platform_tags::PlatformError), + #[error(transparent)] + Hash(#[from] uv_types::HashStrategyError), + #[error(transparent)] Io(#[from] std::io::Error), diff --git a/crates/uv/src/commands/pip_sync.rs b/crates/uv/src/commands/pip_sync.rs index d9ad30410..218ad29c3 100644 --- a/crates/uv/src/commands/pip_sync.rs +++ b/crates/uv/src/commands/pip_sync.rs @@ -1,22 +1,26 @@ use std::fmt::Write; +use anstream::eprint; use anyhow::{anyhow, Context, Result}; use itertools::Itertools; use owo_colors::OwoColorize; use tracing::debug; use distribution_types::{ - IndexLocations, InstalledMetadata, LocalDist, LocalEditable, Name, ResolvedDist, + IndexLocations, InstalledMetadata, LocalDist, LocalEditable, LocalEditables, Name, ResolvedDist, }; use install_wheel_rs::linker::LinkMode; + use platform_tags::Tags; use pypi_types::Yanked; use requirements_txt::EditableRequirement; use uv_auth::{KeyringProvider, GLOBAL_AUTH_STORE}; use uv_cache::{ArchiveTarget, ArchiveTimestamp, Cache}; use uv_client::{ - BaseClientBuilder, Connectivity, FlatIndex, FlatIndexClient, RegistryClient, - RegistryClientBuilder, + BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClient, RegistryClientBuilder, +}; +use uv_configuration::{ + ConfigSettings, IndexStrategy, NoBinary, NoBuild, Reinstall, SetupPyStrategy, }; use uv_dispatch::BuildDispatch; use uv_fs::Simplified; @@ -26,11 +30,8 @@ use uv_requirements::{ ExtrasSpecification, NamedRequirementsResolver, RequirementsSource, RequirementsSpecification, SourceTreeResolver, }; -use uv_resolver::{DependencyMode, InMemoryIndex, Manifest, OptionsBuilder, Resolver}; -use uv_types::{ - BuildIsolation, ConfigSettings, EmptyInstalledPackages, InFlight, NoBinary, NoBuild, Reinstall, - SetupPyStrategy, -}; +use uv_resolver::{DependencyMode, FlatIndex, InMemoryIndex, Manifest, OptionsBuilder, Resolver}; +use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight}; use uv_warnings::warn_user; use crate::commands::reporters::{DownloadReporter, InstallReporter, ResolverReporter}; @@ -44,7 +45,9 @@ pub(crate) async fn pip_sync( reinstall: &Reinstall, link_mode: LinkMode, compile: bool, + require_hashes: bool, index_locations: IndexLocations, + index_strategy: IndexStrategy, keyring_provider: KeyringProvider, setup_py: SetupPyStrategy, connectivity: Connectivity, @@ -61,6 +64,7 @@ pub(crate) async fn pip_sync( printer: Printer, ) -> Result { let start = std::time::Instant::now(); + let client_builder = BaseClientBuilder::new() .connectivity(connectivity) .native_tls(native_tls) @@ -128,6 +132,19 @@ pub(crate) async fn pip_sync( // Determine the current environment markers. let tags = venv.interpreter().tags()?; + let markers = venv.interpreter().markers(); + + // Collect the set of required hashes. + let hasher = if require_hashes { + HashStrategy::from_requirements( + requirements + .iter() + .map(|entry| (&entry.requirement, entry.hashes.as_slice())), + markers, + )? + } else { + HashStrategy::None + }; // Incorporate any index locations from the provided sources. let index_locations = @@ -143,6 +160,7 @@ pub(crate) async fn pip_sync( .native_tls(native_tls) .connectivity(connectivity) .index_urls(index_locations.index_urls()) + .index_strategy(index_strategy) .keyring_provider(keyring_provider) .markers(venv.interpreter().markers()) .platform(venv.interpreter().platform()) @@ -152,7 +170,7 @@ pub(crate) async fn pip_sync( let flat_index = { let client = FlatIndexClient::new(&client, &cache); let entries = client.fetch(index_locations.flat_index()).await?; - FlatIndex::from_entries(entries, tags) + FlatIndex::from_entries(entries, tags, &hasher, &no_build, &no_binary) }; // Create a shared in-memory index. @@ -195,7 +213,7 @@ pub(crate) async fn pip_sync( let requirements = { // Convert from unnamed to named requirements. let mut requirements = - NamedRequirementsResolver::new(requirements, &build_dispatch, &client, &index) + NamedRequirementsResolver::new(requirements, &hasher, &build_dispatch, &client, &index) .with_reporter(ResolverReporter::from(printer)) .resolve() .await?; @@ -206,6 +224,7 @@ pub(crate) async fn pip_sync( SourceTreeResolver::new( source_trees, &ExtrasSpecification::None, + &hasher, &build_dispatch, &client, &index, @@ -224,6 +243,7 @@ pub(crate) async fn pip_sync( editables, &site_packages, reinstall, + &hasher, venv.interpreter(), tags, &cache, @@ -247,6 +267,7 @@ pub(crate) async fn pip_sync( site_packages, reinstall, &no_binary, + &hasher, &index_locations, &cache, &venv, @@ -300,12 +321,22 @@ pub(crate) async fn pip_sync( &client, &flat_index, &index, + &hasher, &build_dispatch, // TODO(zanieb): We should consider support for installed packages in pip sync &EmptyInstalledPackages, )? .with_reporter(reporter); - let resolution = resolver.resolve().await?; + + let resolution = match resolver.resolve().await { + Err(uv_resolver::ResolveError::NoSolution(err)) => { + let report = miette::Report::msg(format!("{err}")) + .context("No solution found when resolving dependencies:"); + eprint!("{report:?}"); + return Ok(ExitStatus::Failure); + } + result => result, + }?; let s = if resolution.len() == 1 { "" } else { "s" }; writeln!( @@ -334,7 +365,7 @@ pub(crate) async fn pip_sync( } else { let start = std::time::Instant::now(); - let downloader = Downloader::new(&cache, tags, &client, &build_dispatch) + let downloader = Downloader::new(&cache, tags, &hasher, &client, &build_dispatch) .with_reporter(DownloadReporter::from(printer).with_length(remote.len() as u64)); let wheels = downloader @@ -530,6 +561,7 @@ async fn resolve_editables( editables: Vec, site_packages: &SitePackages<'_>, reinstall: &Reinstall, + hasher: &HashStrategy, interpreter: &Interpreter, tags: &Tags, cache: &Cache, @@ -596,32 +628,28 @@ async fn resolve_editables( } else { let start = std::time::Instant::now(); - let temp_dir = tempfile::tempdir_in(cache.root())?; - - let downloader = Downloader::new(cache, tags, client, build_dispatch) + let downloader = Downloader::new(cache, tags, hasher, client, build_dispatch) .with_reporter(DownloadReporter::from(printer).with_length(uninstalled.len() as u64)); - let local_editables: Vec = uninstalled - .iter() - .map(|editable| { - let EditableRequirement { url, path, extras } = editable; - Ok(LocalEditable { - url: url.clone(), - path: path.clone(), - extras: extras.clone(), - }) - }) - .collect::>()?; + let editables = LocalEditables::from_editables(uninstalled.iter().map(|editable| { + let EditableRequirement { url, path, extras } = editable; + LocalEditable { + url: url.clone(), + path: path.clone(), + extras: extras.clone(), + } + })); - let built_editables: Vec<_> = downloader - .build_editables(local_editables, temp_dir.path()) + let editable_wheel_dir = tempfile::tempdir_in(cache.root())?; + let editables: Vec<_> = downloader + .build_editables(editables, editable_wheel_dir.path()) .await .context("Failed to build editables")? .into_iter() .collect(); // Validate that the editables are compatible with the target Python version. - for editable in &built_editables { + for editable in &editables { if let Some(python_requires) = editable.metadata.requires_python.as_ref() { if !python_requires.contains(interpreter.python_version()) { return Err(anyhow!( @@ -634,19 +662,19 @@ async fn resolve_editables( } } - let s = if built_editables.len() == 1 { "" } else { "s" }; + let s = if editables.len() == 1 { "" } else { "s" }; writeln!( printer.stderr(), "{}", format!( "Built {} in {}", - format!("{} editable{}", built_editables.len(), s).bold(), + format!("{} editable{}", editables.len(), s).bold(), elapsed(start.elapsed()) ) .dimmed() )?; - (built_editables, Some(temp_dir)) + (editables, Some(editable_wheel_dir)) }; Ok(ResolvedEditables { diff --git a/crates/uv/src/commands/pip_uninstall.rs b/crates/uv/src/commands/pip_uninstall.rs index e4d91154d..45e19da59 100644 --- a/crates/uv/src/commands/pip_uninstall.rs +++ b/crates/uv/src/commands/pip_uninstall.rs @@ -82,7 +82,7 @@ pub(crate) async fn pip_uninstall( let (named, unnamed): (Vec, Vec) = spec .requirements .into_iter() - .partition_map(|requirement| match requirement { + .partition_map(|entry| match entry.requirement { RequirementsTxtRequirement::Pep508(requirement) => Either::Left(requirement), RequirementsTxtRequirement::Unnamed(requirement) => Either::Right(requirement), }); diff --git a/crates/uv/src/commands/venv.rs b/crates/uv/src/commands/venv.rs index 6faa5e131..6f44fecbe 100644 --- a/crates/uv/src/commands/venv.rs +++ b/crates/uv/src/commands/venv.rs @@ -15,14 +15,13 @@ use distribution_types::{DistributionMetadata, IndexLocations, Name, ResolvedDis use pep508_rs::Requirement; use uv_auth::{KeyringProvider, GLOBAL_AUTH_STORE}; use uv_cache::Cache; -use uv_client::{Connectivity, FlatIndex, FlatIndexClient, RegistryClientBuilder}; +use uv_client::{Connectivity, FlatIndexClient, RegistryClientBuilder}; +use uv_configuration::{ConfigSettings, IndexStrategy, NoBinary, NoBuild, SetupPyStrategy}; use uv_dispatch::BuildDispatch; use uv_fs::Simplified; use uv_interpreter::{find_default_python, find_requested_python, Error}; -use uv_resolver::{InMemoryIndex, OptionsBuilder}; -use uv_types::{ - BuildContext, BuildIsolation, ConfigSettings, InFlight, NoBinary, NoBuild, SetupPyStrategy, -}; +use uv_resolver::{FlatIndex, InMemoryIndex, OptionsBuilder}; +use uv_types::{BuildContext, BuildIsolation, HashStrategy, InFlight}; use crate::commands::ExitStatus; use crate::printer::Printer; @@ -34,6 +33,7 @@ pub(crate) async fn venv( path: &Path, python_request: Option<&str>, index_locations: &IndexLocations, + index_strategy: IndexStrategy, keyring_provider: KeyringProvider, prompt: uv_virtualenv::Prompt, system_site_packages: bool, @@ -48,6 +48,7 @@ pub(crate) async fn venv( path, python_request, index_locations, + index_strategy, keyring_provider, prompt, system_site_packages, @@ -93,6 +94,7 @@ async fn venv_impl( path: &Path, python_request: Option<&str>, index_locations: &IndexLocations, + index_strategy: IndexStrategy, keyring_provider: KeyringProvider, prompt: uv_virtualenv::Prompt, system_site_packages: bool, @@ -150,6 +152,7 @@ async fn venv_impl( let client = RegistryClientBuilder::new(cache.clone()) .native_tls(native_tls) .index_urls(index_locations.index_urls()) + .index_strategy(index_strategy) .keyring_provider(keyring_provider) .connectivity(connectivity) .markers(interpreter.markers()) @@ -164,7 +167,13 @@ async fn venv_impl( .fetch(index_locations.flat_index()) .await .map_err(VenvError::FlatIndex)?; - FlatIndex::from_entries(entries, tags) + FlatIndex::from_entries( + entries, + tags, + &HashStrategy::None, + &NoBuild::All, + &NoBinary::None, + ) }; // Create a shared in-memory index. diff --git a/crates/uv/src/logging.rs b/crates/uv/src/logging.rs index 67c491a4d..3ba021d59 100644 --- a/crates/uv/src/logging.rs +++ b/crates/uv/src/logging.rs @@ -5,7 +5,6 @@ use std::str::FromStr; use anyhow::Context; use chrono::Utc; use owo_colors::OwoColorize; -use tracing::level_filters::LevelFilter; use tracing::{Event, Subscriber}; #[cfg(feature = "tracing-durations-export")] use tracing_durations_export::{ @@ -115,19 +114,26 @@ where /// includes targets and timestamps, along with all `uv=debug` messages by default. pub(crate) fn setup_logging( level: Level, - duration: impl Layer + Send + Sync, + durations: impl Layer + Send + Sync, ) -> anyhow::Result<()> { let default_directive = match level { Level::Default => { // Show nothing, but allow `RUST_LOG` to override. - LevelFilter::OFF.into() + tracing::level_filters::LevelFilter::OFF.into() } Level::Verbose | Level::ExtraVerbose => { // Show `DEBUG` messages from the CLI crate, but allow `RUST_LOG` to override. - Directive::from_str("uv=debug").unwrap() + Directive::from_str("uv=trace").unwrap() } }; + // Only record our own spans. + let durations_layer = + durations.with_filter(tracing_subscriber::filter::Targets::new().with_target( + env!("CARGO_PKG_NAME"), + tracing::level_filters::LevelFilter::INFO, + )); + let filter = EnvFilter::builder() .with_default_directive(default_directive) .from_env() @@ -148,26 +154,26 @@ pub(crate) fn setup_logging( ColorChoice::Auto => unreachable!(), }; tracing_subscriber::registry() - .with(duration) - .with(filter) + .with(durations_layer) .with( tracing_subscriber::fmt::layer() .event_format(format) .with_writer(std::io::stderr) - .with_ansi(ansi), + .with_ansi(ansi) + .with_filter(filter), ) .init(); } Level::ExtraVerbose => { // Regardless of the tracing level, include the uptime and target for each message. tracing_subscriber::registry() - .with(duration) - .with(filter) + .with(durations_layer) .with( HierarchicalLayer::default() .with_targets(true) .with_timer(Uptime::default()) - .with_writer(std::io::stderr), + .with_writer(std::io::stderr) + .with_filter(filter), ) .init(); } @@ -178,15 +184,15 @@ pub(crate) fn setup_logging( /// Setup the `TRACING_DURATIONS_FILE` environment variable to enable tracing durations. #[cfg(feature = "tracing-durations-export")] -pub(crate) fn setup_duration() -> ( +pub(crate) fn setup_duration() -> anyhow::Result<( Option>, Option, -) { +)> { if let Ok(location) = std::env::var("TRACING_DURATIONS_FILE") { let location = std::path::PathBuf::from(location); if let Some(parent) = location.parent() { fs_err::create_dir_all(parent) - .expect("Failed to create parent of TRACING_DURATIONS_FILE"); + .context("Failed to create parent of TRACING_DURATIONS_FILE")?; } let plot_config = PlotConfig { multi_lane: true, @@ -203,9 +209,9 @@ pub(crate) fn setup_duration() -> ( .plot_file(location.with_extension("svg")) .plot_config(plot_config) .build() - .expect("Couldn't create TRACING_DURATIONS_FILE files"); - (Some(layer), Some(guard)) + .context("Couldn't create TRACING_DURATIONS_FILE files")?; + Ok((Some(layer), Some(guard))) } else { - (None, None) + Ok((None, None)) } } diff --git a/crates/uv/src/main.rs b/crates/uv/src/main.rs index 97511eb4d..d39b7198c 100644 --- a/crates/uv/src/main.rs +++ b/crates/uv/src/main.rs @@ -16,15 +16,15 @@ use distribution_types::{FlatIndexLocation, IndexLocations, IndexUrl}; use uv_auth::KeyringProvider; use uv_cache::{Cache, CacheArgs, Refresh}; use uv_client::Connectivity; -use uv_interpreter::PythonVersion; -use uv_normalize::{ExtraName, PackageName}; -use uv_requirements::{ExtrasSpecification, RequirementsSource}; -use uv_resolver::{AnnotationStyle, DependencyMode, PreReleaseMode, ResolutionMode}; -use uv_types::NoBinary; -use uv_types::{ +use uv_configuration::{ ConfigSettingEntry, ConfigSettings, NoBuild, PackageNameSpecifier, Reinstall, SetupPyStrategy, Upgrade, }; +use uv_configuration::{IndexStrategy, NoBinary}; +use uv_normalize::{ExtraName, PackageName}; +use uv_requirements::{ExtrasSpecification, RequirementsSource}; +use uv_resolver::{AnnotationStyle, DependencyMode, PreReleaseMode, ResolutionMode}; +use uv_toolchain::PythonVersion; use crate::commands::{extra_name_with_clap_error, ExitStatus, ListFormat, VersionFormat}; use crate::compat::CompatArgs; @@ -335,6 +335,10 @@ struct PipCompileArgs { #[clap(long)] no_header: bool, + /// Choose the style of the annotation comments, which indicate the source of each package. + #[clap(long, default_value_t=AnnotationStyle::Split, value_enum)] + annotation_style: AnnotationStyle, + /// Change header comment to reflect custom command wrapping `uv pip compile`. #[clap(long, env = "UV_CUSTOM_COMPILE_COMMAND")] custom_compile_command: Option, @@ -384,6 +388,15 @@ struct PipCompileArgs { #[clap(long, conflicts_with = "index_url", conflicts_with = "extra_index_url")] no_index: bool, + /// The strategy to use when resolving against multiple index URLs. + /// + /// By default, `uv` will stop at the first index on which a given package is available, and + /// limit resolutions to those present on that first index. This prevents "dependency confusion" + /// attacks, whereby an attack can upload a malicious package under the same name to a secondary + /// index. + #[clap(long, default_value_t, value_enum, env = "UV_INDEX_STRATEGY")] + index_strategy: IndexStrategy, + /// Attempt to use `keyring` for authentication for index urls /// /// Due to not having Python imports, only `--keyring-provider subprocess` argument is currently @@ -486,9 +499,10 @@ struct PipCompileArgs { #[clap(long, hide = true)] emit_marker_expression: bool, - /// Choose the style of the annotation comments, which indicate the source of each package. - #[clap(long, default_value_t=AnnotationStyle::Split, value_enum)] - annotation_style: AnnotationStyle, + /// Include comment annotations indicating the index used to resolve each package (e.g., + /// `# from https://pypi.org/simple`). + #[clap(long)] + emit_index_annotation: bool, #[command(flatten)] compat_args: compat::PipCompileCompatArgs, @@ -570,6 +584,29 @@ struct PipSyncArgs { #[clap(long, conflicts_with = "index_url", conflicts_with = "extra_index_url")] no_index: bool, + /// The strategy to use when resolving against multiple index URLs. + /// + /// By default, `uv` will stop at the first index on which a given package is available, and + /// limit resolutions to those present on that first index. This prevents "dependency confusion" + /// attacks, whereby an attack can upload a malicious package under the same name to a secondary + /// index. + #[clap(long, default_value_t, value_enum, env = "UV_INDEX_STRATEGY")] + index_strategy: IndexStrategy, + + /// Require a matching hash for each requirement. + /// + /// Hash-checking mode is all or nothing. If enabled, _all_ requirements must be provided + /// with a corresponding hash or set of hashes. Additionally, if enabled, _all_ requirements + /// must either be pinned to exact versions (e.g., `==1.0.0`), or be specified via direct URL. + /// + /// Hash-checking mode introduces a number of additional constraints: + /// - Git dependencies are not supported. + /// - Editable installs are not supported. + /// - Local dependencies are not supported, unless they point to a specific wheel (`.whl`) or + /// source archive (`.zip`, `.tar.gz`), as opposed to a directory. + #[clap(long, hide = true)] + require_hashes: bool, + /// Attempt to use `keyring` for authentication for index urls /// /// Function's similar to `pip`'s `--keyring-provider subprocess` argument, @@ -620,7 +657,7 @@ struct PipSyncArgs { /// environments, when installing into Python installations that are managed by an external /// package manager, like `apt`. It should be used with caution, as such Python installations /// explicitly recommend against modifications by other package managers (like `uv` or `pip`). - #[clap(long, requires = "discovery")] + #[clap(long, env = "UV_BREAK_SYSTEM_PACKAGES", requires = "discovery")] break_system_packages: bool, /// Use legacy `setuptools` behavior when building source distributions without a @@ -677,6 +714,10 @@ struct PipSyncArgs { #[clap(long)] compile: bool, + /// Don't compile Python files to bytecode. + #[clap(long, hide = true, conflicts_with = "compile")] + no_compile: bool, + /// Settings to pass to the PEP 517 build backend, specified as `KEY=VALUE` pairs. #[clap(long, short = 'C', alias = "config-settings")] config_setting: Vec, @@ -790,10 +831,6 @@ struct PipInstallArgs { #[clap(long, hide = true, conflicts_with = "prerelease")] pre: bool, - /// Write the compiled requirements to the given `requirements.txt` file. - #[clap(long, short)] - output_file: Option, - /// The URL of the Python package index (by default: ). /// /// The index given by this flag is given lower priority than all other @@ -831,6 +868,29 @@ struct PipInstallArgs { #[clap(long, conflicts_with = "index_url", conflicts_with = "extra_index_url")] no_index: bool, + /// The strategy to use when resolving against multiple index URLs. + /// + /// By default, `uv` will stop at the first index on which a given package is available, and + /// limit resolutions to those present on that first index. This prevents "dependency confusion" + /// attacks, whereby an attack can upload a malicious package under the same name to a secondary + /// index. + #[clap(long, default_value_t, value_enum, env = "UV_INDEX_STRATEGY")] + index_strategy: IndexStrategy, + + /// Require a matching hash for each requirement. + /// + /// Hash-checking mode is all or nothing. If enabled, _all_ requirements must be provided + /// with a corresponding hash or set of hashes. Additionally, if enabled, _all_ requirements + /// must either be pinned to exact versions (e.g., `==1.0.0`), or be specified via direct URL. + /// + /// Hash-checking mode introduces a number of additional constraints: + /// - Git dependencies are not supported. + /// - Editable installs are not supported. + /// - Local dependencies are not supported, unless they point to a specific wheel (`.whl`) or + /// source archive (`.zip`, `.tar.gz`), as opposed to a directory. + #[clap(long, hide = true)] + require_hashes: bool, + /// Attempt to use `keyring` for authentication for index urls /// /// Due to not having Python imports, only `--keyring-provider subprocess` argument is currently @@ -881,7 +941,7 @@ struct PipInstallArgs { /// environments, when installing into Python installations that are managed by an external /// package manager, like `apt`. It should be used with caution, as such Python installations /// explicitly recommend against modifications by other package managers (like `uv` or `pip`). - #[clap(long, requires = "discovery")] + #[clap(long, env = "UV_BREAK_SYSTEM_PACKAGES", requires = "discovery")] break_system_packages: bool, /// Use legacy `setuptools` behavior when building source distributions without a @@ -938,6 +998,10 @@ struct PipInstallArgs { #[clap(long)] compile: bool, + /// Don't compile Python files to bytecode. + #[clap(long, hide = true, conflicts_with = "compile")] + no_compile: bool, + /// Settings to pass to the PEP 517 build backend, specified as `KEY=VALUE` pairs. #[clap(long, short = 'C', alias = "config-settings")] config_setting: Vec, @@ -1022,7 +1086,7 @@ struct PipUninstallArgs { /// environments, when installing into Python installations that are managed by an external /// package manager, like `apt`. It should be used with caution, as such Python installations /// explicitly recommend against modifications by other package managers (like `uv` or `pip`). - #[clap(long, requires = "discovery")] + #[clap(long, env = "UV_BREAK_SYSTEM_PACKAGES", requires = "discovery")] break_system_packages: bool, /// Run offline, i.e., without accessing the network. @@ -1328,6 +1392,15 @@ struct VenvArgs { #[clap(long, conflicts_with = "index_url", conflicts_with = "extra_index_url")] no_index: bool, + /// The strategy to use when resolving against multiple index URLs. + /// + /// By default, `uv` will stop at the first index on which a given package is available, and + /// limit resolutions to those present on that first index. This prevents "dependency confusion" + /// attacks, whereby an attack can upload a malicious package under the same name to a secondary + /// index. + #[clap(long, default_value_t, value_enum, env = "UV_INDEX_STRATEGY")] + index_strategy: IndexStrategy, + /// Attempt to use `keyring` for authentication for index urls /// /// Due to not having Python imports, only `--keyring-provider subprocess` argument is currently @@ -1423,7 +1496,7 @@ async fn run() -> Result { // Configure the `tracing` crate, which controls internal logging. #[cfg(feature = "tracing-durations-export")] - let (duration_layer, _duration_guard) = logging::setup_duration(); + let (duration_layer, _duration_guard) = logging::setup_duration()?; #[cfg(not(feature = "tracing-durations-export"))] let duration_layer = None::; logging::setup_logging( @@ -1543,7 +1616,9 @@ async fn run() -> Result { args.emit_index_url, args.emit_find_links, args.emit_marker_expression, + args.emit_index_annotation, index_urls, + args.index_strategy, args.keyring_provider, setup_py, config_settings, @@ -1599,7 +1674,9 @@ async fn run() -> Result { &reinstall, args.link_mode, args.compile, + args.require_hashes, index_urls, + args.index_strategy, args.keyring_provider, setup_py, if args.offline { @@ -1693,10 +1770,12 @@ async fn run() -> Result { dependency_mode, upgrade, index_urls, + args.index_strategy, args.keyring_provider, reinstall, args.link_mode, args.compile, + args.require_hashes, setup_py, if args.offline { Connectivity::Offline @@ -1825,6 +1904,7 @@ async fn run() -> Result { &args.name, args.python.as_deref(), &index_locations, + args.index_strategy, args.keyring_provider, uv_virtualenv::Prompt::from_args(prompt), args.system_site_packages, diff --git a/crates/uv/tests/cache_prune.rs b/crates/uv/tests/cache_prune.rs index fe23ca759..4d96e6979 100644 --- a/crates/uv/tests/cache_prune.rs +++ b/crates/uv/tests/cache_prune.rs @@ -21,6 +21,7 @@ fn prune_command(context: &TestContext) -> Command { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir); if cfg!(all(windows, debug_assertions)) { @@ -41,6 +42,7 @@ fn sync_command(context: &TestContext) -> Command { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir); if cfg!(all(windows, debug_assertions)) { @@ -126,7 +128,7 @@ fn prune_stale_symlink() -> Result<()> { .success(); // Remove the wheels directory, causing the symlink to become stale. - let wheels = context.cache_dir.child("wheels-v0"); + let wheels = context.cache_dir.child("wheels-v1"); fs_err::remove_dir_all(wheels)?; let filters: Vec<_> = context diff --git a/crates/uv/tests/common/mod.rs b/crates/uv/tests/common/mod.rs index f9f384165..2c85e3e7f 100644 --- a/crates/uv/tests/common/mod.rs +++ b/crates/uv/tests/common/mod.rs @@ -4,25 +4,29 @@ use assert_cmd::assert::{Assert, OutputAssertExt}; use assert_cmd::Command; use assert_fs::assert::PathAssert; + use assert_fs::fixture::PathChild; -#[cfg(unix)] -use fs_err::os::unix::fs::symlink as symlink_file; -#[cfg(windows)] -use fs_err::os::windows::fs::symlink_file; use regex::Regex; use std::borrow::BorrowMut; use std::env; use std::ffi::OsString; use std::path::{Path, PathBuf}; use std::process::Output; -use uv_fs::Simplified; +use std::str::FromStr; +use uv_interpreter::find_requested_python; use uv_cache::Cache; -use uv_interpreter::find_requested_python; +use uv_fs::Simplified; +use uv_toolchain::{toolchains_for_version, PythonVersion}; // Exclude any packages uploaded after this date. pub static EXCLUDE_NEWER: &str = "2024-03-25T00:00:00Z"; +/// 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. +pub const BUILD_VENDOR_LINKS_URL: &str = + "https://raw.githubusercontent.com/astral-sh/packse/0.3.14/vendor/links.html"; + #[doc(hidden)] // Macro and test context only, don't use directly. pub const INSTA_FILTERS: &[(&str, &str)] = &[ (r"--cache-dir [^\s]+", "--cache-dir [CACHE_DIR]"), @@ -69,6 +73,9 @@ impl TestContext { let site_packages = site_packages_path(&venv, format!("python{python_version}")); + let python_version = + PythonVersion::from_str(python_version).expect("Tests must use valid Python versions"); + let mut filters = Vec::new(); filters.extend( Self::path_patterns(&cache_dir) @@ -123,6 +130,18 @@ impl TestContext { // Destroy any remaining UNC prefixes (Windows only) filters.push((r"\\\\\?\\".to_string(), String::new())); + // Add Python patch version filtering unless explicitly requested to ensure + // snapshots are patch version agnostic when it is not a part of the test. + if python_version.patch().is_none() { + filters.push(( + format!( + r"({})\.\d+", + regex::escape(python_version.to_string().as_str()) + ), + "$1.[X]".to_string(), + )); + } + Self { temp_dir, cache_dir, @@ -139,14 +158,25 @@ impl TestContext { /// * Set a cutoff for versions used in the resolution so the snapshots don't change after a new release. /// * Set the venv to a fresh `.venv` in `temp_dir`. pub fn compile(&self) -> std::process::Command { + let mut command = self.compile_without_exclude_newer(); + command.arg("--exclude-newer").arg(EXCLUDE_NEWER); + command + } + + /// Create a `pip compile` command with no `--exclude-newer` option. + /// + /// One should avoid using this in tests to the extent possible because + /// it can result in tests failing when the index state changes. Therefore, + /// if you use this, there should be some other kind of mitigation in place. + /// For example, pinning package versions. + pub fn compile_without_exclude_newer(&self) -> std::process::Command { let mut cmd = std::process::Command::new(get_bin()); cmd.arg("pip") .arg("compile") .arg("--cache-dir") .arg(self.cache_dir.path()) - .arg("--exclude-newer") - .arg(EXCLUDE_NEWER) .env("VIRTUAL_ENV", self.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(self.temp_dir.path()); if cfg!(all(windows, debug_assertions)) { @@ -179,6 +209,7 @@ impl TestContext { .arg("--cache-dir") .arg(self.cache_dir.path()) .env("VIRTUAL_ENV", self.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&self.temp_dir); if cfg!(all(windows, debug_assertions)) { @@ -288,81 +319,23 @@ pub fn venv_to_interpreter(venv: &Path) -> PathBuf { } } -/// If bootstrapped python build standalone pythons exists in `/bin`, -/// return the paths to the directories containing the python binaries (i.e. as paths that -/// `which::which_in` can use). -/// -/// Use `scripts/bootstrap/install.py` to bootstrap. -/// -/// Python versions are sorted from newest to oldest. -pub fn bootstrapped_pythons() -> Option> { - // Current dir is `/crates/uv`. - let project_root = std::env::current_dir() - .unwrap() - .parent() - .unwrap() - .parent() - .unwrap() - .to_path_buf(); - let bootstrap_dir = if let Some(bootstrap_dir) = env::var_os("UV_BOOTSTRAP_DIR") { - let bootstrap_dir = PathBuf::from(bootstrap_dir); - if bootstrap_dir.is_absolute() { - bootstrap_dir - } else { - // cargo test changes directory to the test crate, but doesn't tell us from where the user is running the - // tests. We'll assume that it's the project root. - project_root.join(bootstrap_dir) - } - } else { - project_root.join("bin") - }; - let bootstrapped_pythons = bootstrap_dir.join("versions"); - let Ok(bootstrapped_pythons) = fs_err::read_dir(bootstrapped_pythons) else { - return None; - }; - - let mut bootstrapped_pythons: Vec = bootstrapped_pythons - .map(Result::unwrap) - .filter(|entry| entry.metadata().unwrap().is_dir()) - .map(|entry| { - if cfg!(unix) { - entry.path().join("install").join("bin") - } else if cfg!(windows) { - entry.path().join("install") - } else { - unimplemented!("Only Windows and Unix are supported") - } - }) - .collect(); - bootstrapped_pythons.sort(); - // Prefer the most recent patch version. - bootstrapped_pythons.reverse(); - Some(bootstrapped_pythons) -} - /// Create a virtual environment named `.venv` in a temporary directory with the given -/// Python version. Expected format for `python` is "python". +/// Python version. Expected format for `python` is "". pub fn create_venv>( temp_dir: &Parent, cache_dir: &assert_fs::TempDir, python: &str, ) -> PathBuf { - let python = if let Some(bootstrapped_pythons) = bootstrapped_pythons() { - bootstrapped_pythons - .into_iter() - // Good enough since we control the directory - .find(|path| path.to_str().unwrap().contains(&format!("@{python}"))) - .expect("Missing python bootstrap version") - .join(if cfg!(unix) { - "python3" - } else if cfg!(windows) { - "python.exe" - } else { - unimplemented!("Only Windows and Unix are supported") - }) - } else { - PathBuf::from(python) - }; + let python = toolchains_for_version( + &PythonVersion::from_str(python).expect("Tests should use a valid Python version"), + ) + .expect("Tests are run on a supported platform") + .first() + .map(uv_toolchain::Toolchain::executable) + // We'll search for the request Python on the PATH if not found in the toolchain versions + // We hack this into a `PathBuf` to satisfy the compiler but it's just a string + .unwrap_or(PathBuf::from(python)); + let venv = temp_dir.child(".venv"); Command::new(get_bin()) .arg("venv") @@ -386,34 +359,48 @@ pub fn get_bin() -> PathBuf { } /// Create a `PATH` with the requested Python versions available in order. -pub fn create_bin_with_executables( +/// +/// Generally this should be used with `UV_TEST_PYTHON_PATH`. +pub fn python_path_with_versions( temp_dir: &assert_fs::TempDir, python_versions: &[&str], ) -> anyhow::Result { - if let Some(bootstrapped_pythons) = bootstrapped_pythons() { - let selected_pythons = python_versions.iter().flat_map(|python_version| { - bootstrapped_pythons.iter().filter(move |path| { - // Good enough since we control the directory - path.to_str() - .unwrap() - .contains(&format!("@{python_version}")) + let cache = Cache::from_path(temp_dir.child("cache").to_path_buf())?; + let selected_pythons = python_versions + .iter() + .flat_map(|python_version| { + let inner = toolchains_for_version( + &PythonVersion::from_str(python_version) + .expect("Tests should use a valid Python version"), + ) + .expect("Tests are run on a supported platform") + .iter() + .map(|toolchain| { + toolchain + .executable() + .parent() + .expect("Executables must exist in a directory") + .to_path_buf() }) - }); - return Ok(env::join_paths(selected_pythons)?); - } + .collect::>(); + if inner.is_empty() { + // Fallback to a system lookup if we failed to find one in the toolchain directory + if let Some(interpreter) = find_requested_python(python_version, &cache).unwrap() { + vec![interpreter + .sys_executable() + .parent() + .expect("Python executable should always be in a directory") + .to_path_buf()] + } else { + panic!("Could not find Python {python_version} for test"); + } + } else { + inner + } + }) + .collect::>(); - let bin = temp_dir.child("bin"); - fs_err::create_dir(&bin)?; - for &request in python_versions { - let interpreter = find_requested_python(request, &Cache::temp().unwrap())? - .ok_or(uv_interpreter::Error::NoSuchPython(request.to_string()))?; - let name = interpreter - .sys_executable() - .file_name() - .expect("Discovered executable must have a filename"); - symlink_file(interpreter.sys_executable(), bin.child(name))?; - } - Ok(bin.canonicalize()?.into()) + Ok(env::join_paths(selected_pythons)?) } /// Execute the command and format its output status, stdout and stderr into a snapshot string. @@ -422,6 +409,7 @@ pub fn create_bin_with_executables( pub fn run_and_format>( mut command: impl BorrowMut, filters: impl AsRef<[(T, T)]>, + function_name: &str, windows_filters: bool, ) -> (String, Output) { let program = command @@ -429,6 +417,19 @@ pub fn run_and_format>( .get_program() .to_string_lossy() .to_string(); + + // Support profiling test run commands with traces. + if let Ok(root) = env::var("TRACING_DURATIONS_TEST_ROOT") { + assert!( + cfg!(feature = "tracing-durations-export"), + "You need to enable the tracing-durations-export feature to use `TRACING_DURATIONS_TEST_ROOT`" + ); + command.borrow_mut().env( + "TRACING_DURATIONS_FILE", + Path::new(&root).join(function_name).with_extension("jsonl"), + ); + } + let output = command .borrow_mut() .output() @@ -499,6 +500,25 @@ pub fn copy_dir_all(src: impl AsRef, dst: impl AsRef) -> std::io::Re Ok(()) } +/// Utility macro to return the name of the current function. +/// +/// https://stackoverflow.com/a/40234666/3549270 +#[doc(hidden)] +#[macro_export] +macro_rules! function_name { + () => {{ + fn f() {} + fn type_name_of_val(_: T) -> &'static str { + std::any::type_name::() + } + let mut name = type_name_of_val(f).strip_suffix("::f").unwrap_or(""); + while let Some(rest) = name.strip_suffix("::{{closure}}") { + name = rest; + } + name + }}; +} + /// Run [`assert_cmd_snapshot!`], with default filters or with custom filters. /// /// By default, the filters will search for the generally windows-only deps colorama and tzdata, @@ -510,13 +530,13 @@ macro_rules! uv_snapshot { }}; ($filters:expr, $spawnable:expr, @$snapshot:literal) => {{ // Take a reference for backwards compatibility with the vec-expecting insta filters. - let (snapshot, output) = $crate::common::run_and_format($spawnable, &$filters, true); + let (snapshot, output) = $crate::common::run_and_format($spawnable, &$filters, function_name!(), true); ::insta::assert_snapshot!(snapshot, @$snapshot); output }}; ($filters:expr, windows_filters=false, $spawnable:expr, @$snapshot:literal) => {{ // Take a reference for backwards compatibility with the vec-expecting insta filters. - let (snapshot, output) = $crate::common::run_and_format($spawnable, &$filters, false); + let (snapshot, output) = $crate::common::run_and_format($spawnable, &$filters, function_name!(), false); ::insta::assert_snapshot!(snapshot, @$snapshot); output }}; diff --git a/crates/uv/tests/pip_check.rs b/crates/uv/tests/pip_check.rs index 3839b901f..28e2aecdd 100644 --- a/crates/uv/tests/pip_check.rs +++ b/crates/uv/tests/pip_check.rs @@ -1,8 +1,8 @@ use std::process::Command; use anyhow::Result; +use assert_fs::fixture::FileWriteStr; use assert_fs::fixture::PathChild; -use assert_fs::fixture::{FileTouch, FileWriteStr}; use common::uv_snapshot; @@ -21,6 +21,7 @@ fn install_command(context: &TestContext) -> Command { .arg("--exclude-newer") .arg(EXCLUDE_NEWER) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir); if cfg!(all(windows, debug_assertions)) { @@ -41,6 +42,7 @@ fn check_command(context: &TestContext) -> Command { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir); command @@ -51,7 +53,6 @@ fn check_compatible_packages() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("requests==2.31.0")?; uv_snapshot!(install_command(&context) @@ -95,7 +96,6 @@ fn check_incompatible_packages() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("requests==2.31.0")?; uv_snapshot!(install_command(&context) @@ -119,7 +119,6 @@ fn check_incompatible_packages() -> Result<()> { ); let requirements_txt_idna = context.temp_dir.child("requirements_idna.txt"); - requirements_txt_idna.touch()?; requirements_txt_idna.write_str("idna==2.4")?; uv_snapshot!(install_command(&context) @@ -163,7 +162,6 @@ fn check_multiple_incompatible_packages() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("requests==2.31.0")?; uv_snapshot!(install_command(&context) @@ -187,7 +185,6 @@ fn check_multiple_incompatible_packages() -> Result<()> { ); let requirements_txt_two = context.temp_dir.child("requirements_two.txt"); - requirements_txt_two.touch()?; requirements_txt_two.write_str("idna==2.4\nurllib3==1.20")?; uv_snapshot!(install_command(&context) diff --git a/crates/uv/tests/pip_compile.rs b/crates/uv/tests/pip_compile.rs index e86c8a971..22a04aeb6 100644 --- a/crates/uv/tests/pip_compile.rs +++ b/crates/uv/tests/pip_compile.rs @@ -419,6 +419,10 @@ authors = ["Astral Software Inc. "] [tool.poetry.dependencies] python = "^3.10" anyio = "^3" +pytest = { version = "*", optional = true } + +[tool.poetry.extras] +test = ["pytest"] [build-system] requires = ["poetry-core"] @@ -427,20 +431,29 @@ build-backend = "poetry.core.masonry.api" )?; uv_snapshot!(context.compile() - .arg("pyproject.toml"), @r###" + .arg("pyproject.toml") + .arg("--extra") + .arg("test"), @r###" success: true exit_code: 0 ----- stdout ----- # This file was autogenerated by uv via the following command: - # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z pyproject.toml + # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z pyproject.toml --extra test anyio==3.7.1 idna==3.6 # via anyio + iniconfig==2.0.0 + # via pytest + packaging==24.0 + # via pytest + pluggy==1.4.0 + # via pytest + pytest==8.1.1 sniffio==1.3.1 # via anyio ----- stderr ----- - Resolved 3 packages in [TIME] + Resolved 7 packages in [TIME] "### ); @@ -869,11 +882,8 @@ fn compile_python_37() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because the requested Python version (3.7) does not satisfy Python>=3.8 - and black==23.10.1 depends on Python>=3.8, we can conclude that - black==23.10.1 cannot be used. - And because you require black==23.10.1, we can conclude that the - requirements are unsatisfiable. + ╰─▶ Because the requested Python version (3.7) does not satisfy Python>=3.8 and black==23.10.1 depends on Python>=3.8, we can conclude that black==23.10.1 cannot be used. + And because you require black==23.10.1, we can conclude that the requirements are unsatisfiable. "###); Ok(()) @@ -1066,13 +1076,15 @@ fn compile_sdist_url_dependency() -> Result<()> { Ok(()) } -/// Resolve a specific Flask source distribution via a Git HTTPS dependency. +/// Resolve a specific source distribution via a Git HTTPS dependency. #[test] #[cfg(feature = "git")] fn compile_git_https_dependency() -> Result<()> { let context = TestContext::new("3.12"); let requirements_in = context.temp_dir.child("requirements.in"); - requirements_in.write_str("flask @ git+https://github.com/pallets/flask.git")?; + requirements_in.write_str( + "uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage", + )?; // In addition to the standard filters, remove the `main` commit, which will change frequently. let filters: Vec<_> = [(r"@(\d|\w){40}", "@[COMMIT]")] @@ -1087,113 +1099,23 @@ fn compile_git_https_dependency() -> Result<()> { ----- stdout ----- # This file was autogenerated by uv via the following command: # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in - blinker==1.7.0 - # via flask - click==8.1.7 - # via flask - flask @ git+https://github.com/pallets/flask.git@[COMMIT] - itsdangerous==2.1.2 - # via flask - jinja2==3.1.3 - # via flask - markupsafe==2.1.5 - # via - # jinja2 - # werkzeug - werkzeug==3.0.1 - # via flask + uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage@[COMMIT] ----- stderr ----- - Resolved 7 packages in [TIME] + Resolved 1 package in [TIME] "###); Ok(()) } -/// Resolve a specific Flask branch via a Git HTTPS dependency. +/// Resolve a specific branch via a Git HTTPS dependency. #[test] #[cfg(feature = "git")] fn compile_git_branch_https_dependency() -> Result<()> { - let context = TestContext::new("3.12"); - let requirements_in = context.temp_dir.child("requirements.in"); - requirements_in.write_str("flask @ git+https://github.com/pallets/flask.git@1.0.x")?; - - uv_snapshot!(context.compile() - .arg("requirements.in"), @r###" - success: true - exit_code: 0 - ----- stdout ----- - # This file was autogenerated by uv via the following command: - # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in - click==8.1.7 - # via flask - flask @ git+https://github.com/pallets/flask.git@d92b64aa275841b0c9aea3903aba72fbc4275d91 - itsdangerous==2.1.2 - # via flask - jinja2==3.1.3 - # via flask - markupsafe==2.1.5 - # via - # jinja2 - # werkzeug - werkzeug==3.0.1 - # via flask - - ----- stderr ----- - Resolved 6 packages in [TIME] - "### - ); - - Ok(()) -} - -/// Resolve a specific Flask tag via a Git HTTPS dependency. -#[test] -#[cfg(feature = "git")] -fn compile_git_tag_https_dependency() -> Result<()> { - let context = TestContext::new("3.12"); - let requirements_in = context.temp_dir.child("requirements.in"); - requirements_in.write_str("flask @ git+https://github.com/pallets/flask.git@3.0.0")?; - - uv_snapshot!(context.compile() - .arg("requirements.in"), @r###" - success: true - exit_code: 0 - ----- stdout ----- - # This file was autogenerated by uv via the following command: - # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in - blinker==1.7.0 - # via flask - click==8.1.7 - # via flask - flask @ git+https://github.com/pallets/flask.git@735a4701d6d5e848241e7d7535db898efb62d400 - itsdangerous==2.1.2 - # via flask - jinja2==3.1.3 - # via flask - markupsafe==2.1.5 - # via - # jinja2 - # werkzeug - werkzeug==3.0.1 - # via flask - - ----- stderr ----- - Resolved 7 packages in [TIME] - "### - ); - - Ok(()) -} - -/// Resolve a specific Flask commit via a Git HTTPS dependency. -#[test] -#[cfg(feature = "git")] -fn compile_git_long_commit_https_dependency() -> Result<()> { let context = TestContext::new("3.12"); let requirements_in = context.temp_dir.child("requirements.in"); requirements_in.write_str( - "flask @ git+https://github.com/pallets/flask.git@d92b64aa275841b0c9aea3903aba72fbc4275d91", + "uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage@test-branch", )?; uv_snapshot!(context.compile() @@ -1203,35 +1125,25 @@ fn compile_git_long_commit_https_dependency() -> Result<()> { ----- stdout ----- # This file was autogenerated by uv via the following command: # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in - click==8.1.7 - # via flask - flask @ git+https://github.com/pallets/flask.git@d92b64aa275841b0c9aea3903aba72fbc4275d91 - itsdangerous==2.1.2 - # via flask - jinja2==3.1.3 - # via flask - markupsafe==2.1.5 - # via - # jinja2 - # werkzeug - werkzeug==3.0.1 - # via flask + uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage@0dacfd662c64cb4ceb16e6cf65a157a8b715b979 ----- stderr ----- - Resolved 6 packages in [TIME] + Resolved 1 package in [TIME] "### ); Ok(()) } -/// Resolve a specific Flask commit via a Git HTTPS dependency. +/// Resolve a specific tag via a Git HTTPS dependency. #[test] #[cfg(feature = "git")] -fn compile_git_short_commit_https_dependency() -> Result<()> { +fn compile_git_tag_https_dependency() -> Result<()> { let context = TestContext::new("3.12"); let requirements_in = context.temp_dir.child("requirements.in"); - requirements_in.write_str("flask @ git+https://github.com/pallets/flask.git@d92b64a")?; + requirements_in.write_str( + "uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage@test-tag", + )?; uv_snapshot!(context.compile() .arg("requirements.in"), @r###" @@ -1240,36 +1152,107 @@ fn compile_git_short_commit_https_dependency() -> Result<()> { ----- stdout ----- # This file was autogenerated by uv via the following command: # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in - click==8.1.7 - # via flask - flask @ git+https://github.com/pallets/flask.git@d92b64aa275841b0c9aea3903aba72fbc4275d91 - itsdangerous==2.1.2 - # via flask - jinja2==3.1.3 - # via flask - markupsafe==2.1.5 - # via - # jinja2 - # werkzeug - werkzeug==3.0.1 - # via flask + uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage@0dacfd662c64cb4ceb16e6cf65a157a8b715b979 ----- stderr ----- - Resolved 6 packages in [TIME] + Resolved 1 package in [TIME] "### ); Ok(()) } -/// Resolve a specific Flask ref via a Git HTTPS dependency. +/// Resolve a specific tag via a Git HTTPS dependency. +/// +/// In this case, the tag is a date, and thus could feasibly refer to a short commit hash. +#[test] +#[cfg(feature = "git")] +fn compile_git_date_tag_https_dependency() -> Result<()> { + let context = TestContext::new("3.12"); + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str( + "uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage@20240402", + )?; + + uv_snapshot!(context.compile() + .arg("requirements.in"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in + uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage@0dacfd662c64cb4ceb16e6cf65a157a8b715b979 + + ----- stderr ----- + Resolved 1 package in [TIME] + "### + ); + + Ok(()) +} + +/// Resolve a specific commit via a Git HTTPS dependency. +#[test] +#[cfg(feature = "git")] +fn compile_git_long_commit_https_dependency() -> Result<()> { + let context = TestContext::new("3.12"); + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str( + "uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage@0dacfd662c64cb4ceb16e6cf65a157a8b715b979", + )?; + + uv_snapshot!(context.compile() + .arg("requirements.in"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in + uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage@0dacfd662c64cb4ceb16e6cf65a157a8b715b979 + + ----- stderr ----- + Resolved 1 package in [TIME] + "### + ); + + Ok(()) +} + +/// Resolve a specific commit via a Git HTTPS dependency. +#[test] +#[cfg(feature = "git")] +fn compile_git_short_commit_https_dependency() -> Result<()> { + let context = TestContext::new("3.12"); + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str( + "uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage@0dacfd6", + )?; + + uv_snapshot!(context.compile() + .arg("requirements.in"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in + uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage@0dacfd662c64cb4ceb16e6cf65a157a8b715b979 + + ----- stderr ----- + Resolved 1 package in [TIME] + "### + ); + + Ok(()) +} + +/// Resolve a specific ref via a Git HTTPS dependency. #[test] #[cfg(feature = "git")] fn compile_git_refs_https_dependency() -> Result<()> { let context = TestContext::new("3.12"); let requirements_in = context.temp_dir.child("requirements.in"); requirements_in - .write_str("flask @ git+https://github.com/pallets/flask.git@refs/pull/5313/head")?; + .write_str("uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage@refs/pull/4/head")?; uv_snapshot!(context.compile() .arg("requirements.in"), @r###" @@ -1278,24 +1261,10 @@ fn compile_git_refs_https_dependency() -> Result<()> { ----- stdout ----- # This file was autogenerated by uv via the following command: # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in - blinker==1.7.0 - # via flask - click==8.1.7 - # via flask - flask @ git+https://github.com/pallets/flask.git@7af0271f4703a71beef8e26d1f5f6f8da04100e6 - itsdangerous==2.1.2 - # via flask - jinja2==3.1.3 - # via flask - markupsafe==2.1.5 - # via - # jinja2 - # werkzeug - werkzeug==3.0.1 - # via flask + uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage@9d01a806f17ddacb9c7b66b1b68574adf790b63f ----- stderr ----- - Resolved 7 packages in [TIME] + Resolved 1 package in [TIME] "### ); @@ -1486,9 +1455,7 @@ fn conflicting_direct_url_dependency() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because there is no version of werkzeug==3.0.0 and you require - werkzeug==3.0.0, we can conclude that the requirements are - unsatisfiable. + ╰─▶ Because there is no version of werkzeug==3.0.0 and you require werkzeug==3.0.0, we can conclude that the requirements are unsatisfiable. "### ); @@ -1612,10 +1579,8 @@ fn conflicting_transitive_url_dependency() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because only werkzeug<3.0.0 is available and flask==3.0.0 depends on - werkzeug>=3.0.0, we can conclude that flask==3.0.0 cannot be used. - And because you require flask==3.0.0, we can conclude that the - requirements are unsatisfiable. + ╰─▶ Because only werkzeug<3.0.0 is available and flask==3.0.0 depends on werkzeug>=3.0.0, we can conclude that flask==3.0.0 cannot be used. + And because you require flask==3.0.0, we can conclude that the requirements are unsatisfiable. "### ); @@ -1964,8 +1929,7 @@ fn requirement_constraint_override_url() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because there is no version of anyio==3.7.0 and you require - anyio==3.7.0, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because there is no version of anyio==3.7.0 and you require anyio==3.7.0, we can conclude that the requirements are unsatisfiable. "### ); @@ -2177,8 +2141,7 @@ dependencies = ["anyio==3.7.0", "anyio==4.0.0"] ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because my-project depends on anyio==3.7.0 and my-project depends on - anyio==4.0.0, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because my-project depends on anyio==3.7.0 and my-project depends on anyio==4.0.0, we can conclude that the requirements are unsatisfiable. "### ); @@ -2209,8 +2172,7 @@ dependencies = ["anyio==300.1.4"] ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because there is no version of anyio==300.1.4 and my-project depends on - anyio==300.1.4, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because there is no version of anyio==300.1.4 and my-project depends on anyio==300.1.4, we can conclude that the requirements are unsatisfiable. "### ); @@ -2235,6 +2197,7 @@ fn compile_exclude_newer() -> Result<()> { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(context.temp_dir.path()), @r###" success: true exit_code: 0 @@ -2259,6 +2222,7 @@ fn compile_exclude_newer() -> Result<()> { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(context.temp_dir.path()), @r###" success: true exit_code: 0 @@ -2282,6 +2246,7 @@ fn compile_exclude_newer() -> Result<()> { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(context.temp_dir.path()), @r###" success: false exit_code: 2 @@ -2656,11 +2621,8 @@ fn compile_yanked_version_indirect() -> Result<()> { attrs<=20.3.0 attrs==21.1.0 attrs>=21.2.0 - and attrs==21.1.0 is unusable because it was yanked (reason: - Installable but not importable on Python 3.4), we can conclude that - attrs>20.3.0,<21.2.0 cannot be used. - And because you require attrs>20.3.0,<21.2.0, we can conclude that the - requirements are unsatisfiable. + and attrs==21.1.0 is unusable because it was yanked (reason: Installable but not importable on Python 3.4), we can conclude that attrs>20.3.0,<21.2.0 cannot be used. + And because you require attrs>20.3.0,<21.2.0, we can conclude that the requirements are unsatisfiable. "### ); @@ -2755,6 +2717,90 @@ fn override_multi_dependency() -> Result<()> { Ok(()) } +/// Flask==3.0.0 depends on Werkzeug>=3.0.0. Demonstrate that we can override this +/// requirement with a URL. +#[test] +fn override_dependency_url() -> Result<()> { + let context = TestContext::new("3.12"); + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("flask==3.0.0")?; + + let overrides_txt = context.temp_dir.child("overrides.txt"); + overrides_txt.write_str("werkzeug @ https://files.pythonhosted.org/packages/cc/94/5f7079a0e00bd6863ef8f1da638721e9da21e5bacee597595b318f71d62e/Werkzeug-1.0.1-py2.py3-none-any.whl")?; + + uv_snapshot!(context.compile() + .arg("requirements.in") + .arg("--override") + .arg("overrides.txt"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in --override overrides.txt + blinker==1.7.0 + # via flask + click==8.1.7 + # via flask + flask==3.0.0 + itsdangerous==2.1.2 + # via flask + jinja2==3.1.3 + # via flask + markupsafe==2.1.5 + # via jinja2 + werkzeug @ https://files.pythonhosted.org/packages/cc/94/5f7079a0e00bd6863ef8f1da638721e9da21e5bacee597595b318f71d62e/Werkzeug-1.0.1-py2.py3-none-any.whl + # via flask + + ----- stderr ----- + Resolved 7 packages in [TIME] + "### + ); + + Ok(()) +} + +/// Flask==3.0.0 depends on Werkzeug>=3.0.0. Demonstrate that we can override this +/// requirement with an unnamed URL. +#[test] +fn override_dependency_unnamed_url() -> Result<()> { + let context = TestContext::new("3.12"); + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("flask==3.0.0")?; + + let overrides_txt = context.temp_dir.child("overrides.txt"); + overrides_txt.write_str("https://files.pythonhosted.org/packages/cc/94/5f7079a0e00bd6863ef8f1da638721e9da21e5bacee597595b318f71d62e/Werkzeug-1.0.1-py2.py3-none-any.whl")?; + + uv_snapshot!(context.compile() + .arg("requirements.in") + .arg("--override") + .arg("overrides.txt"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in --override overrides.txt + blinker==1.7.0 + # via flask + click==8.1.7 + # via flask + flask==3.0.0 + itsdangerous==2.1.2 + # via flask + jinja2==3.1.3 + # via flask + markupsafe==2.1.5 + # via jinja2 + werkzeug @ https://files.pythonhosted.org/packages/cc/94/5f7079a0e00bd6863ef8f1da638721e9da21e5bacee597595b318f71d62e/Werkzeug-1.0.1-py2.py3-none-any.whl + # via flask + + ----- stderr ----- + Resolved 7 packages in [TIME] + "### + ); + + Ok(()) +} + /// Request an extra that doesn't exist on the specified package. #[test] fn missing_registry_extra() -> Result<()> { @@ -3124,6 +3170,56 @@ fn compile_editable() -> Result<()> { Ok(()) } +/// If an editable is repeated, it should only be built once. +#[test] +fn deduplicate_editable() -> Result<()> { + let context = TestContext::new("3.12"); + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str(indoc! {r" + -e file://../../scripts/packages/black_editable + -e ${PROJECT_ROOT}/../../scripts/packages/black_editable + -e file://../../scripts/packages/black_editable[dev] + " + })?; + + uv_snapshot!(context.filters(), context.compile() + .arg(requirements_in.path()) + .current_dir(current_dir()?), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z [TEMP_DIR]/requirements.in + -e file://../../scripts/packages/black_editable + aiohttp==3.9.3 + # via black + aiosignal==1.3.1 + # via aiohttp + attrs==23.2.0 + # via aiohttp + frozenlist==1.4.1 + # via + # aiohttp + # aiosignal + idna==3.6 + # via yarl + multidict==6.0.5 + # via + # aiohttp + # yarl + uvloop==0.19.0 + # via black + yarl==1.9.4 + # via aiohttp + + ----- stderr ----- + Built 1 editable in [TIME] + Resolved 9 packages in [TIME] + "###); + + Ok(()) +} + #[test] fn recursive_extras_direct_url() -> Result<()> { let context = TestContext::new("3.12"); @@ -3309,6 +3405,7 @@ fn compile_html() -> Result<()> { .arg("--index-url") .arg("https://download.pytorch.org/whl") .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(context.temp_dir.path()), @r###" success: true exit_code: 0 @@ -3434,33 +3531,14 @@ fn compile_legacy_sdist_setuptools() -> Result<()> { Ok(()) } -/// Include hashes in the generated output. +/// Include hashes from the registry in the generated output. #[test] -fn generate_hashes() -> Result<()> { +fn generate_hashes_registry() -> Result<()> { let context = TestContext::new("3.12"); let requirements_in = context.temp_dir.child("requirements.in"); - requirements_in.write_str("flask==3.0.0")?; + requirements_in.write_str("anyio==4.0.0")?; - let colorama_locked = regex::escape(indoc! {r" - colorama==0.4.6 \ - --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ - --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 - # via click - "}); - let filters: Vec<_> = if cfg!(windows) { - // Remove colorama - vec![ - (colorama_locked.as_str(), ""), - ("Resolved 8 packages", "Resolved 7 packages"), - ] - } else { - vec![] - } - .into_iter() - .chain(context.filters()) - .collect(); - - uv_snapshot!(filters, context.compile() + uv_snapshot!(context.compile() .arg("requirements.in") .arg("--generate-hashes"), @r###" success: true @@ -3468,102 +3546,246 @@ fn generate_hashes() -> Result<()> { ----- stdout ----- # This file was autogenerated by uv via the following command: # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in --generate-hashes - blinker==1.7.0 \ - --hash=sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9 \ - --hash=sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182 - # via flask - click==8.1.7 \ - --hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \ - --hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de - # via flask - flask==3.0.0 \ - --hash=sha256:21128f47e4e3b9d597a3e8521a329bf56909b690fcc3fa3e477725aa81367638 \ - --hash=sha256:cfadcdb638b609361d29ec22360d6070a77d7463dcb3ab08d2c2f2f168845f58 - itsdangerous==2.1.2 \ - --hash=sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44 \ - --hash=sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a - # via flask - jinja2==3.1.3 \ - --hash=sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa \ - --hash=sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90 - # via flask - markupsafe==2.1.5 \ - --hash=sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf \ - --hash=sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff \ - --hash=sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f \ - --hash=sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3 \ - --hash=sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532 \ - --hash=sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f \ - --hash=sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617 \ - --hash=sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df \ - --hash=sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4 \ - --hash=sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906 \ - --hash=sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f \ - --hash=sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4 \ - --hash=sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8 \ - --hash=sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371 \ - --hash=sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2 \ - --hash=sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465 \ - --hash=sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52 \ - --hash=sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6 \ - --hash=sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169 \ - --hash=sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad \ - --hash=sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2 \ - --hash=sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0 \ - --hash=sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029 \ - --hash=sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f \ - --hash=sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a \ - --hash=sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced \ - --hash=sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5 \ - --hash=sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c \ - --hash=sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf \ - --hash=sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9 \ - --hash=sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb \ - --hash=sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad \ - --hash=sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3 \ - --hash=sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1 \ - --hash=sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46 \ - --hash=sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc \ - --hash=sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a \ - --hash=sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee \ - --hash=sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900 \ - --hash=sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5 \ - --hash=sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea \ - --hash=sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f \ - --hash=sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5 \ - --hash=sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e \ - --hash=sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a \ - --hash=sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f \ - --hash=sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50 \ - --hash=sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a \ - --hash=sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b \ - --hash=sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4 \ - --hash=sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff \ - --hash=sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2 \ - --hash=sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46 \ - --hash=sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b \ - --hash=sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf \ - --hash=sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5 \ - --hash=sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5 \ - --hash=sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab \ - --hash=sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd \ - --hash=sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68 - # via - # jinja2 - # werkzeug - werkzeug==3.0.1 \ - --hash=sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc \ - --hash=sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10 - # via flask + anyio==4.0.0 \ + --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f \ + --hash=sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a + idna==3.6 \ + --hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \ + --hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f + # via anyio + sniffio==1.3.1 \ + --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \ + --hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc + # via anyio ----- stderr ----- - Resolved 7 packages in [TIME] + Resolved 3 packages in [TIME] "### ); Ok(()) } +/// Include hashes from the URL in the generated output. +#[test] +fn generate_hashes_source_distribution_url() -> Result<()> { + let context = TestContext::new("3.12"); + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("anyio @ https://files.pythonhosted.org/packages/2d/b8/7333d87d5f03247215d86a86362fd3e324111788c6cdd8d2e6196a6ba833/anyio-4.2.0.tar.gz")?; + + uv_snapshot!(context.compile() + .arg("requirements.in") + .arg("--generate-hashes"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in --generate-hashes + anyio @ https://files.pythonhosted.org/packages/2d/b8/7333d87d5f03247215d86a86362fd3e324111788c6cdd8d2e6196a6ba833/anyio-4.2.0.tar.gz \ + --hash=sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f + idna==3.6 \ + --hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \ + --hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f + # via anyio + sniffio==1.3.1 \ + --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \ + --hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc + # via anyio + + ----- stderr ----- + Resolved 3 packages in [TIME] + "### + ); + + Ok(()) +} + +/// Include hashes from the URL in the generated output. +#[test] +fn generate_hashes_built_distribution_url() -> Result<()> { + let context = TestContext::new("3.12"); + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("anyio @ https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl")?; + + uv_snapshot!(context.compile() + .arg("requirements.in") + .arg("--generate-hashes"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in --generate-hashes + anyio @ https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl \ + --hash=sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8 + idna==3.6 \ + --hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \ + --hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f + # via anyio + sniffio==1.3.1 \ + --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \ + --hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc + # via anyio + + ----- stderr ----- + Resolved 3 packages in [TIME] + "### + ); + + Ok(()) +} + +/// Given a VCS dependency, include hashes for its dependencies, but not the repository itself. +#[test] +fn generate_hashes_git() -> Result<()> { + let context = TestContext::new("3.12"); + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("anyio @ git+https://github.com/agronholm/anyio@4.3.0")?; + + uv_snapshot!(context.compile() + .arg("requirements.in") + .arg("--generate-hashes"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in --generate-hashes + anyio @ git+https://github.com/agronholm/anyio@437a7e310925a962cab4a58fcd2455fbcd578d51 + idna==3.6 \ + --hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \ + --hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f + # via anyio + sniffio==1.3.1 \ + --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \ + --hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc + # via anyio + + ----- stderr ----- + Resolved 3 packages in [TIME] + "### + ); + + Ok(()) +} + +/// Given an unnamed URL, include hashes for the URL and its dependencies. +#[test] +fn generate_hashes_unnamed_url() -> Result<()> { + let context = TestContext::new("3.12"); + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl")?; + + uv_snapshot!(context.compile() + .arg("requirements.in") + .arg("--generate-hashes"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in --generate-hashes + anyio @ https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl \ + --hash=sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8 + idna==3.6 \ + --hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \ + --hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f + # via anyio + sniffio==1.3.1 \ + --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \ + --hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc + # via anyio + + ----- stderr ----- + Resolved 3 packages in [TIME] + "### + ); + + Ok(()) +} + +/// Given a local directory, include hashes for its dependencies, but not the directory itself. +#[test] +fn generate_hashes_local_directory() -> Result<()> { + let _context = TestContext::new("3.12"); + + let context = TestContext::new("3.12"); + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str(indoc! {r" + ../../scripts/packages/poetry_editable + " + })?; + + uv_snapshot!(context.filters(), context.compile() + .arg(requirements_in.path()) + .arg("--generate-hashes") + .current_dir(current_dir()?), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z [TEMP_DIR]/requirements.in --generate-hashes + anyio==4.3.0 \ + --hash=sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8 \ + --hash=sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6 + # via poetry-editable + idna==3.6 \ + --hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \ + --hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f + # via anyio + poetry-editable @ ../../scripts/packages/poetry_editable + sniffio==1.3.1 \ + --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \ + --hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc + # via anyio + + ----- stderr ----- + Resolved 4 packages in [TIME] + "###); + + Ok(()) +} + +/// Given an editable dependency, include hashes for its dependencies, but not the directory itself. +#[test] +fn generate_hashes_editable() -> Result<()> { + let _context = TestContext::new("3.12"); + + let context = TestContext::new("3.12"); + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str(indoc! {r" + -e ../../scripts/packages/poetry_editable + " + })?; + + uv_snapshot!(context.filters(), context.compile() + .arg(requirements_in.path()) + .arg("--generate-hashes") + .current_dir(current_dir()?), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z [TEMP_DIR]/requirements.in --generate-hashes + -e ../../scripts/packages/poetry_editable + anyio==4.3.0 \ + --hash=sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8 \ + --hash=sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6 + # via poetry-editable + idna==3.6 \ + --hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \ + --hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f + # via anyio + sniffio==1.3.1 \ + --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \ + --hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc + # via anyio + + ----- stderr ----- + Built 1 editable in [TIME] + Resolved 4 packages in [TIME] + "###); + + Ok(()) +} + /// Compile using `--find-links` with a local directory. #[test] fn find_links_directory() -> Result<()> { @@ -3578,7 +3800,7 @@ fn find_links_directory() -> Result<()> { uv_snapshot!(context.filters(), context.compile() .arg("requirements.in") .arg("--find-links") - .arg(context.workspace_root.join("scripts").join("wheels")), @r###" + .arg(context.workspace_root.join("scripts").join("links")), @r###" success: true exit_code: 0 ----- stdout ----- @@ -4203,12 +4425,9 @@ fn no_index_requirements_txt() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because tqdm was not found in the provided package locations and you - require tqdm, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because tqdm was not found in the provided package locations and you require tqdm, we can conclude that the requirements are unsatisfiable. - hint: Packages were unavailable because index lookups were disabled - and no additional package locations were provided (try: `--find-links - `) + hint: Packages were unavailable because index lookups were disabled and no additional package locations were provided (try: `--find-links `) "### ); @@ -4297,9 +4516,9 @@ fn matching_index_urls_requirements_txt() -> Result<()> { Ok(()) } -/// Resolve without network access via the `--offline` flag. +/// Resolve a registry package without network access via the `--offline` flag. #[test] -fn offline() -> Result<()> { +fn offline_registry() -> Result<()> { let context = TestContext::new("3.12"); let requirements_in = context.temp_dir.child("requirements.in"); requirements_in.write_str("black==23.10.1")?; @@ -4314,8 +4533,7 @@ fn offline() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because black==23.10.1 was not found in the cache and you require - black==23.10.1, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because black==23.10.1 was not found in the cache and you require black==23.10.1, we can conclude that the requirements are unsatisfiable. hint: Packages were unavailable because the network was disabled "### @@ -4375,8 +4593,53 @@ fn offline() -> Result<()> { Ok(()) } -/// Resolve without network access via the `--offline` flag, using `--find-links` for an HTML -/// registry. +/// Resolve a registry package without network access via the `--offline` flag. We should backtrack +/// to the latest version of the package that's available in the cache. +#[test] +fn offline_registry_backtrack() -> Result<()> { + let context = TestContext::new("3.12"); + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("iniconfig==1.1.1")?; + + // Populate the cache. + uv_snapshot!(context.compile() + .arg("requirements.in"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in + iniconfig==1.1.1 + + ----- stderr ----- + Resolved 1 package in [TIME] + "### + ); + + // Resolve with `--offline`, with a looser requirement. We should backtrack to `1.1.1`. + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("iniconfig")?; + + uv_snapshot!(context.compile() + .arg("requirements.in") + .arg("--offline"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in --offline + iniconfig==1.1.1 + + ----- stderr ----- + Resolved 1 package in [TIME] + "### + ); + + Ok(()) +} + +/// Resolve a package without network access via the `--offline` flag, using `--find-links` for an +/// HTML registry. #[test] fn offline_find_links() -> Result<()> { let context = TestContext::new("3.12"); @@ -4397,8 +4660,7 @@ fn offline_find_links() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because tqdm was not found in the cache and you require tqdm, we can - conclude that the requirements are unsatisfiable. + ╰─▶ Because tqdm was not found in the cache and you require tqdm, we can conclude that the requirements are unsatisfiable. hint: Packages were unavailable because the network was disabled "### @@ -4417,8 +4679,7 @@ fn offline_find_links() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because tqdm was not found in the cache and you require tqdm, we can - conclude that the requirements are unsatisfiable. + ╰─▶ Because tqdm was not found in the cache and you require tqdm, we can conclude that the requirements are unsatisfiable. hint: Packages were unavailable because the network was disabled "### @@ -4427,6 +4688,152 @@ fn offline_find_links() -> Result<()> { Ok(()) } +/// Resolve a direct URL package without network access via the `--offline` flag. +#[test] +fn offline_direct_url() -> Result<()> { + let context = TestContext::new("3.12"); + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("iniconfig @ https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl")?; + + // Resolve with `--offline` with an empty cache. + uv_snapshot!(context.compile() + .arg("requirements.in") + .arg("--offline"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Failed to download: iniconfig @ https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl + Caused by: Network connectivity is disabled, but the requested data wasn't found in the cache for: `https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl` + "### + ); + + // Populate the cache. + uv_snapshot!(context.compile() + .arg("requirements.in"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in + iniconfig @ https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl + + ----- stderr ----- + Resolved 1 package in [TIME] + "### + ); + + // Resolve with `--offline` with a populated cache. + uv_snapshot!(context.compile() + .arg("requirements.in") + .arg("--offline"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in --offline + iniconfig @ https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl + + ----- stderr ----- + Resolved 1 package in [TIME] + "### + ); + + Ok(()) +} + +/// Resolve a package with invalid metadata, by way of an invalid `Requires-Python` field in the +/// `METADATA` file. +#[test] +fn invalid_metadata_requires_python() -> Result<()> { + let context = TestContext::new("3.12"); + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("validation==2.0.0")?; + + // `2.0.0` has invalid metadata. + uv_snapshot!(context.compile() + .arg("requirements.in") + .arg("--no-index") + .arg("--find-links") + .arg(context.workspace_root.join("scripts").join("links")), @r###" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + × No solution found when resolving dependencies: + ╰─▶ Because validation==2.0.0 is unusable because the package metadata could not be parsed and you require validation==2.0.0, we can conclude that the requirements are unsatisfiable. + + hint: Metadata for validation==2.0.0 could not be parsed: + Failed to parse version: Unexpected end of version specifier, expected operator: + 12 + ^^ + + "### + ); + + Ok(()) +} + +/// Resolve a package with multiple `.dist-info` directories. +#[test] +fn invalid_metadata_multiple_dist_info() -> Result<()> { + let context = TestContext::new("3.12"); + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("validation==3.0.0")?; + + // `3.0.0` has an invalid structure (multiple `.dist-info` directories). + uv_snapshot!(context.compile() + .arg("requirements.in") + .arg("--no-index") + .arg("--find-links") + .arg(context.workspace_root.join("scripts").join("links")), @r###" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + × No solution found when resolving dependencies: + ╰─▶ Because validation==3.0.0 is unusable because the package has an invalid format and you require validation==3.0.0, we can conclude that the requirements are unsatisfiable. + + hint: The structure of validation==3.0.0 was invalid: + Multiple .dist-info directories found: validation-2.0.0, validation-3.0.0 + "### + ); + + Ok(()) +} + +/// Resolve a package, but backtrack past versions with invalid metadata. +#[test] +fn invalid_metadata_backtrack() -> Result<()> { + let context = TestContext::new("3.12"); + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("validation")?; + + // `2.0.0` and `3.0.0` have invalid metadata. We should backtrack to `1.0.0` (the preceding + // version, which has valid metadata). + uv_snapshot!(context.compile() + .arg("requirements.in") + .arg("--no-index") + .arg("--find-links") + .arg(context.workspace_root.join("scripts").join("links")), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in --no-index + validation==1.0.0 + + ----- stderr ----- + Resolved 1 package in [TIME] + "### + ); + + Ok(()) +} + /// Resolve nested `-r` requirements files with relative paths. #[test] fn compile_relative_subfile() -> Result<()> { @@ -4606,8 +5013,7 @@ fn compile_constraints_incompatible_url() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because only anyio>=4 is available and you require anyio<4, we can - conclude that the requirements are unsatisfiable. + ╰─▶ Because only anyio>=4 is available and you require anyio<4, we can conclude that the requirements are unsatisfiable. "### ); @@ -4630,8 +5036,7 @@ fn index_url_in_requirements() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because anyio<4 was not found in the package registry and you require - anyio<4, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because anyio<4 was not found in the package registry and you require anyio<4, we can conclude that the requirements are unsatisfiable. "### ); @@ -4965,8 +5370,7 @@ fn compile_constraints_incompatible_version() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because you require filelock==1.0.0 and you require filelock==3.8.0, we - can conclude that the requirements are unsatisfiable. + ╰─▶ Because you require filelock==1.0.0 and you require filelock==3.8.0, we can conclude that the requirements are unsatisfiable. "### ); @@ -4994,8 +5398,7 @@ fn conflicting_url_markers() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because you require filelock==1.0.0 and you require filelock==3.8.0, we - can conclude that the requirements are unsatisfiable. + ╰─▶ Because you require filelock==1.0.0 and you require filelock==3.8.0, we can conclude that the requirements are unsatisfiable. "### ); @@ -5143,8 +5546,7 @@ fn override_with_incompatible_constraint() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because you require anyio>=3.0.0 and you require anyio<3.0.0, we can - conclude that the requirements are unsatisfiable. + ╰─▶ Because you require anyio>=3.0.0 and you require anyio<3.0.0, we can conclude that the requirements are unsatisfiable. "### ); @@ -5595,14 +5997,14 @@ requires-python = "<=3.8" let requirements_in = context.temp_dir.child("requirements.in"); requirements_in.write_str(&format!("-e {}", editable_dir.path().display()))?; - uv_snapshot!(context.compile() + uv_snapshot!(context.filters(), context.compile() .arg("requirements.in"), @r###" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- - error: Editable `example` requires Python <=3.8, but resolution targets Python 3.12.1 + error: Editable `example` requires Python <=3.8, but resolution targets Python 3.12.[X] "### ); @@ -5807,6 +6209,7 @@ fn no_stream() -> Result<()> { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir), @r###" success: true exit_code: 0 @@ -5825,6 +6228,28 @@ fn no_stream() -> Result<()> { Ok(()) } +/// Resolve a direct URL package with a URL that doesn't exist (i.e., returns a 404). +#[test] +fn not_found_direct_url() -> Result<()> { + let context = TestContext::new("3.12"); + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("iniconfig @ https://files.pythonhosted.org/packages/ef/a6/fake/iniconfig-2.0.0-py3-none-any.whl")?; + + uv_snapshot!(context.compile() + .arg("requirements.in"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Failed to download: iniconfig @ https://files.pythonhosted.org/packages/ef/a6/fake/iniconfig-2.0.0-py3-none-any.whl + Caused by: HTTP status client error (404 Not Found) for url (https://files.pythonhosted.org/packages/ef/a6/fake/iniconfig-2.0.0-py3-none-any.whl) + "### + ); + + Ok(()) +} + /// Raise an error when a direct URL dependency's `Requires-Python` constraint is not met. #[test] fn requires_python_direct_url() -> Result<()> { @@ -5849,7 +6274,7 @@ requires-python = "<=3.8" let requirements_in = context.temp_dir.child("requirements.in"); requirements_in.write_str(&format!("example @ {}", editable_dir.path().display()))?; - uv_snapshot!(context.compile() + uv_snapshot!(context.filters(), context.compile() .arg("requirements.in"), @r###" success: false exit_code: 1 @@ -5857,11 +6282,8 @@ requires-python = "<=3.8" ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because the current Python version (3.12.1) does not satisfy Python<=3.8 - and example==0.0.0 depends on Python<=3.8, we can conclude that - example==0.0.0 cannot be used. - And because only example==0.0.0 is available and you require example, we - can conclude that the requirements are unsatisfiable. + ╰─▶ Because the current Python version (3.12.[X]) does not satisfy Python<=3.8 and example==0.0.0 depends on Python<=3.8, we can conclude that example==0.0.0 cannot be used. + And because only example==0.0.0 is available and you require example, we can conclude that the requirements are unsatisfiable. "### ); @@ -6085,6 +6507,93 @@ fn preserve_hashes_upgrade() -> Result<()> { Ok(()) } +/// `--generate-hashes` should update the hashes in the "lockfile" if the package does not have +/// hashes, even if `--upgrade` is _not_ specified. +#[test] +fn preserve_hashes_no_existing_hashes() -> Result<()> { + let context = TestContext::new("3.12"); + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("markupsafe")?; + + // Write a subset of the hashes to the "lockfile". + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str(indoc! {r" + # This file was autogenerated by uv via the following command: + # uv pip compile requirements.in --python-version 3.12 --cache-dir [CACHE_DIR] + markupsafe==2.1.2 + "})?; + + // Add additional hashes to the "lockfile". + uv_snapshot!(context.compile() + .arg("requirements.in") + .arg("--output-file") + .arg("requirements.txt") + .arg("--generate-hashes"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in --output-file requirements.txt --generate-hashes + markupsafe==2.1.2 \ + --hash=sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed \ + --hash=sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc \ + --hash=sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2 \ + --hash=sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460 \ + --hash=sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7 \ + --hash=sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0 \ + --hash=sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1 \ + --hash=sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa \ + --hash=sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03 \ + --hash=sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323 \ + --hash=sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65 \ + --hash=sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013 \ + --hash=sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036 \ + --hash=sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f \ + --hash=sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4 \ + --hash=sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419 \ + --hash=sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2 \ + --hash=sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619 \ + --hash=sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a \ + --hash=sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a \ + --hash=sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd \ + --hash=sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7 \ + --hash=sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666 \ + --hash=sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65 \ + --hash=sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859 \ + --hash=sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625 \ + --hash=sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff \ + --hash=sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156 \ + --hash=sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd \ + --hash=sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba \ + --hash=sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f \ + --hash=sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1 \ + --hash=sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094 \ + --hash=sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a \ + --hash=sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513 \ + --hash=sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed \ + --hash=sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d \ + --hash=sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3 \ + --hash=sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147 \ + --hash=sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c \ + --hash=sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603 \ + --hash=sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601 \ + --hash=sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a \ + --hash=sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1 \ + --hash=sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d \ + --hash=sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3 \ + --hash=sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54 \ + --hash=sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2 \ + --hash=sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6 \ + --hash=sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58 + + ----- stderr ----- + Resolved 1 package in [TIME] + "### + ); + + Ok(()) +} + /// `--generate-hashes` should update the hashes in the "lockfile" if the package is upgraded due /// to a change in requirements. #[test] @@ -6253,7 +6762,7 @@ fn unnamed_path_requirement() -> Result<()> { fn unnamed_git_requirement() -> Result<()> { let context = TestContext::new("3.12"); let requirements_in = context.temp_dir.child("requirements.in"); - requirements_in.write_str("git+https://github.com/pallets/flask.git")?; + requirements_in.write_str("git+https://github.com/pallets/flask.git@3.0.0")?; uv_snapshot!(context.compile() .arg("requirements.in"), @r###" @@ -6266,7 +6775,7 @@ fn unnamed_git_requirement() -> Result<()> { # via flask click==8.1.7 # via flask - flask @ git+https://github.com/pallets/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe + flask @ git+https://github.com/pallets/flask.git@735a4701d6d5e848241e7d7535db898efb62d400 itsdangerous==2.1.2 # via flask jinja2==3.1.3 @@ -6875,19 +7384,16 @@ requires-python = ">3.8" .arg("requirements.in") .arg("--override") .arg("overrides.txt"), @r###" - success: false - exit_code: 1 - ----- stdout ----- + success: false + exit_code: 1 + ----- stdout ----- - ----- stderr ----- - × No solution found when resolving dependencies: - ╰─▶ Because there is no version of anyio==0.0.0 and lib==0.0.0 depends on - anyio==0.0.0, we can conclude that lib==0.0.0 cannot be used. - And because only lib==0.0.0 is available and example==0.0.0 depends on - lib, we can conclude that example==0.0.0 cannot be used. - And because only example==0.0.0 is available and you require example, we - can conclude that the requirements are unsatisfiable. - "### + ----- stderr ----- + × No solution found when resolving dependencies: + ╰─▶ Because there is no version of anyio==0.0.0 and lib==0.0.0 depends on anyio==0.0.0, we can conclude that lib==0.0.0 cannot be used. + And because only lib==0.0.0 is available and example==0.0.0 depends on lib, we can conclude that example==0.0.0 cannot be used. + And because only example==0.0.0 is available and you require example, we can conclude that the requirements are unsatisfiable. + "### ); // Now constrain `anyio` to the local version. @@ -7027,3 +7533,255 @@ requires-python = ">3.8" Ok(()) } + +/// Install a package via `--extra-index-url`. +/// +/// If the package exists exist on the "extra" index, but at an incompatible version, the +/// resolution should fail by default (even though a compatible version exists on the "primary" +/// index). +#[test] +fn compile_index_url_first_match() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("jinja2==3.1.0")?; + + uv_snapshot!(context.compile() + .arg("--index-url") + .arg("https://pypi.org/simple") + .arg("--extra-index-url") + .arg("https://download.pytorch.org/whl/cpu") + .arg("requirements.in") + .arg("--no-deps"), @r###" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + × No solution found when resolving dependencies: + ╰─▶ Because there is no version of jinja2==3.1.0 and you require jinja2==3.1.0, we can conclude that the requirements are unsatisfiable. + "### + ); + + Ok(()) +} + +/// Install a package via `--extra-index-url`. +/// +/// If the package exists exist on the "extra" index, but at an incompatible version, the +/// resolution should fallback to the "primary" index when `--index-strategy unsafe-any-match` +/// is provided. +#[test] +fn compile_index_url_fallback() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("jinja2==3.1.0")?; + + uv_snapshot!(context.compile() + .arg("--index-strategy") + .arg("unsafe-any-match") + .arg("--index-url") + .arg("https://pypi.org/simple") + .arg("--extra-index-url") + .arg("https://download.pytorch.org/whl/cpu") + .arg("requirements.in") + .arg("--no-deps"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z --index-strategy unsafe-any-match requirements.in --no-deps + jinja2==3.1.0 + + ----- stderr ----- + Resolved 1 package in [TIME] + "### + ); + + Ok(()) +} + +/// Install a package via `--extra-index-url`. +/// +/// If the package exists exist on the "extra" index at a compatible version, the resolver should +/// prefer it, even if a newer versions exists on the "primary" index. +/// +/// In this case, Jinja 3.1.2 is hosted on the "extra" index, but newer versions are available on +/// the "primary" index. +#[test] +fn compile_index_url_fallback_prefer_primary() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("jinja2")?; + + uv_snapshot!(context.compile_without_exclude_newer() + .arg("--index-strategy") + .arg("unsafe-any-match") + .arg("--index-url") + .arg("https://pypi.org/simple") + .arg("--extra-index-url") + .arg("https://download.pytorch.org/whl/cpu") + .arg("requirements.in") + .arg("--no-deps"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] --index-strategy unsafe-any-match requirements.in --no-deps + jinja2==3.1.2 + + ----- stderr ----- + Resolved 1 package in [TIME] + "### + ); + + Ok(()) +} + +/// Ensure that `--emit-index-annotation` prints the index URL for each package. +#[test] +fn emit_index_annotation_pypi_org_simple() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("requests")?; + + uv_snapshot!(context.compile() + .arg("requirements.in") + .arg("--emit-index-annotation"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in --emit-index-annotation + certifi==2024.2.2 + # via requests + # from https://pypi.org/simple + charset-normalizer==3.3.2 + # via requests + # from https://pypi.org/simple + idna==3.6 + # via requests + # from https://pypi.org/simple + requests==2.31.0 + # from https://pypi.org/simple + urllib3==2.2.1 + # via requests + # from https://pypi.org/simple + + ----- stderr ----- + Resolved 5 packages in [TIME] + "### + ); + + Ok(()) +} + +/// Ensure that `--emit-index-annotation` plays nicely with `--no-annotate`. +/// +/// For now, `--no-annotate` doesn't affect `--emit-index-annotation`, in that we still emit the +/// index annotation, and leave `--no-annotate` to only affect the package _source_ annotations. +#[test] +fn emit_index_annotation_no_annotate() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("requests")?; + + uv_snapshot!(context.compile() + .arg("requirements.in") + .arg("--emit-index-annotation") + .arg("--no-annotate"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in --emit-index-annotation --no-annotate + certifi==2024.2.2 + # from https://pypi.org/simple + charset-normalizer==3.3.2 + # from https://pypi.org/simple + idna==3.6 + # from https://pypi.org/simple + requests==2.31.0 + # from https://pypi.org/simple + urllib3==2.2.1 + # from https://pypi.org/simple + + ----- stderr ----- + Resolved 5 packages in [TIME] + "### + ); + + Ok(()) +} + +/// Ensure that `--emit-index-annotation` plays nicely with `--annotation-style=line`. +#[test] +fn emit_index_annotation_line() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("requests")?; + + uv_snapshot!(context.compile() + .arg("requirements.in") + .arg("--emit-index-annotation") + .arg("--annotation-style") + .arg("line"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in --emit-index-annotation --annotation-style line + certifi==2024.2.2 # via requests + # from https://pypi.org/simple + charset-normalizer==3.3.2 # via requests + # from https://pypi.org/simple + idna==3.6 # via requests + # from https://pypi.org/simple + requests==2.31.0 + # from https://pypi.org/simple + urllib3==2.2.1 # via requests + # from https://pypi.org/simple + + ----- stderr ----- + Resolved 5 packages in [TIME] + "### + ); + + Ok(()) +} + +/// `--emit-index-annotation` where packages are pulled from two distinct indexes. +#[test] +fn emit_index_annotation_multiple_indexes() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("uv\nrequests")?; + + uv_snapshot!(context.compile() + .arg("requirements.in") + .arg("--extra-index-url") + .arg("https://test.pypi.org/simple") + .arg("--emit-index-annotation"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in --emit-index-annotation + requests==2.5.4.1 + # from https://test.pypi.org/simple + uv==0.1.24 + # from https://pypi.org/simple + + ----- stderr ----- + Resolved 2 packages in [TIME] + "### + ); + + Ok(()) +} diff --git a/crates/uv/tests/pip_compile_scenarios.rs b/crates/uv/tests/pip_compile_scenarios.rs index 7398d84b1..c5a920a12 100644 --- a/crates/uv/tests/pip_compile_scenarios.rs +++ b/crates/uv/tests/pip_compile_scenarios.rs @@ -1,9 +1,9 @@ //! DO NOT EDIT //! //! Generated with `./scripts/sync_scenarios.sh` -//! Scenarios from +//! Scenarios from //! -#![cfg(all(feature = "python", feature = "pypi"))] +#![cfg(all(feature = "python", feature = "pypi", unix))] use std::env; use std::process::Command; @@ -13,28 +13,28 @@ use assert_cmd::assert::OutputAssertExt; use assert_fs::fixture::{FileWriteStr, PathChild}; use predicates::prelude::predicate; -use common::{create_bin_with_executables, get_bin, uv_snapshot, TestContext}; +use common::{get_bin, python_path_with_versions, uv_snapshot, TestContext}; mod common; /// Provision python binaries and return a `pip compile` command with options shared across all scenarios. fn command(context: &TestContext, python_versions: &[&str]) -> Command { - let bin = create_bin_with_executables(&context.temp_dir, python_versions) - .expect("Failed to create bin dir"); + let python_path = python_path_with_versions(&context.temp_dir, python_versions) + .expect("Failed to create Python test path"); let mut command = Command::new(get_bin()); command .arg("pip") .arg("compile") .arg("requirements.in") .arg("--index-url") - .arg("https://astral-sh.github.io/packse/0.3.12/simple-html/") + .arg("https://astral-sh.github.io/packse/0.3.14/simple-html/") .arg("--find-links") - .arg("https://raw.githubusercontent.com/zanieb/packse/0.3.12/vendor/links.html") + .arg("https://raw.githubusercontent.com/astral-sh/packse/0.3.14/vendor/links.html") .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) .env("UV_NO_WRAP", "1") - .env("UV_TEST_PYTHON_PATH", bin) + .env("UV_TEST_PYTHON_PATH", python_path) .current_dir(&context.temp_dir); if cfg!(all(windows, debug_assertions)) { @@ -84,7 +84,7 @@ fn incompatible_python_compatible_override() -> Result<()> { package-a==1.0.0 ----- stderr ----- - warning: The requested Python version 3.11 is not available; 3.9.18 will be used to build dependencies instead. + warning: The requested Python version 3.11 is not available; 3.9.[X] will be used to build dependencies instead. Resolved 1 package in [TIME] "### ); @@ -130,7 +130,7 @@ fn compatible_python_incompatible_override() -> Result<()> { ----- stdout ----- ----- stderr ----- - warning: The requested Python version 3.9 is not available; 3.11.7 will be used to build dependencies instead. + warning: The requested Python version 3.9 is not available; 3.11.[X] will be used to build dependencies instead. × No solution found when resolving dependencies: ╰─▶ Because the requested Python version (3.9) does not satisfy Python>=3.10 and package-a==1.0.0 depends on Python>=3.10, we can conclude that package-a==1.0.0 cannot be used. And because you require package-a==1.0.0, we can conclude that the requirements are unsatisfiable. @@ -184,9 +184,9 @@ fn incompatible_python_compatible_override_unavailable_no_wheels() -> Result<()> ----- stdout ----- ----- stderr ----- - warning: The requested Python version 3.11 is not available; 3.9.18 will be used to build dependencies instead. + warning: The requested Python version 3.11 is not available; 3.9.[X] will be used to build dependencies instead. × No solution found when resolving dependencies: - ╰─▶ Because the current Python version (3.9.18) does not satisfy Python>=3.10 and package-a==1.0.0 depends on Python>=3.10, we can conclude that package-a==1.0.0 cannot be used. + ╰─▶ Because the current Python version (3.9.[X]) does not satisfy Python>=3.10 and package-a==1.0.0 depends on Python>=3.10, we can conclude that package-a==1.0.0 cannot be used. And because you require package-a==1.0.0, we can conclude that the requirements are unsatisfiable. "### ); @@ -295,9 +295,9 @@ fn incompatible_python_compatible_override_no_compatible_wheels() -> Result<()> ----- stdout ----- ----- stderr ----- - warning: The requested Python version 3.11 is not available; 3.9.18 will be used to build dependencies instead. + warning: The requested Python version 3.11 is not available; 3.9.[X] will be used to build dependencies instead. × No solution found when resolving dependencies: - ╰─▶ Because the current Python version (3.9.18) does not satisfy Python>=3.10 and package-a==1.0.0 depends on Python>=3.10, we can conclude that package-a==1.0.0 cannot be used. + ╰─▶ Because the current Python version (3.9.[X]) does not satisfy Python>=3.10 and package-a==1.0.0 depends on Python>=3.10, we can conclude that package-a==1.0.0 cannot be used. And because you require package-a==1.0.0, we can conclude that the requirements are unsatisfiable. "### ); @@ -353,9 +353,9 @@ fn incompatible_python_compatible_override_other_wheel() -> Result<()> { ----- stdout ----- ----- stderr ----- - warning: The requested Python version 3.11 is not available; 3.9.18 will be used to build dependencies instead. + warning: The requested Python version 3.11 is not available; 3.9.[X] will be used to build dependencies instead. × No solution found when resolving dependencies: - ╰─▶ Because the current Python version (3.9.18) does not satisfy Python>=3.10 and package-a==1.0.0 depends on Python>=3.10, we can conclude that package-a==1.0.0 cannot be used. + ╰─▶ Because the current Python version (3.9.[X]) does not satisfy Python>=3.10 and package-a==1.0.0 depends on Python>=3.10, we can conclude that package-a==1.0.0 cannot be used. And because only the following versions of package-a are available: package-a==1.0.0 package-a==2.0.0 @@ -386,6 +386,7 @@ fn incompatible_python_compatible_override_other_wheel() -> Result<()> { /// └── a-1.0.0 /// └── requires python>=3.8.4 /// ``` +#[cfg(feature = "python-patch")] #[test] fn python_patch_override_no_patch() -> Result<()> { let context = TestContext::new("3.8.18"); @@ -433,6 +434,7 @@ fn python_patch_override_no_patch() -> Result<()> { /// └── a-1.0.0 /// └── requires python>=3.8.0 /// ``` +#[cfg(feature = "python-patch")] #[test] fn python_patch_override_patch_compatible() -> Result<()> { let context = TestContext::new("3.8.18"); diff --git a/crates/uv/tests/pip_freeze.rs b/crates/uv/tests/pip_freeze.rs index d3e5b4582..71da76dc9 100644 --- a/crates/uv/tests/pip_freeze.rs +++ b/crates/uv/tests/pip_freeze.rs @@ -19,6 +19,7 @@ fn command(context: &TestContext) -> Command { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir); command } @@ -32,6 +33,7 @@ fn sync_command(context: &TestContext) -> Command { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir); if cfg!(all(windows, debug_assertions)) { diff --git a/crates/uv/tests/pip_install.rs b/crates/uv/tests/pip_install.rs index ee987fb0b..95457a472 100644 --- a/crates/uv/tests/pip_install.rs +++ b/crates/uv/tests/pip_install.rs @@ -1,5 +1,7 @@ #![cfg(all(feature = "python", feature = "pypi"))] +use std::process::Command; + use anyhow::Result; use assert_cmd::prelude::*; use assert_fs::prelude::*; @@ -7,12 +9,10 @@ use base64::{prelude::BASE64_STANDARD as base64, Engine}; use indoc::indoc; use itertools::Itertools; -use std::process::Command; - use common::{uv_snapshot, TestContext}; use uv_fs::Simplified; -use crate::common::get_bin; +use crate::common::{get_bin, BUILD_VENDOR_LINKS_URL}; mod common; @@ -267,11 +267,8 @@ fn no_solution() { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because only flask<=3.0.2 is available and flask==3.0.2 depends - on werkzeug>=3.0.0, we can conclude that flask>=3.0.2 depends on - werkzeug>=3.0.0. - And because you require flask>=3.0.2 and you require werkzeug<1.0.0, we - can conclude that the requirements are unsatisfiable. + ╰─▶ Because only flask<=3.0.2 is available and flask==3.0.2 depends on werkzeug>=3.0.0, we can conclude that flask>=3.0.2 depends on werkzeug>=3.0.0. + And because you require flask>=3.0.2 and you require werkzeug<1.0.0, we can conclude that the requirements are unsatisfiable. "###); } @@ -360,6 +357,55 @@ fn install_requirements_txt() -> Result<()> { Ok(()) } +/// Install a `pyproject.toml` file with a `poetry` section. +#[test] +fn install_pyproject_toml_poetry() -> Result<()> { + let context = TestContext::new("3.12"); + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#"[tool.poetry] +name = "poetry-editable" +version = "0.1.0" +description = "" +authors = ["Astral Software Inc. "] + +[tool.poetry.dependencies] +python = "^3.10" +anyio = "^3" +iniconfig = { version = "*", optional = true } + +[tool.poetry.extras] +test = ["iniconfig"] + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" +"#, + )?; + + uv_snapshot!(context.install() + .arg("-r") + .arg("pyproject.toml") + .arg("--extra") + .arg("test"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 4 packages in [TIME] + Downloaded 4 packages in [TIME] + Installed 4 packages in [TIME] + + anyio==3.7.1 + + idna==3.6 + + iniconfig==2.0.0 + + sniffio==1.3.1 + "### + ); + + Ok(()) +} + /// Respect installed versions when resolving. #[test] fn respect_installed_and_reinstall() -> Result<()> { @@ -367,7 +413,6 @@ fn respect_installed_and_reinstall() -> Result<()> { // Install Flask. let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("Flask==2.3.2")?; uv_snapshot!(context.install() @@ -396,7 +441,6 @@ fn respect_installed_and_reinstall() -> Result<()> { // Re-install Flask. We should respect the existing version. let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("Flask")?; uv_snapshot!(context.install() @@ -416,7 +460,6 @@ fn respect_installed_and_reinstall() -> Result<()> { // Install a newer version of Flask. We should upgrade it. let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("Flask==2.3.3")?; let filters = if cfg!(windows) { @@ -448,7 +491,6 @@ fn respect_installed_and_reinstall() -> Result<()> { // Re-install Flask. We should upgrade it. let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("Flask")?; uv_snapshot!(filters, context.install() @@ -472,7 +514,6 @@ fn respect_installed_and_reinstall() -> Result<()> { // Re-install Flask. We should install even though the version is current let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("Flask")?; uv_snapshot!(filters, context.install() @@ -531,7 +572,6 @@ fn reinstall_extras() -> Result<()> { // Re-install httpx, with an extra. let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("httpx[http2]")?; uv_snapshot!(context.install() @@ -564,7 +604,6 @@ fn reinstall_incomplete() -> Result<()> { // Install anyio. let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("anyio==3.7.0")?; uv_snapshot!(context.install() @@ -589,7 +628,6 @@ fn reinstall_incomplete() -> Result<()> { // Re-install anyio. let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("anyio==4.0.0")?; uv_snapshot!(context.filters(), context.install() @@ -619,7 +657,6 @@ fn allow_incompatibilities() -> Result<()> { // Install Flask, which relies on `Werkzeug>=3.0.0`. let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("Flask")?; uv_snapshot!(context.install() @@ -648,7 +685,6 @@ fn allow_incompatibilities() -> Result<()> { // Install an incompatible version of Jinja2. let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("jinja2==2.11.3")?; uv_snapshot!(context.install() @@ -897,12 +933,9 @@ fn install_no_index() { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because flask was not found in the provided package locations and you - require flask, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because flask was not found in the provided package locations and you require flask, we can conclude that the requirements are unsatisfiable. - hint: Packages were unavailable because index lookups were disabled - and no additional package locations were provided (try: `--find-links - `) + hint: Packages were unavailable because index lookups were disabled and no additional package locations were provided (try: `--find-links `) "### ); @@ -924,13 +957,9 @@ fn install_no_index_version() { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because flask==3.0.0 was not found in the provided package locations - and you require flask==3.0.0, we can conclude that the requirements - are unsatisfiable. + ╰─▶ Because flask==3.0.0 was not found in the provided package locations and you require flask==3.0.0, we can conclude that the requirements are unsatisfiable. - hint: Packages were unavailable because index lookups were disabled - and no additional package locations were provided (try: `--find-links - `) + hint: Packages were unavailable because index lookups were disabled and no additional package locations were provided (try: `--find-links `) "### ); @@ -1323,10 +1352,7 @@ fn only_binary_requirements_txt() { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because django-allauth==0.51.0 is unusable because no wheels - are usable and building from source is disabled and you require - django-allauth==0.51.0, we can conclude that the requirements are - unsatisfiable. + ╰─▶ Because django-allauth==0.51.0 is unusable because no wheels are usable and building from source is disabled and you require django-allauth==0.51.0, we can conclude that the requirements are unsatisfiable. "### ); } @@ -1805,7 +1831,7 @@ fn launcher() -> Result<()> { uv_snapshot!( filters, context.install() - .arg(format!("simple_launcher@{}", project_root.join("scripts/wheels/simple_launcher-0.1.0-py3-none-any.whl").display())) + .arg(format!("simple_launcher@{}", project_root.join("scripts/links/simple_launcher-0.1.0-py3-none-any.whl").display())) .arg("--strict"), @r###" success: true exit_code: 0 @@ -1850,7 +1876,7 @@ fn launcher_with_symlink() -> Result<()> { uv_snapshot!(filters, context.install() - .arg(format!("simple_launcher@{}", project_root.join("scripts/wheels/simple_launcher-0.1.0-py3-none-any.whl").display())) + .arg(format!("simple_launcher@{}", project_root.join("scripts/links/simple_launcher-0.1.0-py3-none-any.whl").display())) .arg("--strict"), @r###" success: true @@ -2379,7 +2405,7 @@ requires-python = "<=3.8" "#, )?; - uv_snapshot!(context.install() + uv_snapshot!(context.filters(), context.install() .arg("--editable") .arg(editable_dir.path()), @r###" success: false @@ -2387,7 +2413,7 @@ requires-python = "<=3.8" ----- stdout ----- ----- stderr ----- - error: Editable `example` requires Python <=3.8, but 3.12.1 is installed + error: Editable `example` requires Python <=3.8, but 3.12.[X] is installed "### ); @@ -2472,7 +2498,6 @@ fn no_build_isolation() -> Result<()> { fn install_utf16le_requirements() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_binary(&utf8_to_utf16_with_bom_le("tomli"))?; uv_snapshot!(context.install_without_exclude_newer() @@ -2499,7 +2524,6 @@ fn install_utf16le_requirements() -> Result<()> { fn install_utf16be_requirements() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_binary(&utf8_to_utf16_with_bom_be("tomli"))?; uv_snapshot!(context.install_without_exclude_newer() @@ -2543,7 +2567,6 @@ fn utf8_to_utf16_with_bom_be(s: &str) -> Vec { fn dry_run_install() -> std::result::Result<(), Box> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("httpx==0.25.1")?; uv_snapshot!(context.install() @@ -2576,7 +2599,6 @@ fn dry_run_install() -> std::result::Result<(), Box> { fn dry_run_install_url_dependency() -> std::result::Result<(), Box> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("anyio @ https://files.pythonhosted.org/packages/2d/b8/7333d87d5f03247215d86a86362fd3e324111788c6cdd8d2e6196a6ba833/anyio-4.2.0.tar.gz")?; uv_snapshot!(context.install() @@ -2605,7 +2627,6 @@ fn dry_run_install_url_dependency() -> std::result::Result<(), Box std::result::Result<(), Box> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("anyio @ https://files.pythonhosted.org/packages/2d/b8/7333d87d5f03247215d86a86362fd3e324111788c6cdd8d2e6196a6ba833/anyio-4.2.0.tar.gz")?; // Install the URL dependency @@ -2657,7 +2678,6 @@ fn dry_run_uninstall_url_dependency() -> std::result::Result<(), Box std::result::Result<(), Box> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("httpx==0.25.1")?; // Install the package @@ -2708,7 +2728,6 @@ fn dry_run_install_transitive_dependency_already_installed( let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("httpcore==1.0.2")?; // Install a dependency of httpx @@ -2759,7 +2778,6 @@ fn dry_run_install_transitive_dependency_already_installed( fn dry_run_install_then_upgrade() -> std::result::Result<(), Box> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("httpx==0.25.0")?; // Install the package @@ -2828,7 +2846,7 @@ requires-python = "<=3.8" "#, )?; - uv_snapshot!(context.install() + uv_snapshot!(context.filters(), context.install() .arg(format!("example @ {}", editable_dir.path().display())), @r###" success: false exit_code: 1 @@ -2836,11 +2854,8 @@ requires-python = "<=3.8" ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because the current Python version (3.12.1) does not satisfy Python<=3.8 - and example==0.0.0 depends on Python<=3.8, we can conclude that - example==0.0.0 cannot be used. - And because only example==0.0.0 is available and you require example, we - can conclude that the requirements are unsatisfiable. + ╰─▶ Because the current Python version (3.12.[X]) does not satisfy Python<=3.8 and example==0.0.0 depends on Python<=3.8, we can conclude that example==0.0.0 cannot be used. + And because only example==0.0.0 is available and you require example, we can conclude that the requirements are unsatisfiable. "### ); @@ -3059,12 +3074,9 @@ fn reinstall_no_index() { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because anyio was not found in the provided package locations and you - require anyio, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because anyio was not found in the provided package locations and you require anyio, we can conclude that the requirements are unsatisfiable. - hint: Packages were unavailable because index lookups were disabled - and no additional package locations were provided (try: `--find-links - `) + hint: Packages were unavailable because index lookups were disabled and no additional package locations were provided (try: `--find-links `) "### ); } @@ -3138,7 +3150,7 @@ fn already_installed_dependent_editable() { // Disable the index to guard this test against dependency confusion attacks .arg("--no-index") .arg("--find-links") - .arg("https://raw.githubusercontent.com/astral-sh/packse/0.3.12/vendor/links.html"), @r###" + .arg(BUILD_VENDOR_LINKS_URL), @r###" success: true exit_code: 0 ----- stdout ----- @@ -3173,19 +3185,15 @@ fn already_installed_dependent_editable() { // Disable the index to guard this test against dependency confusion attacks .arg("--no-index") .arg("--find-links") - .arg("https://raw.githubusercontent.com/astral-sh/packse/0.3.12/vendor/links.html"), @r###" + .arg(BUILD_VENDOR_LINKS_URL), @r###" success: false exit_code: 1 ----- stdout ----- ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because first-editable was not found in the provided package locations - and second-editable==0.0.1 depends on first-editable, we can conclude - that second-editable==0.0.1 cannot be used. - And because only second-editable==0.0.1 is available and you - require second-editable, we can conclude that the requirements are - unsatisfiable. + ╰─▶ Because first-editable was not found in the provided package locations and second-editable==0.0.1 depends on first-editable, we can conclude that second-editable==0.0.1 cannot be used. + And because only second-editable==0.0.1 is available and you require second-editable, we can conclude that the requirements are unsatisfiable. "### ); @@ -3238,7 +3246,7 @@ fn already_installed_local_path_dependent() { // Disable the index to guard this test against dependency confusion attacks .arg("--no-index") .arg("--find-links") - .arg("https://raw.githubusercontent.com/astral-sh/packse/0.3.12/vendor/links.html"), @r###" + .arg(BUILD_VENDOR_LINKS_URL), @r###" success: true exit_code: 0 ----- stdout ----- @@ -3273,18 +3281,15 @@ fn already_installed_local_path_dependent() { // Disable the index to guard this test against dependency confusion attacks .arg("--no-index") .arg("--find-links") - .arg("https://raw.githubusercontent.com/astral-sh/packse/0.3.12/vendor/links.html"), @r###" + .arg(BUILD_VENDOR_LINKS_URL), @r###" success: false exit_code: 1 ----- 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 the requirements are unsatisfiable. + ╰─▶ 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 the requirements are unsatisfiable. "### ); @@ -3316,18 +3321,15 @@ fn already_installed_local_path_dependent() { // Disable the index to guard this test against dependency confusion attacks .arg("--no-index") .arg("--find-links") - .arg("https://raw.githubusercontent.com/astral-sh/packse/0.3.12/vendor/links.html"), @r###" + .arg(BUILD_VENDOR_LINKS_URL), @r###" success: false exit_code: 1 ----- 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 the requirements are unsatisfiable. + ╰─▶ 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 the requirements are unsatisfiable. "### ); @@ -3341,7 +3343,7 @@ fn already_installed_local_path_dependent() { // Disable the index to guard this test against dependency confusion attacks .arg("--no-index") .arg("--find-links") - .arg("https://raw.githubusercontent.com/astral-sh/packse/0.3.12/vendor/links.html"), @r###" + .arg(BUILD_VENDOR_LINKS_URL), @r###" success: true exit_code: 0 ----- stdout ----- @@ -3398,13 +3400,9 @@ fn already_installed_local_version_of_remote_package() { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because anyio==4.2.0 was not found in the provided package locations - and you require anyio==4.2.0, we can conclude that the requirements - are unsatisfiable. + ╰─▶ Because anyio==4.2.0 was not found in the provided package locations and you require anyio==4.2.0, we can conclude that the requirements are unsatisfiable. - hint: Packages were unavailable because index lookups were disabled - and no additional package locations were provided (try: `--find-links - `) + hint: Packages were unavailable because index lookups were disabled and no additional package locations were provided (try: `--find-links `) "### ); @@ -3419,9 +3417,7 @@ fn already_installed_local_version_of_remote_package() { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because there is no version of anyio==4.3.0+foo and you require - anyio==4.3.0+foo, we can conclude that the requirements are - unsatisfiable. + ╰─▶ Because there is no version of anyio==4.3.0+foo and you require anyio==4.3.0+foo, we can conclude that the requirements are unsatisfiable. "### ); @@ -3632,13 +3628,9 @@ fn already_installed_remote_url() { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because uv-public-pypackage was not found in the provided package - locations and you require uv-public-pypackage, we can conclude that the - requirements are unsatisfiable. + ╰─▶ Because uv-public-pypackage was not found in the provided package locations and you require uv-public-pypackage, we can conclude that the requirements are unsatisfiable. - hint: Packages were unavailable because index lookups were disabled - and no additional package locations were provided (try: `--find-links - `) + hint: Packages were unavailable because index lookups were disabled and no additional package locations were provided (try: `--find-links `) "###); // Request installation again with just the full URL @@ -3681,12 +3673,428 @@ fn already_installed_remote_url() { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because uv-public-pypackage==0.2.0 was not found in the provided package - locations and you require uv-public-pypackage==0.2.0, we can conclude - that the requirements are unsatisfiable. + ╰─▶ Because uv-public-pypackage==0.2.0 was not found in the provided package locations and you require uv-public-pypackage==0.2.0, we can conclude that the requirements are unsatisfiable. - hint: Packages were unavailable because index lookups were disabled - and no additional package locations were provided (try: `--find-links - `) + hint: Packages were unavailable because index lookups were disabled and no additional package locations were provided (try: `--find-links `) "###); } + +/// Sync using `--find-links` with a local directory. +#[test] +fn find_links() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str(indoc! {r" + tqdm + "})?; + + uv_snapshot!(context.filters(), context.install() + .arg("tqdm") + .arg("--find-links") + .arg(context.workspace_root.join("scripts/links/")), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] + Installed 1 package in [TIME] + + tqdm==1000.0.0 + "### + ); + + Ok(()) +} + +/// Sync using `--find-links` with a local directory, with wheels disabled. +#[test] +fn find_links_no_binary() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str(indoc! {r" + tqdm + "})?; + + uv_snapshot!(context.filters(), context.install() + .arg("tqdm") + .arg("--no-binary") + .arg(":all:") + .arg("--find-links") + .arg(context.workspace_root.join("scripts/links/")), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] + Installed 1 package in [TIME] + + tqdm==999.0.0 + "### + ); + + Ok(()) +} + +/// Provide valid hashes for all dependencies with `--require-hashes`. +#[test] +fn require_hashes() -> Result<()> { + 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" + anyio==4.0.0 \ + --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f \ + --hash=sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a + idna==3.6 \ + --hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \ + --hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f + # via anyio + sniffio==1.3.1 \ + --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \ + --hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc + # via anyio + "})?; + + uv_snapshot!(context.install() + .arg("-r") + .arg("requirements.txt") + .arg("--require-hashes"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 3 packages in [TIME] + Downloaded 3 packages in [TIME] + Installed 3 packages in [TIME] + + anyio==4.0.0 + + idna==3.6 + + sniffio==1.3.1 + "### + ); + + Ok(()) +} + +/// Omit hashes for dependencies with `--require-hashes`, which is allowed with `--no-deps`. +#[test] +fn require_hashes_no_deps() -> Result<()> { + 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" + anyio==4.0.0 \ + --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f \ + --hash=sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a + "})?; + + uv_snapshot!(context.install() + .arg("-r") + .arg("requirements.txt") + .arg("--no-deps") + .arg("--require-hashes"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] + Installed 1 package in [TIME] + + anyio==4.0.0 + "### + ); + + Ok(()) +} + +/// Provide the wrong hash with `--require-hashes`. +#[test] +fn require_hashes_mismatch() -> Result<()> { + let context = TestContext::new("3.12"); + + // Write to a requirements file. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str( + "anyio==4.0.0 --hash=sha256:afdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f", + )?; + + // Raise an error. + uv_snapshot!(context.install() + .arg("-r") + .arg("requirements.txt") + .arg("--require-hashes"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: In `--require-hashes` mode, all requirements must be pinned upfront with `==`, but found: idna + "### + ); + + Ok(()) +} + +/// Omit a transitive dependency in `--require-hashes`. +#[test] +fn require_hashes_missing_dependency() -> Result<()> { + let context = TestContext::new("3.12"); + + // Write to a requirements file. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str( + "anyio==4.0.0 --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f", + )?; + + // Install without error when `--require-hashes` is omitted. + uv_snapshot!(context.install() + .arg("-r") + .arg("requirements.txt") + .arg("--require-hashes"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: In `--require-hashes` mode, all requirements must be pinned upfront with `==`, but found: idna + "### + ); + + Ok(()) +} + +/// We disallow `--require-hashes` for editables' dependencies. +#[test] +fn require_hashes_editable() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str(&indoc::formatdoc! {r" + -e file://{workspace_root}/scripts/packages/black_editable[d] + ", + workspace_root = context.workspace_root.simplified_display(), + })?; + + // Install the editable packages. + uv_snapshot!(context.filters(), context.install() + .arg("-r") + .arg(requirements_txt.path()) + .arg("--require-hashes"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Built 1 editable in [TIME] + error: In `--require-hashes` mode, all requirements must be pinned upfront with `==`, but found: aiohttp + "### + ); + + Ok(()) +} + +/// If a hash is only included as a constraint, that's not good enough for `--require-hashes`. +#[test] +fn require_hashes_constraint() -> Result<()> { + let context = TestContext::new("3.12"); + + // Include the hash in the constraint file. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str("anyio==4.0.0")?; + + let constraints_txt = context.temp_dir.child("constraints.txt"); + constraints_txt.write_str("anyio==4.0.0 --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f")?; + + // Install the editable packages. + uv_snapshot!(context.install() + .arg("-r") + .arg(requirements_txt.path()) + .arg("--require-hashes") + .arg("-c") + .arg(constraints_txt.path()), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: In `--require-hashes` mode, all requirement must have a hash, but none were provided for: anyio==4.0.0 + "### + ); + + // Include the hash in the requirements file, but pin the version in the constraint file. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str( + "anyio --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f", + )?; + + let constraints_txt = context.temp_dir.child("constraints.txt"); + constraints_txt.write_str("anyio==4.0.0")?; + + // Install the editable packages. + uv_snapshot!(context.install() + .arg("-r") + .arg(requirements_txt.path()) + .arg("--require-hashes") + .arg("-c") + .arg(constraints_txt.path()), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: In `--require-hashes` mode, all requirement must have their versions pinned with `==`, but found: anyio + "### + ); + + Ok(()) +} + +/// We allow `--require-hashes` for unnamed URL dependencies. +#[test] +fn require_hashes_unnamed() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str(indoc::indoc! {r" + https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f + idna==3.6 \ + --hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \ + --hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f + # via anyio + sniffio==1.3.1 \ + --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \ + --hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc + # via anyio + "})?; + + uv_snapshot!(context.install() + .arg("-r") + .arg("requirements.txt") + .arg("--require-hashes"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 3 packages in [TIME] + Downloaded 3 packages in [TIME] + Installed 3 packages in [TIME] + + anyio==4.0.0 (from https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl) + + idna==3.6 + + sniffio==1.3.1 + "### + ); + + Ok(()) +} + +/// We allow `--require-hashes` for unnamed URL dependencies. In this case, the unnamed URL is +/// a repeat of a registered package. +#[test] +fn require_hashes_unnamed_repeated() -> Result<()> { + let context = TestContext::new("3.12"); + + // Re-run, but duplicate `anyio`. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str(indoc::indoc! {r" + anyio==4.0.0 \ + --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f \ + --hash=sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a + https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f + idna==3.6 \ + --hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \ + --hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f + # via anyio + sniffio==1.3.1 \ + --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \ + --hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc + # via anyio + "} )?; + + uv_snapshot!(context.install() + .arg("-r") + .arg("requirements.txt") + .arg("--require-hashes"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 3 packages in [TIME] + Downloaded 3 packages in [TIME] + Installed 3 packages in [TIME] + + anyio==4.0.0 (from https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl) + + idna==3.6 + + sniffio==1.3.1 + "### + ); + + Ok(()) +} + +/// If a hash is only included as a override, that's not good enough for `--require-hashes`. +/// +/// TODO(charlie): This _should_ be allowed. It's a bug. +#[test] +fn require_hashes_override() -> Result<()> { + let context = TestContext::new("3.12"); + + // Include the hash in the override file. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str("anyio==4.0.0")?; + + let overrides_txt = context.temp_dir.child("overrides.txt"); + overrides_txt.write_str("anyio==4.0.0 --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f")?; + + // Install the editable packages. + uv_snapshot!(context.install() + .arg("-r") + .arg(requirements_txt.path()) + .arg("--require-hashes") + .arg("--override") + .arg(overrides_txt.path()), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: In `--require-hashes` mode, all requirement must have a hash, but none were provided for: anyio==4.0.0 + "### + ); + + // Include the hash in the requirements file, but pin the version in the override file. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str( + "anyio --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f", + )?; + + let overrides_txt = context.temp_dir.child("overrides.txt"); + overrides_txt.write_str("anyio==4.0.0")?; + + // Install the editable packages. + uv_snapshot!(context.install() + .arg("-r") + .arg(requirements_txt.path()) + .arg("--require-hashes") + .arg("--override") + .arg(overrides_txt.path()), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: In `--require-hashes` mode, all requirement must have their versions pinned with `==`, but found: anyio + "### + ); + + Ok(()) +} diff --git a/crates/uv/tests/pip_install_scenarios.rs b/crates/uv/tests/pip_install_scenarios.rs index f79a30d19..43c399e1b 100644 --- a/crates/uv/tests/pip_install_scenarios.rs +++ b/crates/uv/tests/pip_install_scenarios.rs @@ -1,9 +1,9 @@ //! DO NOT EDIT //! //! Generated with `./scripts/sync_scenarios.sh` -//! Scenarios from +//! Scenarios from //! -#![cfg(all(feature = "python", feature = "pypi"))] +#![cfg(all(feature = "python", feature = "pypi", unix))] use std::path::Path; use std::process::Command; @@ -46,9 +46,9 @@ fn command(context: &TestContext) -> Command { .arg("pip") .arg("install") .arg("--index-url") - .arg("https://astral-sh.github.io/packse/0.3.12/simple-html/") + .arg("https://astral-sh.github.io/packse/0.3.14/simple-html/") .arg("--find-links") - .arg("https://raw.githubusercontent.com/zanieb/packse/0.3.12/vendor/links.html") + .arg("https://raw.githubusercontent.com/astral-sh/packse/0.3.14/vendor/links.html") .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) @@ -501,7 +501,7 @@ fn dependency_excludes_range_of_compatible_versions() { /// There is a non-contiguous range of compatible versions for the requested package /// `a`, but another dependency `c` excludes the range. This is the same as /// `dependency-excludes-range-of-compatible-versions` but some of the versions of -/// `a` are incompatible for another reason e.g. dependency on non-existent package +/// `a` are incompatible for another reason e.g. dependency on non-existant package /// `d`. /// /// ```text @@ -708,6 +708,7 @@ fn missing_extra() { ----- stderr ----- Resolved 1 package in [TIME] + warning: The package `package-a==1.0.0` does not have an extra named `extra`. Downloaded 1 package in [TIME] Installed 1 package in [TIME] + package-a==1.0.0 @@ -1080,6 +1081,7 @@ fn extra_does_not_exist_backtrack() { ----- stderr ----- Resolved 1 package in [TIME] + warning: The package `package-a==3.0.0` does not have an extra named `extra`. Downloaded 1 package in [TIME] Installed 1 package in [TIME] + package-a==3.0.0 @@ -1750,7 +1752,7 @@ fn local_transitive_confounding() { And because only package-a==1.0.0 is available and you require package-a, we can conclude that the requirements are unsatisfiable. "###); - // The version '1.2.3+foo' satisfies the constraint '==1.2.3'. + // The version '2.0.0+foo' satisfies the constraint '==2.0.0'. assert_not_installed( &context.venv, "local_transitive_confounding_a", @@ -3483,7 +3485,7 @@ fn package_only_prereleases_boundary() { "###); // Since there are only prerelease versions of `a` available, a prerelease is - // allowed. Since the user did not explicitly request a pre-release, pre-releases at + // allowed. Since the user did not explictly request a pre-release, pre-releases at // the boundary should not be selected. assert_installed( &context.venv, @@ -3669,7 +3671,7 @@ fn python_version_does_not_exist() { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because the current Python version (3.8.18) does not satisfy Python>=3.30 and package-a==1.0.0 depends on Python>=3.30, we can conclude that package-a==1.0.0 cannot be used. + ╰─▶ Because the current Python version (3.8.[X]) does not satisfy Python>=3.30 and package-a==1.0.0 depends on Python>=3.30, we can conclude that package-a==1.0.0 cannot be used. And because you require package-a==1.0.0, we can conclude that the requirements are unsatisfiable. "###); @@ -3711,7 +3713,7 @@ fn python_less_than_current() { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because the current Python version (3.9.18) does not satisfy Python<=3.8 and package-a==1.0.0 depends on Python<=3.8, we can conclude that package-a==1.0.0 cannot be used. + ╰─▶ Because the current Python version (3.9.[X]) does not satisfy Python<=3.8 and package-a==1.0.0 depends on Python<=3.8, we can conclude that package-a==1.0.0 cannot be used. And because you require package-a==1.0.0, we can conclude that the requirements are unsatisfiable. "###); @@ -3753,7 +3755,7 @@ fn python_greater_than_current() { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because the current Python version (3.9.18) does not satisfy Python>=3.10 and package-a==1.0.0 depends on Python>=3.10, we can conclude that package-a==1.0.0 cannot be used. + ╰─▶ Because the current Python version (3.9.[X]) does not satisfy Python>=3.10 and package-a==1.0.0 depends on Python>=3.10, we can conclude that package-a==1.0.0 cannot be used. And because you require package-a==1.0.0, we can conclude that the requirements are unsatisfiable. "###); @@ -3778,6 +3780,7 @@ fn python_greater_than_current() { /// └── a-1.0.0 /// └── requires python>=3.8.14 (incompatible with environment) /// ``` +#[cfg(feature = "python-patch")] #[test] fn python_greater_than_current_patch() { let context = TestContext::new("3.8.12"); @@ -3959,22 +3962,22 @@ fn python_greater_than_current_excluded() { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because the current Python version (3.9.18) does not satisfy Python>=3.10,<3.11 and the current Python version (3.9.18) does not satisfy Python>=3.12, we can conclude that any of: + ╰─▶ Because the current Python version (3.9.[X]) does not satisfy Python>=3.10,<3.11 and the current Python version (3.9.[X]) does not satisfy Python>=3.12, we can conclude that any of: Python>=3.10,<3.11 Python>=3.12 are incompatible. - And because the current Python version (3.9.18) does not satisfy Python>=3.11,<3.12, we can conclude that Python>=3.10 are incompatible. + And because the current Python version (3.9.[X]) does not satisfy Python>=3.11,<3.12, we can conclude that Python>=3.10 are incompatible. And because package-a==2.0.0 depends on Python>=3.10 and only the following versions of package-a are available: package-a<=2.0.0 package-a==3.0.0 package-a==4.0.0 we can conclude that package-a>=2.0.0,<3.0.0 cannot be used. (1) - Because the current Python version (3.9.18) does not satisfy Python>=3.11,<3.12 and the current Python version (3.9.18) does not satisfy Python>=3.12, we can conclude that Python>=3.11 are incompatible. + Because the current Python version (3.9.[X]) does not satisfy Python>=3.11,<3.12 and the current Python version (3.9.[X]) does not satisfy Python>=3.12, we can conclude that Python>=3.11 are incompatible. And because package-a==3.0.0 depends on Python>=3.11, we can conclude that package-a==3.0.0 cannot be used. And because we know from (1) that package-a>=2.0.0,<3.0.0 cannot be used, we can conclude that package-a>=2.0.0,<4.0.0 cannot be used. (2) - Because the current Python version (3.9.18) does not satisfy Python>=3.12 and package-a==4.0.0 depends on Python>=3.12, we can conclude that package-a==4.0.0 cannot be used. + Because the current Python version (3.9.[X]) does not satisfy Python>=3.12 and package-a==4.0.0 depends on Python>=3.12, we can conclude that package-a==4.0.0 cannot be used. And because we know from (2) that package-a>=2.0.0,<4.0.0 cannot be used, we can conclude that package-a>=2.0.0 cannot be used. And because you require package-a>=2.0.0, we can conclude that the requirements are unsatisfiable. "###); diff --git a/crates/uv/tests/pip_list.rs b/crates/uv/tests/pip_list.rs index 9b545ee2c..4b4fb7b36 100644 --- a/crates/uv/tests/pip_list.rs +++ b/crates/uv/tests/pip_list.rs @@ -1,8 +1,8 @@ use std::process::Command; use anyhow::Result; +use assert_fs::fixture::FileWriteStr; use assert_fs::fixture::PathChild; -use assert_fs::fixture::{FileTouch, FileWriteStr}; use common::uv_snapshot; @@ -21,6 +21,7 @@ fn install_command(context: &TestContext) -> Command { .arg("--exclude-newer") .arg(EXCLUDE_NEWER) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir); if cfg!(all(windows, debug_assertions)) { @@ -42,6 +43,7 @@ fn list_empty() { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir), @r###" success: true exit_code: 0 @@ -57,7 +59,6 @@ fn list_single_no_editable() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("MarkupSafe==2.1.3")?; uv_snapshot!(install_command(&context) @@ -84,6 +85,7 @@ fn list_single_no_editable() -> Result<()> { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir), @r###" success: true exit_code: 0 @@ -135,6 +137,7 @@ fn list_editable() { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir), @r###" success: true exit_code: 0 @@ -188,6 +191,7 @@ fn list_editable_only() { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir), @r###" success: true exit_code: 0 @@ -207,6 +211,7 @@ fn list_editable_only() { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir), @r###" success: true exit_code: 0 @@ -229,6 +234,7 @@ fn list_editable_only() { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir), @r###" success: true exit_code: 0 @@ -277,6 +283,7 @@ fn list_exclude() { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir), @r###" success: true exit_code: 0 @@ -300,6 +307,7 @@ fn list_exclude() { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir), @r###" success: true exit_code: 0 @@ -324,6 +332,7 @@ fn list_exclude() { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir), @r###" success: true exit_code: 0 @@ -377,6 +386,7 @@ fn list_format_json() { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir), @r###" success: true exit_code: 0 @@ -395,6 +405,7 @@ fn list_format_json() { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir), @r###" success: true exit_code: 0 @@ -413,6 +424,7 @@ fn list_format_json() { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir), @r###" success: true exit_code: 0 @@ -432,6 +444,7 @@ fn list_format_json() { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir), @r###" success: true exit_code: 0 @@ -479,6 +492,7 @@ fn list_format_freeze() { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir), @r###" success: true exit_code: 0 @@ -500,6 +514,7 @@ fn list_format_freeze() { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir), @r###" success: true exit_code: 0 @@ -518,6 +533,7 @@ fn list_format_freeze() { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir), @r###" success: true exit_code: 0 @@ -539,6 +555,7 @@ fn list_format_freeze() { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir), @r###" success: true exit_code: 0 diff --git a/crates/uv/tests/pip_show.rs b/crates/uv/tests/pip_show.rs index 5edee570d..4bfbbce15 100644 --- a/crates/uv/tests/pip_show.rs +++ b/crates/uv/tests/pip_show.rs @@ -3,8 +3,8 @@ use std::process::Command; use anyhow::Result; use assert_cmd::prelude::*; +use assert_fs::fixture::FileWriteStr; use assert_fs::fixture::PathChild; -use assert_fs::fixture::{FileTouch, FileWriteStr}; use indoc::indoc; use common::uv_snapshot; @@ -24,6 +24,7 @@ fn install_command(context: &TestContext) -> Command { .arg("--exclude-newer") .arg(EXCLUDE_NEWER) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir); if cfg!(all(windows, debug_assertions)) { @@ -45,6 +46,7 @@ fn show_empty() { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir), @r###" success: false exit_code: 1 @@ -61,7 +63,6 @@ fn show_requires_multiple() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("requests==2.31.0")?; uv_snapshot!(install_command(&context) @@ -92,6 +93,7 @@ fn show_requires_multiple() -> Result<()> { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir), @r###" success: true exit_code: 0 @@ -116,7 +118,6 @@ fn show_python_version_marker() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("click==8.1.7")?; uv_snapshot!(install_command(&context) @@ -149,6 +150,7 @@ fn show_python_version_marker() -> Result<()> { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir), @r###" success: true exit_code: 0 @@ -171,7 +173,6 @@ fn show_found_single_package() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("MarkupSafe==2.1.3")?; uv_snapshot!(install_command(&context) @@ -199,6 +200,7 @@ fn show_found_single_package() -> Result<()> { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir), @r###" success: true exit_code: 0 @@ -221,7 +223,6 @@ fn show_found_multiple_packages() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str(indoc! {r" MarkupSafe==2.1.3 pip==21.3.1 @@ -255,6 +256,7 @@ fn show_found_multiple_packages() -> Result<()> { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir), @r###" success: true exit_code: 0 @@ -283,7 +285,6 @@ fn show_found_one_out_of_three() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str(indoc! {r" MarkupSafe==2.1.3 pip==21.3.1 @@ -318,6 +319,7 @@ fn show_found_one_out_of_three() -> Result<()> { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir), @r###" success: true exit_code: 0 @@ -341,7 +343,6 @@ fn show_found_one_out_of_two_quiet() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str(indoc! {r" MarkupSafe==2.1.3 pip==21.3.1 @@ -377,6 +378,7 @@ fn show_found_one_out_of_two_quiet() -> Result<()> { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir), @r###" success: true exit_code: 0 @@ -394,7 +396,6 @@ fn show_empty_quiet() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str(indoc! {r" MarkupSafe==2.1.3 pip==21.3.1 @@ -429,6 +430,7 @@ fn show_empty_quiet() -> Result<()> { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir), @r###" success: false exit_code: 1 @@ -464,6 +466,7 @@ fn show_editable() -> Result<()> { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir), @r###" success: true exit_code: 0 @@ -487,7 +490,6 @@ fn show_required_by_multiple() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str(indoc! {r" anyio==4.0.0 requests==2.31.0 @@ -526,6 +528,7 @@ fn show_required_by_multiple() -> Result<()> { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir), @r###" success: true exit_code: 0 diff --git a/crates/uv/tests/pip_sync.rs b/crates/uv/tests/pip_sync.rs index b95f001cf..811a681f5 100644 --- a/crates/uv/tests/pip_sync.rs +++ b/crates/uv/tests/pip_sync.rs @@ -13,7 +13,7 @@ use indoc::indoc; use predicates::Predicate; use url::Url; -use common::{create_bin_with_executables, create_venv, uv_snapshot, venv_to_interpreter}; +use common::{create_venv, python_path_with_versions, uv_snapshot, venv_to_interpreter}; use uv_fs::Simplified; use crate::common::{copy_dir_all, get_bin, TestContext}; @@ -41,6 +41,7 @@ fn command(context: &TestContext) -> Command { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir); if cfg!(all(windows, debug_assertions)) { @@ -61,6 +62,7 @@ fn uninstall_command(context: &TestContext) -> Command { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir); if cfg!(all(windows, debug_assertions)) { @@ -140,7 +142,6 @@ fn install() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("MarkupSafe==2.1.3")?; uv_snapshot!(command(&context) @@ -182,7 +183,6 @@ fn install_copy() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("MarkupSafe==2.1.3")?; uv_snapshot!(command(&context) @@ -218,7 +218,6 @@ fn install_hardlink() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("MarkupSafe==2.1.3")?; uv_snapshot!(command(&context) @@ -254,7 +253,6 @@ fn install_many() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("MarkupSafe==2.1.3\ntomli==2.0.1")?; uv_snapshot!(command(&context) @@ -286,7 +284,6 @@ fn noop() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("MarkupSafe==2.1.3")?; command(&context) @@ -320,7 +317,6 @@ fn link() -> Result<()> { let venv1 = &context.venv; let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("MarkupSafe==2.1.3")?; Command::new(get_bin()) @@ -336,8 +332,8 @@ fn link() -> Result<()> { .success(); let venv2 = context.temp_dir.child(".venv2"); - let bin = create_bin_with_executables(&context.temp_dir, &["3.12"]) - .expect("Failed to create bin dir"); + let python_path = python_path_with_versions(&context.temp_dir, &["3.12"]) + .expect("Failed to create Python test path"); Command::new(get_bin()) .arg("venv") .arg(venv2.as_os_str()) @@ -345,7 +341,7 @@ fn link() -> Result<()> { .arg(context.cache_dir.path()) .arg("--python") .arg("3.12") - .env("UV_TEST_PYTHON_PATH", bin) + .env("UV_TEST_PYTHON_PATH", python_path) .current_dir(&context.temp_dir) .assert() .success(); @@ -382,7 +378,6 @@ fn add_remove() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("MarkupSafe==2.1.3")?; command(&context) @@ -392,7 +387,6 @@ fn add_remove() -> Result<()> { .success(); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("tomli==2.0.1")?; uv_snapshot!(command(&context) @@ -425,7 +419,6 @@ fn install_sequential() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("MarkupSafe==2.1.3")?; command(&context) @@ -435,7 +428,6 @@ fn install_sequential() -> Result<()> { .success(); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("MarkupSafe==2.1.3\ntomli==2.0.1")?; uv_snapshot!(command(&context) @@ -467,7 +459,6 @@ fn upgrade() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("tomli==2.0.0")?; command(&context) @@ -477,7 +468,6 @@ fn upgrade() -> Result<()> { .success(); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("tomli==2.0.1")?; uv_snapshot!(command(&context) @@ -508,7 +498,6 @@ fn install_url() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("werkzeug @ https://files.pythonhosted.org/packages/ff/1d/960bb4017c68674a1cb099534840f18d3def3ce44aed12b5ed8b78e0153e/Werkzeug-2.0.0-py3-none-any.whl")?; uv_snapshot!(command(&context) @@ -538,7 +527,6 @@ fn install_git_commit() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("werkzeug @ git+https://github.com/pallets/werkzeug.git@af160e0b6b7ddd81c22f1652c728ff5ac72d5c74")?; uv_snapshot!(command(&context) @@ -568,7 +556,6 @@ fn install_git_tag() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("werkzeug @ git+https://github.com/pallets/WerkZeug.git@2.0.0")?; uv_snapshot!(command(&context) @@ -598,7 +585,6 @@ fn install_git_subdirectories() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("example-pkg-a @ git+https://github.com/pypa/sample-namespace-packages.git@df7530eeb8fa0cb7dbb8ecb28363e8e36bfa2f45#subdirectory=pkg_resources/pkg_a\nexample-pkg-b @ git+https://github.com/pypa/sample-namespace-packages.git@df7530eeb8fa0cb7dbb8ecb28363e8e36bfa2f45#subdirectory=pkg_resources/pkg_b")?; uv_snapshot!(command(&context) @@ -630,7 +616,6 @@ fn install_sdist() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("Werkzeug==0.9.6")?; uv_snapshot!(command(&context) @@ -659,7 +644,6 @@ fn install_sdist_url() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("Werkzeug @ https://files.pythonhosted.org/packages/63/69/5702e5eb897d1a144001e21d676676bcb87b88c0862f947509ea95ea54fc/Werkzeug-0.9.6.tar.gz")?; uv_snapshot!(command(&context) @@ -689,7 +673,6 @@ fn install_url_then_install_url() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("werkzeug @ https://files.pythonhosted.org/packages/ff/1d/960bb4017c68674a1cb099534840f18d3def3ce44aed12b5ed8b78e0153e/Werkzeug-2.0.0-py3-none-any.whl")?; command(&context) @@ -722,7 +705,6 @@ fn install_url_then_install_version() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("werkzeug @ https://files.pythonhosted.org/packages/ff/1d/960bb4017c68674a1cb099534840f18d3def3ce44aed12b5ed8b78e0153e/Werkzeug-2.0.0-py3-none-any.whl")?; command(&context) @@ -732,7 +714,6 @@ fn install_url_then_install_version() -> Result<()> { .success(); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("werkzeug==2.0.0")?; uv_snapshot!(command(&context) @@ -759,7 +740,6 @@ fn install_version_then_install_url() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("werkzeug==2.0.0")?; command(&context) @@ -769,7 +749,6 @@ fn install_version_then_install_url() -> Result<()> { .success(); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("werkzeug @ https://files.pythonhosted.org/packages/ff/1d/960bb4017c68674a1cb099534840f18d3def3ce44aed12b5ed8b78e0153e/Werkzeug-2.0.0-py3-none-any.whl")?; uv_snapshot!(command(&context) @@ -801,7 +780,6 @@ fn install_numpy_py38() -> Result<()> { let context = TestContext::new("3.8"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("numpy")?; uv_snapshot!(command(&context) @@ -830,7 +808,6 @@ fn install_no_index() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("MarkupSafe==2.1.3")?; uv_snapshot!(command(&context) @@ -838,13 +815,14 @@ fn install_no_index() -> Result<()> { .arg("--no-index") .arg("--strict"), @r###" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- - error: Because markupsafe==2.1.3 was not found in the provided package locations and you require markupsafe==2.1.3, we can conclude that the requirements are unsatisfiable. + × No solution found when resolving dependencies: + ╰─▶ Because markupsafe==2.1.3 was not found in the provided package locations and you require markupsafe==2.1.3, we can conclude that the requirements are unsatisfiable. - hint: Packages were unavailable because index lookups were disabled and no additional package locations were provided (try: `--find-links `) + hint: Packages were unavailable because index lookups were disabled and no additional package locations were provided (try: `--find-links `) "### ); @@ -860,7 +838,6 @@ fn install_no_index_cached() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("MarkupSafe==2.1.3")?; uv_snapshot!(command(&context) @@ -890,13 +867,14 @@ fn install_no_index_cached() -> Result<()> { .arg("--no-index") .arg("--strict"), @r###" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- - error: Because markupsafe==2.1.3 was not found in the provided package locations and you require markupsafe==2.1.3, we can conclude that the requirements are unsatisfiable. + × No solution found when resolving dependencies: + ╰─▶ Because markupsafe==2.1.3 was not found in the provided package locations and you require markupsafe==2.1.3, we can conclude that the requirements are unsatisfiable. - hint: Packages were unavailable because index lookups were disabled and no additional package locations were provided (try: `--find-links `) + hint: Packages were unavailable because index lookups were disabled and no additional package locations were provided (try: `--find-links `) "### ); @@ -909,10 +887,8 @@ fn install_no_index_cached() -> Result<()> { fn warn_on_yanked_version() -> Result<()> { let context = TestContext::new("3.12"); - let requirements_in = context.temp_dir.child("requirements.txt"); - requirements_in.touch()?; - // This version is yanked. + let requirements_in = context.temp_dir.child("requirements.txt"); requirements_in.write_str("colorama==0.4.2")?; uv_snapshot!(context.filters(), windows_filters=false, command(&context) @@ -1096,12 +1072,15 @@ fn mismatched_name() -> Result<()> { .arg("requirements.txt") .arg("--strict"), @r###" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- - error: Failed to read: foo @ file://[TEMP_DIR]/foo-2.0.1-py3-none-any.whl - Caused by: The .dist-info directory tomli-2.0.1 does not start with the normalized package name: foo + × No solution found when resolving dependencies: + ╰─▶ Because foo was found, but has an invalid format and you require foo, we can conclude that the requirements are unsatisfiable. + + hint: The structure of foo was invalid: + The .dist-info directory tomli-2.0.1 does not start with the normalized package name: foo "### ); @@ -1160,7 +1139,6 @@ fn install_ujson() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("ujson @ https://files.pythonhosted.org/packages/43/1a/b0a027144aa5c8f4ea654f4afdd634578b450807bb70b9f8bad00d6f6d3c/ujson-5.7.0.tar.gz")?; uv_snapshot!(command(&context) @@ -1200,7 +1178,6 @@ fn install_build_system_no_backend() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("build-system-no-backend @ https://files.pythonhosted.org/packages/ec/25/1e531108ca027dc3a3b37d351f4b86d811df4884c6a81cd99e73b8b589f5/build-system-no-backend-0.1.0.tar.gz")?; uv_snapshot!(command(&context) @@ -1231,7 +1208,6 @@ fn install_url_source_dist_cached() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("tqdm @ https://files.pythonhosted.org/packages/62/06/d5604a70d160f6a6ca5fd2ba25597c24abd5c5ca5f437263d177ac242308/tqdm-4.66.1.tar.gz")?; let filters = if cfg!(windows) { @@ -1329,7 +1305,6 @@ fn install_git_source_dist_cached() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("werkzeug @ git+https://github.com/pallets/werkzeug.git@af160e0b6b7ddd81c22f1652c728ff5ac72d5c74")?; uv_snapshot!(command(&context) @@ -1426,7 +1401,6 @@ fn install_registry_source_dist_cached() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("future==0.18.3")?; uv_snapshot!(command(&context) @@ -1597,7 +1571,7 @@ fn install_path_source_dist_cached() -> Result<()> { ----- stdout ----- ----- stderr ----- - Removed 4 files for wheel ([SIZE]) + Removed 102 files for wheel ([SIZE]) "### ); @@ -1704,7 +1678,7 @@ fn install_path_built_dist_cached() -> Result<()> { ----- stdout ----- ----- stderr ----- - Removed 1 file for tomli ([SIZE]) + Removed 2 files for tomli ([SIZE]) "### ); @@ -1735,7 +1709,6 @@ fn install_url_built_dist_cached() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("tqdm @ https://files.pythonhosted.org/packages/00/e5/f12a80907d0884e6dff9c16d0c0114d81b8cd07dc3ae54c5e962cc83037e/tqdm-4.66.1-py3-none-any.whl")?; let filters = if cfg!(windows) { @@ -1832,7 +1805,6 @@ fn duplicate_package_overlap() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("MarkupSafe==2.1.3\nMarkupSafe==2.1.2")?; uv_snapshot!(command(&context) @@ -1857,7 +1829,6 @@ fn duplicate_package_disjoint() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("MarkupSafe==2.1.3\nMarkupSafe==2.1.2 ; python_version < '3.6'")?; uv_snapshot!(command(&context) @@ -1884,7 +1855,6 @@ fn reinstall() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("MarkupSafe==2.1.3\ntomli==2.0.1")?; uv_snapshot!(command(&context) @@ -1937,7 +1907,6 @@ fn reinstall_package() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("MarkupSafe==2.1.3\ntomli==2.0.1")?; uv_snapshot!(command(&context) @@ -1990,7 +1959,6 @@ fn reinstall_git() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("werkzeug @ git+https://github.com/pallets/werkzeug.git@af160e0b6b7ddd81c22f1652c728ff5ac72d5c74")?; uv_snapshot!(command(&context) @@ -2039,7 +2007,6 @@ fn refresh() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("MarkupSafe==2.1.3\ntomli==2.0.1")?; uv_snapshot!(command(&context) @@ -2097,7 +2064,6 @@ fn refresh_package() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("MarkupSafe==2.1.3\ntomli==2.0.1")?; uv_snapshot!(command(&context) @@ -2461,7 +2427,7 @@ fn find_links() -> Result<()> { uv_snapshot!(context.filters(), command(&context) .arg("requirements.txt") .arg("--find-links") - .arg(context.workspace_root.join("scripts/wheels/")), @r###" + .arg(context.workspace_root.join("scripts/links/")), @r###" success: true exit_code: 0 ----- stdout ----- @@ -2494,7 +2460,7 @@ fn find_links_no_index_match() -> Result<()> { .arg("requirements.txt") .arg("--no-index") .arg("--find-links") - .arg(context.workspace_root.join("scripts/wheels/")), @r###" + .arg(context.workspace_root.join("scripts/links/")), @r###" success: true exit_code: 0 ----- stdout ----- @@ -2524,7 +2490,7 @@ fn find_links_offline_match() -> Result<()> { .arg("requirements.txt") .arg("--offline") .arg("--find-links") - .arg(context.workspace_root.join("scripts/wheels/")), @r###" + .arg(context.workspace_root.join("scripts/links/")), @r###" success: true exit_code: 0 ----- stdout ----- @@ -2555,15 +2521,113 @@ fn find_links_offline_no_match() -> Result<()> { .arg("requirements.txt") .arg("--offline") .arg("--find-links") - .arg(context.workspace_root.join("scripts/wheels/")), @r###" + .arg(context.workspace_root.join("scripts/links/")), @r###" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- - error: Because numpy was not found in the cache and you require numpy, we can conclude that the requirements are unsatisfiable. + × No solution found when resolving dependencies: + ╰─▶ Because numpy was not found in the cache and you require numpy, we can conclude that the requirements are unsatisfiable. - hint: Packages were unavailable because the network was disabled + hint: Packages were unavailable because the network was disabled + "### + ); + + Ok(()) +} + +/// Sync using `--find-links` with a local directory. Ensure that cached wheels are reused. +#[test] +fn find_links_wheel_cache() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str(indoc! {r" + tqdm==1000.0.0 + "})?; + + // Install `tqdm`. + uv_snapshot!(context.filters(), command(&context) + .arg("requirements.txt") + .arg("--find-links") + .arg(context.workspace_root.join("scripts/links/")), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] + Installed 1 package in [TIME] + + tqdm==1000.0.0 + "### + ); + + // Reinstall `tqdm` with `--reinstall`. Ensure that the wheel is reused. + uv_snapshot!(context.filters(), command(&context) + .arg("requirements.txt") + .arg("--reinstall") + .arg("--find-links") + .arg(context.workspace_root.join("scripts/links/")), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Uninstalled 1 package in [TIME] + Installed 1 package in [TIME] + - tqdm==1000.0.0 + + tqdm==1000.0.0 + "### + ); + + Ok(()) +} + +/// Sync using `--find-links` with a local directory. Ensure that cached source distributions are +/// reused. +#[test] +fn find_links_source_cache() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str(indoc! {r" + tqdm==999.0.0 + "})?; + + // Install `tqdm`. + uv_snapshot!(context.filters(), command(&context) + .arg("requirements.txt") + .arg("--find-links") + .arg(context.workspace_root.join("scripts/links/")), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] + Installed 1 package in [TIME] + + tqdm==999.0.0 + "### + ); + + // Reinstall `tqdm` with `--reinstall`. Ensure that the wheel is reused. + uv_snapshot!(context.filters(), command(&context) + .arg("requirements.txt") + .arg("--reinstall") + .arg("--find-links") + .arg(context.workspace_root.join("scripts/links/")), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Uninstalled 1 package in [TIME] + Installed 1 package in [TIME] + - tqdm==999.0.0 + + tqdm==999.0.0 "### ); @@ -2582,13 +2646,14 @@ fn offline() -> Result<()> { .arg("requirements.in") .arg("--offline"), @r###" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- - error: Because black==23.10.1 was not found in the cache and you require black==23.10.1, we can conclude that the requirements are unsatisfiable. + × No solution found when resolving dependencies: + ╰─▶ Because black==23.10.1 was not found in the cache and you require black==23.10.1, we can conclude that the requirements are unsatisfiable. - hint: Packages were unavailable because the network was disabled + hint: Packages were unavailable because the network was disabled "### ); @@ -2730,7 +2795,6 @@ fn pip_entrypoints() -> Result<()> { // TODO(konstin): Remove git dep when the next pip version is released. for pip_requirement in ["pip==24.0", "pip @ git+https://github.com/pypa/pip"] { let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str(pip_requirement)?; command(&context) @@ -2842,7 +2906,6 @@ fn compile() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("MarkupSafe==2.1.3")?; uv_snapshot!(command(&context) @@ -2941,14 +3004,14 @@ requires-python = "<=3.5" let requirements_in = context.temp_dir.child("requirements.in"); requirements_in.write_str(&format!("-e {}", editable_dir.path().display()))?; - uv_snapshot!(command(&context) + uv_snapshot!(context.filters(), command(&context) .arg("requirements.in"), @r###" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- - error: Editable `example` requires Python <=3.5, but 3.12.1 is installed + error: Editable `example` requires Python <=3.5, but 3.12.[X] is installed "### ); @@ -3012,12 +3075,1553 @@ requires-python = "<=3.5" uv_snapshot!(context.filters(), command(&context) .arg("requirements.in"), @r###" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- - error: Because the current Python version (3.12.1) does not satisfy Python<=3.5 and example==0.0.0 depends on Python<=3.5, we can conclude that example==0.0.0 cannot be used. - And because only example==0.0.0 is available and you require example, we can conclude that the requirements are unsatisfiable. + × No solution found when resolving dependencies: + ╰─▶ Because the current Python version (3.12.[X]) does not satisfy Python<=3.5 and example==0.0.0 depends on Python<=3.5, we can conclude that example==0.0.0 cannot be used. + And because only example==0.0.0 is available and you require example, we can conclude that the requirements are unsatisfiable. + "### + ); + + Ok(()) +} + +/// Omit the hash with `--require-hashes`. +#[test] +fn require_hashes_missing_hash() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str("anyio==4.0.0")?; + + // Install without error when `--require-hashes` is omitted. + uv_snapshot!(command(&context) + .arg("requirements.txt"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] + Installed 1 package in [TIME] + + anyio==4.0.0 + "### + ); + + // Error when `--require-hashes` is provided. + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--require-hashes"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: In `--require-hashes` mode, all requirement must have a hash, but none were provided for: anyio==4.0.0 + "### + ); + + Ok(()) +} + +/// Omit the version with `--require-hashes`. +#[test] +fn require_hashes_missing_version() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str( + "anyio --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f", + )?; + + // Install without error when `--require-hashes` is omitted. + uv_snapshot!(command(&context) + .arg("requirements.txt"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] + Installed 1 package in [TIME] + + anyio==4.3.0 + "### + ); + + // Error when `--require-hashes` is provided. + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--require-hashes"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: In `--require-hashes` mode, all requirement must have their versions pinned with `==`, but found: anyio + "### + ); + + Ok(()) +} + +/// Include the hash for _just_ the wheel with `--no-binary`. +#[test] +fn require_hashes_wheel_no_binary() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str("anyio==4.0.0 --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--no-binary") + .arg(":all:") + .arg("--require-hashes"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + error: Failed to download distributions + Caused by: Failed to fetch wheel: anyio==4.0.0 + Caused by: Hash mismatch for anyio==4.0.0 + + Expected: + sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f + + Computed: + sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a + "### + ); + + Ok(()) +} + +/// Include the hash for _just_ the wheel with `--only-binary`. +#[test] +fn require_hashes_wheel_only_binary() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str("anyio==4.0.0 --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--only-binary") + .arg(":all:") + .arg("--require-hashes"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] + Installed 1 package in [TIME] + + anyio==4.0.0 + "### + ); + + Ok(()) +} + +/// Include the hash for _just_ the source distribution with `--no-binary`. +#[test] +fn require_hashes_source_no_binary() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str("anyio==4.0.0 --hash=sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--no-binary") + .arg(":all:") + .arg("--require-hashes"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] + Installed 1 package in [TIME] + + anyio==4.0.0 + "### + ); + + Ok(()) +} + +/// Include the hash for _just_ the source distribution, with `--binary-only`. +#[test] +fn require_hashes_source_only_binary() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str("anyio==4.0.0 --hash=sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--only-binary") + .arg(":all:") + .arg("--require-hashes"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + error: Failed to download distributions + Caused by: Failed to fetch wheel: anyio==4.0.0 + Caused by: Hash mismatch for anyio==4.0.0 + + Expected: + sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a + + Computed: + sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f + "### + ); + + Ok(()) +} + +/// Include the correct hash algorithm, but the wrong digest. +#[test] +fn require_hashes_wrong_digest() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str("anyio==4.0.0 --hash=sha256:afdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--require-hashes"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + error: Failed to download distributions + Caused by: Failed to fetch wheel: anyio==4.0.0 + Caused by: Hash mismatch for anyio==4.0.0 + + Expected: + sha256:afdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f + + Computed: + sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f + "### + ); + + Ok(()) +} + +/// Include the correct hash, but the wrong algorithm. +#[test] +fn require_hashes_wrong_algorithm() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str("anyio==4.0.0 --hash=sha512:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--require-hashes"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + error: Failed to download distributions + Caused by: Failed to fetch wheel: anyio==4.0.0 + Caused by: Hash mismatch for anyio==4.0.0 + + Expected: + sha512:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f + + Computed: + sha512:f30761c1e8725b49c498273b90dba4b05c0fd157811994c806183062cb6647e773364ce45f0e1ff0b10e32fe6d0232ea5ad39476ccf37109d6b49603a09c11c2 + "### + ); + + Ok(()) +} + +/// Include the hash for a source distribution specified as a direct URL dependency. +#[test] +fn require_hashes_source_url() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str("anyio @ https://files.pythonhosted.org/packages/74/17/5075225ee1abbb93cd7fc30a2d343c6a3f5f71cf388f14768a7a38256581/anyio-4.0.0.tar.gz --hash=sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--require-hashes"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] + Installed 1 package in [TIME] + + anyio==4.0.0 (from https://files.pythonhosted.org/packages/74/17/5075225ee1abbb93cd7fc30a2d343c6a3f5f71cf388f14768a7a38256581/anyio-4.0.0.tar.gz) + "### + ); + + // Reinstall with the right hash, and verify that it's reused. + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--reinstall") + .arg("--require-hashes"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Uninstalled 1 package in [TIME] + Installed 1 package in [TIME] + - anyio==4.0.0 (from https://files.pythonhosted.org/packages/74/17/5075225ee1abbb93cd7fc30a2d343c6a3f5f71cf388f14768a7a38256581/anyio-4.0.0.tar.gz) + + anyio==4.0.0 (from https://files.pythonhosted.org/packages/74/17/5075225ee1abbb93cd7fc30a2d343c6a3f5f71cf388f14768a7a38256581/anyio-4.0.0.tar.gz) + "### + ); + + // Reinstall with the wrong hash, and verify that it's rejected despite being cached. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str("anyio @ https://files.pythonhosted.org/packages/74/17/5075225ee1abbb93cd7fc30a2d343c6a3f5f71cf388f14768a7a38256581/anyio-4.0.0.tar.gz --hash=sha256:a7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--reinstall") + .arg("--require-hashes"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Failed to download and build: anyio @ https://files.pythonhosted.org/packages/74/17/5075225ee1abbb93cd7fc30a2d343c6a3f5f71cf388f14768a7a38256581/anyio-4.0.0.tar.gz + Caused by: Hash mismatch for anyio @ https://files.pythonhosted.org/packages/74/17/5075225ee1abbb93cd7fc30a2d343c6a3f5f71cf388f14768a7a38256581/anyio-4.0.0.tar.gz + + Expected: + sha256:a7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a + + Computed: + sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a + "### + ); + + Ok(()) +} + +/// Include the _wrong_ hash for a source distribution specified as a direct URL dependency. +#[test] +fn require_hashes_source_url_mismatch() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str("anyio @ https://files.pythonhosted.org/packages/74/17/5075225ee1abbb93cd7fc30a2d343c6a3f5f71cf388f14768a7a38256581/anyio-4.0.0.tar.gz --hash=sha256:a7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--require-hashes"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Failed to download and build: anyio @ https://files.pythonhosted.org/packages/74/17/5075225ee1abbb93cd7fc30a2d343c6a3f5f71cf388f14768a7a38256581/anyio-4.0.0.tar.gz + Caused by: Hash mismatch for anyio @ https://files.pythonhosted.org/packages/74/17/5075225ee1abbb93cd7fc30a2d343c6a3f5f71cf388f14768a7a38256581/anyio-4.0.0.tar.gz + + Expected: + sha256:a7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a + + Computed: + sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a + "### + ); + + Ok(()) +} + +/// Include the hash for a built distribution specified as a direct URL dependency. +#[test] +fn require_hashes_wheel_url() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str("anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--require-hashes"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] + Installed 1 package in [TIME] + + anyio==4.0.0 (from https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl) + "### + ); + + // Reinstall with the right hash, and verify that it's reused. + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--reinstall") + .arg("--require-hashes"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Uninstalled 1 package in [TIME] + Installed 1 package in [TIME] + - anyio==4.0.0 (from https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl) + + anyio==4.0.0 (from https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl) + "### + ); + + // Reinstall with the wrong hash, and verify that it's rejected despite being cached. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str("anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl --hash=sha256:afdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--reinstall") + .arg("--require-hashes"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + error: Failed to download distributions + Caused by: Failed to fetch wheel: anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl + Caused by: Hash mismatch for anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl + + Expected: + sha256:afdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f + + Computed: + sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f + "### + ); + + // Sync a new dependency and include the wrong hash for anyio. Verify that we reuse anyio + // despite the wrong hash, like pip, since we don't validate hashes for already-installed + // distributions. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str("anyio==4.0.0 --hash=sha256:afdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f\niniconfig==2.0.0 --hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--require-hashes"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] + Installed 1 package in [TIME] + + iniconfig==2.0.0 + "### + ); + + Ok(()) +} + +/// Include the _wrong_ hash for a built distribution specified as a direct URL dependency. +#[test] +fn require_hashes_wheel_url_mismatch() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str("anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl --hash=sha256:afdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--require-hashes"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + error: Failed to download distributions + Caused by: Failed to fetch wheel: anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl + Caused by: Hash mismatch for anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl + + Expected: + sha256:afdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f + + Computed: + sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f + "### + ); + + Ok(()) +} + +/// Reject Git dependencies when `--require-hashes` is provided. +#[test] +fn require_hashes_git() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str("anyio @ git+https://github.com/agronholm/anyio@4a23745badf5bf5ef7928f1e346e9986bd696d82 --hash=sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--require-hashes"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Failed to download and build: anyio @ git+https://github.com/agronholm/anyio@4a23745badf5bf5ef7928f1e346e9986bd696d82 + Caused by: Hash-checking is not supported for Git repositories: anyio @ git+https://github.com/agronholm/anyio@4a23745badf5bf5ef7928f1e346e9986bd696d82 + "### + ); + + Ok(()) +} + +/// Reject local directory dependencies when `--require-hashes` is provided. +#[test] +fn require_hashes_source_tree() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str(&format!( + "black @ {} --hash=sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a", + context + .workspace_root + .join("scripts/packages/black_editable") + .display() + ))?; + + uv_snapshot!(context.filters(), command(&context) + .arg("requirements.txt") + .arg("--require-hashes"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Failed to build: black @ file://[WORKSPACE]/scripts/packages/black_editable + Caused by: Hash-checking is not supported for local directories: black @ file://[WORKSPACE]/scripts/packages/black_editable + "### + ); + + Ok(()) +} + +/// Include the hash for _just_ the wheel with `--only-binary`. +#[test] +fn require_hashes_re_download() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str("anyio==4.0.0")?; + + // Install without `--require-hashes`. + uv_snapshot!(command(&context) + .arg("requirements.txt"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] + Installed 1 package in [TIME] + + anyio==4.0.0 + "### + ); + + // Reinstall with `--require-hashes`, and the wrong hash. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str("anyio==4.0.0 --hash=sha256:afdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--reinstall") + .arg("--require-hashes"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + error: Failed to download distributions + Caused by: Failed to fetch wheel: anyio==4.0.0 + Caused by: Hash mismatch for anyio==4.0.0 + + Expected: + sha256:afdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f + + Computed: + sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f + "### + ); + + // Reinstall with `--require-hashes`, and the right hash. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str("anyio==4.0.0 --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--reinstall") + .arg("--require-hashes"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Uninstalled 1 package in [TIME] + Installed 1 package in [TIME] + - anyio==4.0.0 + + anyio==4.0.0 + "### + ); + + Ok(()) +} + +/// Include the hash for a built distribution specified as a local path dependency. +#[test] +fn require_hashes_wheel_path() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str(&format!( + "tqdm @ {} --hash=sha256:a34996d4bd5abb2336e14ff0a2d22b92cfd0f0ed344e6883041ce01953276a13", + context + .workspace_root + .join("scripts/links/tqdm-1000.0.0-py3-none-any.whl") + .display() + ))?; + + uv_snapshot!(context.filters(), command(&context) + .arg("requirements.txt") + .arg("--require-hashes"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] + Installed 1 package in [TIME] + + tqdm==1000.0.0 (from file://[WORKSPACE]/scripts/links/tqdm-1000.0.0-py3-none-any.whl) + "### + ); + + Ok(()) +} + +/// Include the _wrong_ hash for a built distribution specified as a local path dependency. +#[test] +fn require_hashes_wheel_path_mismatch() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str(&format!( + "tqdm @ {} --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f", + context + .workspace_root + .join("scripts/links/tqdm-1000.0.0-py3-none-any.whl") + .display() + ))?; + + uv_snapshot!(context.filters(), command(&context) + .arg("requirements.txt") + .arg("--require-hashes"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + error: Failed to download distributions + Caused by: Failed to fetch wheel: tqdm @ file://[WORKSPACE]/scripts/links/tqdm-1000.0.0-py3-none-any.whl + Caused by: Hash mismatch for tqdm @ file://[WORKSPACE]/scripts/links/tqdm-1000.0.0-py3-none-any.whl + + Expected: + sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f + + Computed: + sha256:a34996d4bd5abb2336e14ff0a2d22b92cfd0f0ed344e6883041ce01953276a13 + "### + ); + + Ok(()) +} + +/// Include the hash for a source distribution specified as a local path dependency. +#[test] +fn require_hashes_source_path() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str(&format!( + "tqdm @ {} --hash=sha256:89fa05cffa7f457658373b85de302d24d0c205ceda2819a8739e324b75e9430b", + context + .workspace_root + .join("scripts/links/tqdm-999.0.0.tar.gz") + .display() + ))?; + + uv_snapshot!(context.filters(), command(&context) + .arg("requirements.txt") + .arg("--require-hashes"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] + Installed 1 package in [TIME] + + tqdm==999.0.0 (from file://[WORKSPACE]/scripts/links/tqdm-999.0.0.tar.gz) + "### + ); + + Ok(()) +} + +/// Include the _wrong_ hash for a source distribution specified as a local path dependency. +#[test] +fn require_hashes_source_path_mismatch() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str(&format!( + "tqdm @ {} --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f", + context + .workspace_root + .join("scripts/links/tqdm-999.0.0.tar.gz") + .display() + ))?; + + uv_snapshot!(context.filters(), command(&context) + .arg("requirements.txt") + .arg("--require-hashes"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Failed to build: tqdm @ file://[WORKSPACE]/scripts/links/tqdm-999.0.0.tar.gz + Caused by: Hash mismatch for tqdm @ file://[WORKSPACE]/scripts/links/tqdm-999.0.0.tar.gz + + Expected: + sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f + + Computed: + sha256:89fa05cffa7f457658373b85de302d24d0c205ceda2819a8739e324b75e9430b + "### + ); + + Ok(()) +} + +/// We allow `--require-hashes` for direct URL dependencies. +#[test] +fn require_hashes_unnamed() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str(indoc::indoc! {r" + https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f + "} )?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--require-hashes"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] + Installed 1 package in [TIME] + + anyio==4.0.0 (from https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl) + "### + ); + + Ok(()) +} + +/// We allow `--require-hashes` for editables, as long as no dependencies are included. +#[test] +fn require_hashes_editable() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str(&indoc::formatdoc! {r" + -e file://{workspace_root}/scripts/packages/black_editable[d] + ", + workspace_root = context.workspace_root.simplified_display(), + })?; + + // Install the editable packages. + uv_snapshot!(context.filters(), command(&context) + .arg(requirements_txt.path()) + .arg("--require-hashes"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Built 1 editable in [TIME] + Installed 1 package in [TIME] + + black==0.1.0 (from file://[WORKSPACE]/scripts/packages/black_editable) + "### + ); + + Ok(()) +} + +/// If a dependency is repeated, the hash should be required for both instances. +#[test] +fn require_hashes_repeated_dependency() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str("anyio==4.0.0 --hash=sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a\nanyio")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--require-hashes"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: In `--require-hashes` mode, all requirement must have their versions pinned with `==`, but found: anyio + "### + ); + + // Reverse the order. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str("anyio\nanyio==4.0.0 --hash=sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--require-hashes"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: In `--require-hashes` mode, all requirement must have their versions pinned with `==`, but found: anyio + "### + ); + + Ok(()) +} + +/// If a dependency is repeated, use the last hash provided. pip seems to use the _first_ hash. +#[test] +fn require_hashes_repeated_hash() -> Result<()> { + let context = TestContext::new("3.12"); + + // Use the same hash in both cases. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str(indoc::indoc! { r" + anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f + anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f + " })?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--require-hashes"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] + Installed 1 package in [TIME] + + anyio==4.0.0 (from https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl) + "### + ); + + // Use a different hash, but both are correct. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str(indoc::indoc! { r" + anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f + anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl --hash=sha512:f30761c1e8725b49c498273b90dba4b05c0fd157811994c806183062cb6647e773364ce45f0e1ff0b10e32fe6d0232ea5ad39476ccf37109d6b49603a09c11c2 + " })?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--require-hashes") + .arg("--reinstall"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] + Uninstalled 1 package in [TIME] + Installed 1 package in [TIME] + - anyio==4.0.0 (from https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl) + + anyio==4.0.0 (from https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl) + "### + ); + + // Use a different hash. The first hash is wrong, but that's fine, since we use the last hash. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str(indoc::indoc! { r" + anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl --hash=sha256:a7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a + anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl --hash=md5:420d85e19168705cdf0223621b18831a + " })?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--require-hashes") + .arg("--reinstall"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] + Uninstalled 1 package in [TIME] + Installed 1 package in [TIME] + - anyio==4.0.0 (from https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl) + + anyio==4.0.0 (from https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl) + "### + ); + + // Use a different hash. The second hash is wrong. This should fail, since we use the last hash. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str(indoc::indoc! { r" + anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl --hash=sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a + anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl --hash=md5:520d85e19168705cdf0223621b18831a + " })?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--require-hashes") + .arg("--reinstall"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + error: Failed to download distributions + Caused by: Failed to fetch wheel: anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl + Caused by: Hash mismatch for anyio @ https://files.pythonhosted.org/packages/36/55/ad4de788d84a630656ece71059665e01ca793c04294c463fd84132f40fe6/anyio-4.0.0-py3-none-any.whl + + Expected: + md5:520d85e19168705cdf0223621b18831a + + Computed: + md5:420d85e19168705cdf0223621b18831a + "### + ); + + Ok(()) +} + +/// If a dependency is repeated, the hash should be required for both instances. +#[test] +fn require_hashes_at_least_one() -> Result<()> { + let context = TestContext::new("3.12"); + + // Request `anyio` with a `sha256` hash. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str("anyio==4.0.0 --hash=sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--require-hashes"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] + Installed 1 package in [TIME] + + anyio==4.0.0 + "### + ); + + // Reinstall, requesting both `sha256` and `sha512`. We should reinstall from the cache, since + // at least one hash matches. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str("anyio==4.0.0 --hash=sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a --hash=md5:420d85e19168705cdf0223621b18831a")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--reinstall") + .arg("--require-hashes"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Uninstalled 1 package in [TIME] + Installed 1 package in [TIME] + - anyio==4.0.0 + + anyio==4.0.0 + "### + ); + + // This should be true even if the second hash is wrong. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str("anyio==4.0.0 --hash=sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a --hash=md5:1234")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--reinstall") + .arg("--require-hashes"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Uninstalled 1 package in [TIME] + Installed 1 package in [TIME] + - anyio==4.0.0 + + anyio==4.0.0 + "### + ); + + Ok(()) +} + +/// Using `--find-links`, but the registry doesn't provide us with a hash. +#[test] +fn require_hashes_find_links_no_hash() -> Result<()> { + let context = TestContext::new("3.12"); + + // First, use the correct hash. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str("example-a-961b4c22==1.0.0 --hash=sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--reinstall") + .arg("--require-hashes") + .arg("--find-links") + .arg("https://raw.githubusercontent.com/astral-test/astral-test-hash/main/no-hash/simple-html/example-a-961b4c22/index.html"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] + Installed 1 package in [TIME] + + example-a-961b4c22==1.0.0 + "### + ); + + // Second, use an incorrect hash. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str("example-a-961b4c22==1.0.0 --hash=sha256:123")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--reinstall") + .arg("--require-hashes") + .arg("--find-links") + .arg("https://raw.githubusercontent.com/astral-test/astral-test-hash/main/no-hash/simple-html/example-a-961b4c22/index.html"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + error: Failed to download distributions + Caused by: Failed to fetch wheel: example-a-961b4c22==1.0.0 + Caused by: Hash mismatch for example-a-961b4c22==1.0.0 + + Expected: + sha256:123 + + Computed: + sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e + "### + ); + + // Third, use the hash from the source distribution. This will actually fail, when it _could_ + // succeed, but pip has the same behavior. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str("example-a-961b4c22==1.0.0 --hash=sha256:294e788dbe500fdc39e8b88e82652ab67409a1dc9dd06543d0fe0ae31b713eb3")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--reinstall") + .arg("--require-hashes") + .arg("--find-links") + .arg("https://raw.githubusercontent.com/astral-test/astral-test-hash/main/no-hash/simple-html/example-a-961b4c22/index.html"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + error: Failed to download distributions + Caused by: Failed to fetch wheel: example-a-961b4c22==1.0.0 + Caused by: Hash mismatch for example-a-961b4c22==1.0.0 + + Expected: + sha256:294e788dbe500fdc39e8b88e82652ab67409a1dc9dd06543d0fe0ae31b713eb3 + + Computed: + sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e + "### + ); + + // Fourth, use the hash from the source distribution, and disable wheels. This should succeed. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str("example-a-961b4c22==1.0.0 --hash=sha256:294e788dbe500fdc39e8b88e82652ab67409a1dc9dd06543d0fe0ae31b713eb3")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--no-binary") + .arg(":all:") + .arg("--reinstall") + .arg("--require-hashes") + .arg("--find-links") + .arg("https://raw.githubusercontent.com/astral-test/astral-test-hash/main/no-hash/simple-html/example-a-961b4c22/index.html"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] + Uninstalled 1 package in [TIME] + Installed 1 package in [TIME] + - example-a-961b4c22==1.0.0 + + example-a-961b4c22==1.0.0 + "### + ); + + Ok(()) +} + +/// Using `--find-links`, and the registry serves us a correct hash. +#[test] +fn require_hashes_find_links_valid_hash() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str("example-a-961b4c22==1.0.0 --hash=sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--require-hashes") + .arg("--find-links") + .arg("https://raw.githubusercontent.com/astral-test/astral-test-hash/main/valid-hash/simple-html/example-a-961b4c22/index.html"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] + Installed 1 package in [TIME] + + example-a-961b4c22==1.0.0 + "### + ); + + Ok(()) +} + +/// Using `--find-links`, and the registry serves us an incorrect hash. +#[test] +fn require_hashes_find_links_invalid_hash() -> Result<()> { + let context = TestContext::new("3.12"); + + // First, request some other hash. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str("example-a-961b4c22==1.0.0 --hash=sha256:123")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--reinstall") + .arg("--require-hashes") + .arg("--find-links") + .arg("https://raw.githubusercontent.com/astral-test/astral-test-hash/main/invalid-hash/simple-html/example-a-961b4c22/index.html"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + error: Failed to download distributions + Caused by: Failed to fetch wheel: example-a-961b4c22==1.0.0 + Caused by: Hash mismatch for example-a-961b4c22==1.0.0 + + Expected: + sha256:123 + + Computed: + sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e + "### + ); + + // Second, request the invalid hash, that the registry _thinks_ is correct. We should reject it. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str("example-a-961b4c22==1.0.0 --hash=sha256:8838f9d005ff0432b258ba648d9cabb1cbdf06ac29d14f788b02edae544032ea")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--reinstall") + .arg("--require-hashes") + .arg("--find-links") + .arg("https://raw.githubusercontent.com/astral-test/astral-test-hash/main/invalid-hash/simple-html/example-a-961b4c22/index.html"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + error: Failed to download distributions + Caused by: Failed to fetch wheel: example-a-961b4c22==1.0.0 + Caused by: Hash mismatch for example-a-961b4c22==1.0.0 + + Expected: + sha256:8838f9d005ff0432b258ba648d9cabb1cbdf06ac29d14f788b02edae544032ea + + Computed: + sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e + "### + ); + + // Third, request the correct hash, that the registry _thinks_ is correct. We should accept + // it, since it's already cached under this hash. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str("example-a-961b4c22==1.0.0 --hash=sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--reinstall") + .arg("--require-hashes") + .arg("--find-links") + .arg("https://raw.githubusercontent.com/astral-test/astral-test-hash/main/invalid-hash/simple-html/example-a-961b4c22/index.html"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed 1 package in [TIME] + + example-a-961b4c22==1.0.0 + "### + ); + + // Fourth, request the correct hash, that the registry _thinks_ is correct, but without the + // cache. We _should_ accept it, but we currently don't. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str("example-a-961b4c22==1.0.0 --hash=sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--refresh") + .arg("--reinstall") + .arg("--require-hashes") + .arg("--find-links") + .arg("https://raw.githubusercontent.com/astral-test/astral-test-hash/main/invalid-hash/simple-html/example-a-961b4c22/index.html"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] + Uninstalled 1 package in [TIME] + Installed 1 package in [TIME] + - example-a-961b4c22==1.0.0 + + example-a-961b4c22==1.0.0 + "### + ); + + // Finally, request the correct hash, along with the incorrect hash for the source distribution. + // Resolution will fail, since the incorrect hash matches the registry's hash. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str("example-a-961b4c22==1.0.0 --hash=sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e --hash=sha256:a3cf07a05aac526131a2e8b6e4375ee6c6eaac8add05b88035e960ac6cd999ee")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--refresh") + .arg("--reinstall") + .arg("--require-hashes") + .arg("--find-links") + .arg("https://raw.githubusercontent.com/astral-test/astral-test-hash/main/invalid-hash/simple-html/example-a-961b4c22/index.html"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Failed to download and build: example-a-961b4c22==1.0.0 + Caused by: Hash mismatch for example-a-961b4c22==1.0.0 + + Expected: + sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e + sha256:a3cf07a05aac526131a2e8b6e4375ee6c6eaac8add05b88035e960ac6cd999ee + + Computed: + sha256:294e788dbe500fdc39e8b88e82652ab67409a1dc9dd06543d0fe0ae31b713eb3 + "### + ); + + Ok(()) +} + +/// Using `--index-url`, but the registry doesn't provide us with a hash. +#[test] +fn require_hashes_registry_no_hash() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str("example-a-961b4c22==1.0.0 --hash=sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--require-hashes") + .arg("--index-url") + .arg("https://astral-test.github.io/astral-test-hash/no-hash/simple-html/"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] + Installed 1 package in [TIME] + + example-a-961b4c22==1.0.0 + "### + ); + + Ok(()) +} + +/// Using `--index-url`, and the registry serves us a correct hash. +#[test] +fn require_hashes_registry_valid_hash() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str("example-a-961b4c22==1.0.0 --hash=sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--require-hashes") + .arg("--find-links") + .arg("https://astral-test.github.io/astral-test-hash/valid-hash/simple-html/"), @r###" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + × No solution found when resolving dependencies: + ╰─▶ Because example-a-961b4c22==1.0.0 was not found in the package registry and you require example-a-961b4c22==1.0.0, we can conclude that the requirements are unsatisfiable. + "### + ); + + Ok(()) +} + +/// Using `--index-url`, and the registry serves us an incorrect hash. +#[test] +fn require_hashes_registry_invalid_hash() -> Result<()> { + let context = TestContext::new("3.12"); + + // First, request some other hash. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str("example-a-961b4c22==1.0.0 --hash=sha256:123")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--reinstall") + .arg("--require-hashes") + .arg("--index-url") + .arg("https://astral-test.github.io/astral-test-hash/invalid-hash/simple-html/"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + error: Failed to download distributions + Caused by: Failed to fetch wheel: example-a-961b4c22==1.0.0 + Caused by: Hash mismatch for example-a-961b4c22==1.0.0 + + Expected: + sha256:123 + + Computed: + sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e + "### + ); + + // Second, request the invalid hash, that the registry _thinks_ is correct. We should reject it. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str("example-a-961b4c22==1.0.0 --hash=sha256:8838f9d005ff0432b258ba648d9cabb1cbdf06ac29d14f788b02edae544032ea")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--reinstall") + .arg("--require-hashes") + .arg("--index-url") + .arg("https://astral-test.github.io/astral-test-hash/invalid-hash/simple-html/"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + error: Failed to download distributions + Caused by: Failed to fetch wheel: example-a-961b4c22==1.0.0 + Caused by: Hash mismatch for example-a-961b4c22==1.0.0 + + Expected: + sha256:8838f9d005ff0432b258ba648d9cabb1cbdf06ac29d14f788b02edae544032ea + + Computed: + sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e + "### + ); + + // Third, request the correct hash, that the registry _thinks_ is correct. We should accept + // it, since it's already cached under this hash. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str("example-a-961b4c22==1.0.0 --hash=sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--reinstall") + .arg("--require-hashes") + .arg("--index-url") + .arg("https://astral-test.github.io/astral-test-hash/invalid-hash/simple-html/"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed 1 package in [TIME] + + example-a-961b4c22==1.0.0 + "### + ); + + // Fourth, request the correct hash, that the registry _thinks_ is correct, but without the + // cache. We _should_ accept it, but we currently don't. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str("example-a-961b4c22==1.0.0 --hash=sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--refresh") + .arg("--reinstall") + .arg("--require-hashes") + .arg("--index-url") + .arg("https://astral-test.github.io/astral-test-hash/invalid-hash/simple-html/"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] + Uninstalled 1 package in [TIME] + Installed 1 package in [TIME] + - example-a-961b4c22==1.0.0 + + example-a-961b4c22==1.0.0 + "### + ); + + // Finally, request the correct hash, along with the incorrect hash for the source distribution. + // Resolution will fail, since the incorrect hash matches the registry's hash. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt + .write_str("example-a-961b4c22==1.0.0 --hash=sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e --hash=sha256:a3cf07a05aac526131a2e8b6e4375ee6c6eaac8add05b88035e960ac6cd999ee")?; + + uv_snapshot!(command(&context) + .arg("requirements.txt") + .arg("--refresh") + .arg("--reinstall") + .arg("--require-hashes") + .arg("--index-url") + .arg("https://astral-test.github.io/astral-test-hash/invalid-hash/simple-html/"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Failed to download and build: example-a-961b4c22==1.0.0 + Caused by: Hash mismatch for example-a-961b4c22==1.0.0 + + Expected: + sha256:5d69f0b590514103234f0c3526563856f04d044d8d0ea1073a843ae429b3187e + sha256:a3cf07a05aac526131a2e8b6e4375ee6c6eaac8add05b88035e960ac6cd999ee + + Computed: + sha256:294e788dbe500fdc39e8b88e82652ab67409a1dc9dd06543d0fe0ae31b713eb3 "### ); diff --git a/crates/uv/tests/pip_uninstall.rs b/crates/uv/tests/pip_uninstall.rs index df3f44251..b6808afb9 100644 --- a/crates/uv/tests/pip_uninstall.rs +++ b/crates/uv/tests/pip_uninstall.rs @@ -19,6 +19,7 @@ fn uninstall_command(context: &TestContext) -> Command { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir); if cfg!(all(windows, debug_assertions)) { @@ -39,6 +40,7 @@ fn sync_command(context: &TestContext) -> Command { .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .current_dir(&context.temp_dir); if cfg!(all(windows, debug_assertions)) { @@ -125,7 +127,6 @@ fn missing_requirements_txt() -> Result<()> { fn invalid_requirements_txt_requirement() -> Result<()> { let temp_dir = assert_fs::TempDir::new()?; let requirements_txt = temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("flask==1.0.x")?; uv_snapshot!(Command::new(get_bin()) @@ -153,7 +154,6 @@ fn uninstall() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("MarkupSafe==2.1.3")?; sync_command(&context) @@ -195,7 +195,6 @@ fn missing_record() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("MarkupSafe==2.1.3")?; sync_command(&context) @@ -233,7 +232,6 @@ fn uninstall_editable_by_name() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str(&format!( "-e {}", context @@ -281,7 +279,6 @@ fn uninstall_by_path() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str( context .workspace_root @@ -329,7 +326,6 @@ fn uninstall_duplicate_by_path() -> Result<()> { let context = TestContext::new("3.12"); let requirements_txt = context.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str( context .workspace_root @@ -381,7 +377,6 @@ fn uninstall_duplicate() -> Result<()> { // Sync a version of `pip` into a virtual environment. let context1 = TestContext::new("3.12"); let requirements_txt = context1.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("pip==21.3.1")?; // Run `pip sync`. @@ -393,7 +388,6 @@ fn uninstall_duplicate() -> Result<()> { // Sync a different version of `pip` into a virtual environment. let context2 = TestContext::new("3.12"); let requirements_txt = context2.temp_dir.child("requirements.txt"); - requirements_txt.touch()?; requirements_txt.write_str("pip==22.1.1")?; // Run `pip sync`. diff --git a/crates/uv/tests/self_update.rs b/crates/uv/tests/self_update.rs new file mode 100644 index 000000000..277f98799 --- /dev/null +++ b/crates/uv/tests/self_update.rs @@ -0,0 +1,41 @@ +#![cfg(feature = "self-update")] + +use crate::common::get_bin; +use axoupdater::{ + test::helpers::{perform_runtest, RuntestArgs}, + ReleaseSourceType, +}; +use std::process::Command; + +mod common; + +#[test] +fn check_self_update() { + // To maximally emulate behaviour in practice, this test actually modifies CARGO_HOME + // and therefore should only be run in CI by default, where it can't hurt developers. + // We use the "CI" env-var that CI machines tend to run + if std::env::var("CI").map(|s| s.is_empty()).unwrap_or(true) { + return; + } + + // Configure the runtest + let args = RuntestArgs { + app_name: "uv".to_owned(), + package: "uv".to_owned(), + owner: "astral-sh".to_owned(), + bin: get_bin(), + binaries: vec!["uv".to_owned()], + args: vec!["self".to_owned(), "update".to_owned()], + release_type: ReleaseSourceType::GitHub, + }; + + // install and update the application + let installed_bin = perform_runtest(&args); + + // check that the binary works like normal + let status = Command::new(installed_bin) + .arg("--version") + .status() + .expect("failed to run 'uv --version'"); + assert!(status.success(), "'uv --version' returned non-zero"); +} diff --git a/crates/uv/tests/venv.rs b/crates/uv/tests/venv.rs index 94e79ae79..26e098300 100644 --- a/crates/uv/tests/venv.rs +++ b/crates/uv/tests/venv.rs @@ -1,7 +1,7 @@ #![cfg(feature = "python")] -use std::ffi::OsString; use std::process::Command; +use std::{ffi::OsString, str::FromStr}; use anyhow::Result; use assert_cmd::prelude::*; @@ -9,10 +9,9 @@ use assert_fs::fixture::ChildPath; use assert_fs::prelude::*; use fs_err::PathExt; use uv_fs::Simplified; +use uv_toolchain::PythonVersion; -use crate::common::{ - create_bin_with_executables, get_bin, uv_snapshot, TestContext, EXCLUDE_NEWER, -}; +use crate::common::{get_bin, python_path_with_versions, uv_snapshot, TestContext, EXCLUDE_NEWER}; mod common; @@ -20,20 +19,28 @@ struct VenvTestContext { cache_dir: assert_fs::TempDir, temp_dir: assert_fs::TempDir, venv: ChildPath, - bin: OsString, + python_path: OsString, + python_versions: Vec, } impl VenvTestContext { fn new(python_versions: &[&str]) -> Self { let temp_dir = assert_fs::TempDir::new().unwrap(); - let bin = create_bin_with_executables(&temp_dir, python_versions) - .expect("Failed to create bin dir"); + let python_path = python_path_with_versions(&temp_dir, python_versions) + .expect("Failed to create Python test path"); let venv = temp_dir.child(".venv"); + let python_versions = python_versions + .iter() + .map(|version| { + PythonVersion::from_str(version).expect("Tests should use valid Python versions") + }) + .collect::>(); Self { cache_dir: assert_fs::TempDir::new().unwrap(), temp_dir, venv, - bin, + python_path, + python_versions, } } @@ -45,7 +52,7 @@ impl VenvTestContext { .arg(self.cache_dir.path()) .arg("--exclude-newer") .arg(EXCLUDE_NEWER) - .env("UV_TEST_PYTHON_PATH", self.bin.clone()) + .env("UV_TEST_PYTHON_PATH", self.python_path.clone()) .current_dir(self.temp_dir.path()); command } @@ -70,6 +77,25 @@ impl VenvTestContext { r"Activate with: (?:.*)\\Scripts\\activate".to_string(), "Activate with: source .venv/bin/activate".to_string(), )); + + // Add Python patch version filtering unless one was explicitly requested to ensure + // snapshots are patch version agnostic when it is not a part of the test. + if self + .python_versions + .iter() + .all(|version| version.patch().is_none()) + { + for python_version in &self.python_versions { + filters.push(( + format!( + r"({})\.\d+", + regex::escape(python_version.to_string().as_str()) + ), + "$1.[X]".to_string(), + )); + } + } + filters } } @@ -88,7 +114,7 @@ fn create_venv() { ----- stdout ----- ----- stderr ----- - Using Python 3.12.1 interpreter at: [PATH] + Using Python 3.12.[X] interpreter at: [PATH] Creating virtualenv at: .venv Activate with: source .venv/bin/activate "### @@ -107,7 +133,7 @@ fn create_venv() { ----- stdout ----- ----- stderr ----- - Using Python 3.12.1 interpreter at: [PATH] + Using Python 3.12.[X] interpreter at: [PATH] Creating virtualenv at: .venv Activate with: source .venv/bin/activate "### @@ -128,7 +154,7 @@ fn create_venv_defaults_to_cwd() { ----- stdout ----- ----- stderr ----- - Using Python 3.12.1 interpreter at: [PATH] + Using Python 3.12.[X] interpreter at: [PATH] Creating virtualenv at: .venv Activate with: source .venv/bin/activate "### @@ -151,7 +177,7 @@ fn seed() { ----- stdout ----- ----- stderr ----- - Using Python 3.12.1 interpreter at: [PATH] + Using Python 3.12.[X] interpreter at: [PATH] Creating virtualenv at: .venv + pip==24.0 Activate with: source .venv/bin/activate @@ -175,7 +201,7 @@ fn seed_older_python_version() { ----- stdout ----- ----- stderr ----- - Using Python 3.10.13 interpreter at: [PATH] + Using Python 3.10.[X] interpreter at: [PATH] Creating virtualenv at: .venv + pip==24.0 + setuptools==69.2.0 @@ -253,6 +279,7 @@ fn create_venv_unknown_python_patch() { context.venv.assert(predicates::path::missing()); } +#[cfg(feature = "python-patch")] #[test] fn create_venv_python_patch() { let context = VenvTestContext::new(&["3.12.1"]); @@ -293,7 +320,7 @@ fn file_exists() -> Result<()> { ----- stdout ----- ----- stderr ----- - Using Python 3.12.1 interpreter at: [PATH] + Using Python 3.12.[X] interpreter at: [PATH] Creating virtualenv at: .venv uv::venv::creation @@ -321,7 +348,7 @@ fn empty_dir_exists() -> Result<()> { ----- stdout ----- ----- stderr ----- - Using Python 3.12.1 interpreter at: [PATH] + Using Python 3.12.[X] interpreter at: [PATH] Creating virtualenv at: .venv Activate with: source .venv/bin/activate "### @@ -350,7 +377,7 @@ fn non_empty_dir_exists() -> Result<()> { ----- stdout ----- ----- stderr ----- - Using Python 3.12.1 interpreter at: [PATH] + Using Python 3.12.[X] interpreter at: [PATH] Creating virtualenv at: .venv uv::venv::creation @@ -368,9 +395,9 @@ fn windows_shims() -> Result<()> { let context = VenvTestContext::new(&["3.9", "3.8"]); let shim_path = context.temp_dir.child("shim"); - let py38 = std::env::split_paths(&context.bin) + let py38 = std::env::split_paths(&context.python_path) .last() - .expect("create_bin_with_executables to set up the python versions"); + .expect("python_path_with_versions to set up the python versions"); // We want 3.8 and the first version should be 3.9. // Picking the last is necessary to prove that shims work because the python version selects // the python version from the first path segment by default, so we take the last to prove it's not @@ -388,14 +415,14 @@ fn windows_shims() -> Result<()> { uv_snapshot!(context.filters(), context.venv_command() .arg(context.venv.as_os_str()) .arg("--clear") - .env("UV_TEST_PYTHON_PATH", format!("{};{}", shim_path.display(), context.bin.simplified_display())), @r###" + .env("UV_TEST_PYTHON_PATH", format!("{};{}", shim_path.display(), context.python_path.simplified_display())), @r###" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- warning: virtualenv's `--clear` has no effect (uv always clears the virtual environment). - Using Python 3.8.12 interpreter at: [PATH] + Using Python 3.8.[X] interpreter at: [PATH] Creating virtualenv at: .venv Activate with: source .venv/bin/activate "### @@ -422,7 +449,7 @@ fn virtualenv_compatibility() { ----- stderr ----- warning: virtualenv's `--clear` has no effect (uv always clears the virtual environment). - Using Python 3.12.1 interpreter at: [PATH] + Using Python 3.12.[X] interpreter at: [PATH] Creating virtualenv at: .venv Activate with: source .venv/bin/activate "### @@ -482,6 +509,7 @@ fn verify_nested_pyvenv_cfg() -> Result<()> { .arg("--python") .arg("3.12") .env("VIRTUAL_ENV", context.venv.as_os_str()) + .env("UV_NO_WRAP", "1") .assert() .success(); diff --git a/pyproject.toml b/pyproject.toml index e8801643a..b4126bac9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "uv" -version = "0.1.28" +version = "0.1.31" description = "An extremely fast Python package installer and resolver, written in Rust." authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }] requires-python = ">=3.8" @@ -41,7 +41,7 @@ manifest-path = "crates/uv/Cargo.toml" module-name = "uv" python-source = "python" strip = true -include = ["rust-toolchain.toml"] +include = [{ path = "rust-toolchain.toml", format = ["sdist", "wheel"] }, { path = "LICENSE-APACHE", format = "sdist" }, { path = "LICENSE-MIT", format = "sdist" }] [tool.rooster] major_labels = [] # We do not use the major version number diff --git a/scripts/bench/__main__.py b/scripts/bench/__main__.py index a08c1ff31..8e87c262f 100644 --- a/scripts/bench/__main__.py +++ b/scripts/bench/__main__.py @@ -1,11 +1,11 @@ """Benchmark uv against other packaging tools. -This script assumes that `pip`, `pip-tools`, `virtualenv`, `poetry` and `hyperfine` are -installed, and that a uv release builds exists at `./target/release/uv` -(relative to the repository root). - This script assumes that Python 3.12 is installed. +By default, this script also assumes that `pip`, `pip-tools`, `virtualenv`, `poetry` and +`hyperfine` are installed, and that a uv release builds exists at `./target/release/uv` +(relative to the repository root). However, the set of tools is configurable. + To set up the required environment, run: cargo build --release @@ -13,17 +13,51 @@ To set up the required environment, run: source .venv/bin/activate ./target/release/uv pip sync ./scripts/bench/requirements.txt -Example usage: +Then, to benchmark uv against `pip-tools`: python -m scripts.bench --uv --pip-compile requirements.in -Multiple versions of uv can be benchmarked by specifying the path to the binary for -each build, as in: +It's most common to benchmark multiple uv versions against one another by building +from multiple branches and specifying the path to each binary, as in: + # Build the baseline version. + git checkout main + cargo build --release + mv ./target/release/uv ./target/release/baseline + + # Build the feature version. + git checkout feature + cargo build --release + + # Run the benchmark. python -m scripts.bench \ --uv-path ./target/release/uv \ --uv-path ./target/release/baseline \ requirements.in + +By default, the script will run the resolution benchmarks when a `requirements.in` file +is provided, and the installation benchmarks when a `requirements.txt` file is provided: + + # Run the resolution benchmarks against the Trio project. + python -m scripts.bench \ + --uv-path ./target/release/uv \ + --uv-path ./target/release/baseline \ + ./scripts/requirements/trio.in + + # Run the installation benchmarks against the Trio project. + python -m scripts.bench \ + --uv-path ./target/release/uv \ + --uv-path ./target/release/baseline \ + ./scripts/requirements/compiled/trio.txt + +You can also specify the benchmark to run explicitly: + + # Run the "uncached install" benchmark against the Trio project. + python -m scripts.bench \ + --uv-path ./target/release/uv \ + --uv-path ./target/release/baseline \ + --benchmark install-cold \ + ./scripts/requirements/compiled/trio.txt """ import abc diff --git a/scripts/bootstrap/install.py b/scripts/bootstrap/install.py deleted file mode 100755 index ce7cc1460..000000000 --- a/scripts/bootstrap/install.py +++ /dev/null @@ -1,204 +0,0 @@ -#!/usr/bin/env python3 -# /// script -# requires-python = ">=3.11" -# dependencies = [ -# "zstandard==0.22.0", -# ] -# /// -# -# Download required Python versions and install to `bin` -# Uses prebuilt Python distributions from indygreg/python-build-standalone -# -# This script can be run without Python installed via `install.sh` -# -# Requirements -# -# pip install zstandard==0.22.0 -# -# Usage -# -# python scripts/bootstrap/install.py -# -# Or -# -# pipx run scripts/bootstrap/install.py -# -# The Python versions are installed from `.python_versions`. -# Python versions are linked in-order such that the _last_ defined version will be the default. -# -# Version metadata can be updated with `fetch-version-metadata.py` - -import concurrent.futures -import hashlib -import json -import os -import platform -import shutil -import sys -import sysconfig -import tarfile -import tempfile -import urllib.parse -import urllib.request -from pathlib import Path - -try: - import zstandard -except ImportError: - print("ERROR: zstandard is required; install with `pip install zstandard==0.22.0`") - sys.exit(1) - -# Setup some file paths -THIS_DIR = Path(__file__).parent -ROOT_DIR = THIS_DIR.parent.parent -if bin_dir := os.environ.get("UV_BOOTSTRAP_DIR"): - BIN_DIR = Path(bin_dir) -else: - BIN_DIR = ROOT_DIR / "bin" -INSTALL_DIR = BIN_DIR / "versions" -VERSIONS_FILE = ROOT_DIR / ".python-versions" -VERSIONS_METADATA_FILE = THIS_DIR / "versions.json" - -# Map system information to those in the versions metadata -ARCH_MAP = {"aarch64": "arm64", "amd64": "x86_64"} -PLATFORM_MAP = {"win32": "windows"} -PLATFORM = sys.platform -ARCH = platform.machine().lower() -INTERPRETER = "cpython" - - -def decompress_file(archive_path: Path, output_path: Path): - if str(archive_path).endswith(".tar.zst"): - dctx = zstandard.ZstdDecompressor() - - with tempfile.TemporaryFile(suffix=".tar") as ofh: - with archive_path.open("rb") as ifh: - dctx.copy_stream(ifh, ofh) - ofh.seek(0) - with tarfile.open(fileobj=ofh) as z: - z.extractall(output_path) - else: - raise ValueError(f"Unknown archive type {archive_path.suffix}") - - -def sha256_file(path: Path): - h = hashlib.sha256() - - with open(path, "rb") as file: - while True: - # Reading is buffered, so we can read smaller chunks. - chunk = file.read(h.block_size) - if not chunk: - break - h.update(chunk) - - return h.hexdigest() - - -versions_metadata = json.loads(VERSIONS_METADATA_FILE.read_text()) -versions = VERSIONS_FILE.read_text().splitlines() - - -def get_key(version): - if platform.system() == "Linux": - libc = sysconfig.get_config_var("SOABI").split("-")[-1] - else: - libc = "none" - key = f"{INTERPRETER}-{version}-{PLATFORM_MAP.get(PLATFORM, PLATFORM)}-{ARCH_MAP.get(ARCH, ARCH)}-{libc}" - return key - - -def download(version): - key = get_key(version) - install_dir = INSTALL_DIR / f"{INTERPRETER}@{version}" - print(f"Downloading {key}") - - url = versions_metadata[key]["url"] - - if not url: - print(f"No matching download for {key}") - sys.exit(1) - - filename = url.split("/")[-1] - print(f"Downloading {urllib.parse.unquote(filename)}") - download_path = THIS_DIR / filename - with urllib.request.urlopen(url) as response: - with download_path.open("wb") as download_file: - shutil.copyfileobj(response, download_file) - - sha = versions_metadata[key]["sha256"] - if not sha: - print(f"WARNING: no checksum for {key}") - else: - print("Verifying checksum...", end="") - if sha256_file(download_path) != sha: - print(" FAILED!") - sys.exit(1) - print(" OK") - - if install_dir.exists(): - shutil.rmtree(install_dir) - print("Extracting to", install_dir) - install_dir.parent.mkdir(parents=True, exist_ok=True) - - # n.b. do not use `.with_suffix` as it will replace the patch Python version - extract_dir = Path(str(install_dir) + ".tmp") - - decompress_file(THIS_DIR / filename, extract_dir) - (extract_dir / "python").rename(install_dir) - (THIS_DIR / filename).unlink() - extract_dir.rmdir() - - return install_dir - - -def install(version, install_dir): - key = get_key(version) - - if PLATFORM == "win32": - executable = install_dir / "install" / "python.exe" - else: - # Use relative paths for links so if the bin is moved they don't break - executable = ( - "." / install_dir.relative_to(BIN_DIR) / "install" / "bin" / "python3" - ) - - major = versions_metadata[key]["major"] - minor = versions_metadata[key]["minor"] - - # Link as all version tuples, later versions in the file will take precedence - BIN_DIR.mkdir(parents=True, exist_ok=True) - - targets = [ - (BIN_DIR / f"python{version}"), - (BIN_DIR / f"python{major}.{minor}"), - (BIN_DIR / f"python{major}"), - (BIN_DIR / "python"), - ] - for target in targets: - target.unlink(missing_ok=True) - if PLATFORM == "win32": - target.hardlink_to(executable) - else: - target.symlink_to(executable) - - print(f"Installed executables for python{version}") - - -if __name__ == "__main__": - if INSTALL_DIR.exists(): - print("Removing existing installations...") - shutil.rmtree(INSTALL_DIR) - - # Download in parallel - with concurrent.futures.ProcessPoolExecutor(max_workers=len(versions)) as executor: - futures = [ - (version, executor.submit(download, version)) for version in versions - ] - - # Install sequentially so overrides are respected - for version, future in futures: - install_dir = future.result() - install(version, install_dir) - - print("Done!") diff --git a/scripts/wheels/maturin-1.4.0-py3-none-any.whl b/scripts/links/maturin-1.4.0-py3-none-any.whl similarity index 100% rename from scripts/wheels/maturin-1.4.0-py3-none-any.whl rename to scripts/links/maturin-1.4.0-py3-none-any.whl diff --git a/scripts/wheels/maturin-2.0.0-py3-none-linux_x86_64.whl b/scripts/links/maturin-2.0.0-py3-none-linux_x86_64.whl similarity index 100% rename from scripts/wheels/maturin-2.0.0-py3-none-linux_x86_64.whl rename to scripts/links/maturin-2.0.0-py3-none-linux_x86_64.whl diff --git a/scripts/wheels/simple_launcher-0.1.0-py3-none-any.whl b/scripts/links/simple_launcher-0.1.0-py3-none-any.whl similarity index 100% rename from scripts/wheels/simple_launcher-0.1.0-py3-none-any.whl rename to scripts/links/simple_launcher-0.1.0-py3-none-any.whl diff --git a/scripts/wheels/tqdm-1000.0.0-py3-none-any.whl b/scripts/links/tqdm-1000.0.0-py3-none-any.whl similarity index 100% rename from scripts/wheels/tqdm-1000.0.0-py3-none-any.whl rename to scripts/links/tqdm-1000.0.0-py3-none-any.whl diff --git a/scripts/wheels/tqdm-4.66.1-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl b/scripts/links/tqdm-4.66.1-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl similarity index 100% rename from scripts/wheels/tqdm-4.66.1-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl rename to scripts/links/tqdm-4.66.1-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl diff --git a/scripts/links/tqdm-999.0.0.tar.gz b/scripts/links/tqdm-999.0.0.tar.gz new file mode 100644 index 000000000..cbe4150bd Binary files /dev/null and b/scripts/links/tqdm-999.0.0.tar.gz differ diff --git a/scripts/links/validation-1.0.0-py3-none-any.whl b/scripts/links/validation-1.0.0-py3-none-any.whl new file mode 100644 index 000000000..8808be42f Binary files /dev/null and b/scripts/links/validation-1.0.0-py3-none-any.whl differ diff --git a/scripts/links/validation-2.0.0-py3-none-any.whl b/scripts/links/validation-2.0.0-py3-none-any.whl new file mode 100644 index 000000000..fa560c775 Binary files /dev/null and b/scripts/links/validation-2.0.0-py3-none-any.whl differ diff --git a/scripts/links/validation-3.0.0-py3-none-any.whl b/scripts/links/validation-3.0.0-py3-none-any.whl new file mode 100644 index 000000000..52ba2e5fd Binary files /dev/null and b/scripts/links/validation-3.0.0-py3-none-any.whl differ diff --git a/scripts/packages/dependent_editables/second_editable/.gitignore b/scripts/packages/dependent_editables/second_editable/.gitignore index eaa9f051b..c847724a5 100644 --- a/scripts/packages/dependent_editables/second_editable/.gitignore +++ b/scripts/packages/dependent_editables/second_editable/.gitignore @@ -1,2 +1,3 @@ # Artifacts from the build process. *.egg-info/ +build/ diff --git a/scripts/requirements/all-kinds.in b/scripts/requirements/all-kinds.in index 1fe196b8d..4b48f77a3 100644 --- a/scripts/requirements/all-kinds.in +++ b/scripts/requirements/all-kinds.in @@ -10,4 +10,3 @@ django_allauth==0.51.0 werkzeug @ https://files.pythonhosted.org/packages/0d/cc/ff1904eb5eb4b455e442834dabf9427331ac0fa02853bf83db817a7dd53d/werkzeug-3.0.1.tar.gz # git source dist pydantic-extra-types @ git+https://github.com/pydantic/pydantic-extra-types.git - diff --git a/scripts/scenarios/generate.py b/scripts/scenarios/generate.py index 37bd180e3..f5a60998e 100755 --- a/scripts/scenarios/generate.py +++ b/scripts/scenarios/generate.py @@ -65,7 +65,6 @@ except ImportError: ) exit(1) - try: import chevron_blue except ImportError: @@ -133,6 +132,13 @@ def main(scenarios: list[Path], snapshot_update: bool = True): else [] ) + # Hack to track which scenarios require a specific Python patch version + for scenario in data["scenarios"]: + if "patch" in scenario["name"]: + scenario["python_patch"] = True + else: + scenario["python_patch"] = False + # We don't yet support local versions that aren't expressed as direct dependencies. for scenario in data["scenarios"]: expected = scenario["expected"] @@ -168,11 +174,11 @@ def main(scenarios: list[Path], snapshot_update: bool = True): # Add generated metadata data["generated_from"] = ( - f"https://github.com/zanieb/packse/tree/{ref}/scenarios" + f"https://github.com/astral-sh/packse/tree/{ref}/scenarios" ) data["generated_with"] = "./scripts/sync_scenarios.sh" data["vendor_links"] = ( - f"https://raw.githubusercontent.com/zanieb/packse/{ref}/vendor/links.html" + f"https://raw.githubusercontent.com/astral-sh/packse/{ref}/vendor/links.html" ) data["index_url"] = f"https://astral-sh.github.io/packse/{ref}/simple-html/" diff --git a/scripts/scenarios/requirements.txt b/scripts/scenarios/requirements.txt index c4dbcd759..1d7df3023 100644 --- a/scripts/scenarios/requirements.txt +++ b/scripts/scenarios/requirements.txt @@ -6,33 +6,37 @@ charset-normalizer==3.3.2 # via requests chevron-blue==0.2.1 # via packse -docutils==0.20.1 +docutils==0.21.post1 # via readme-renderer -editables==0.5 - # via hatchling -hatchling==1.21.1 +hatchling==1.22.5 # via packse idna==3.6 # via requests -importlib-metadata==7.0.2 +importlib-metadata==7.1.0 # via twine -jaraco-classes==3.3.1 +jaraco-classes==3.4.0 # via keyring -keyring==24.3.1 +jaraco-context==5.3.0 + # via keyring +jaraco-functools==4.0.0 + # via keyring +keyring==25.1.0 # via twine markdown-it-py==3.0.0 # via rich mdurl==0.1.2 # via markdown-it-py more-itertools==10.2.0 - # via jaraco-classes + # via + # jaraco-classes + # jaraco-functools msgspec==0.18.6 # via packse -nh3==0.2.15 +nh3==0.2.17 # via readme-renderer packaging==24.0 # via hatchling -packse==0.3.12 +packse==0.3.14 pathspec==0.12.1 # via hatchling pkginfo==1.10.0 @@ -57,7 +61,7 @@ rich==13.7.1 # via twine setuptools==69.2.0 # via packse -trove-classifiers==2024.3.3 +trove-classifiers==2024.4.10 # via hatchling twine==4.0.2 # via packse diff --git a/scripts/scenarios/templates/compile.mustache b/scripts/scenarios/templates/compile.mustache index 6a21783e1..7084da6be 100644 --- a/scripts/scenarios/templates/compile.mustache +++ b/scripts/scenarios/templates/compile.mustache @@ -3,7 +3,7 @@ //! Generated with `{{generated_with}}` //! Scenarios from <{{generated_from}}> //! -#![cfg(all(feature = "python", feature = "pypi"))] +#![cfg(all(feature = "python", feature = "pypi", unix))] use std::env; use std::process::Command; @@ -13,14 +13,14 @@ use assert_cmd::assert::OutputAssertExt; use assert_fs::fixture::{FileWriteStr, PathChild}; use predicates::prelude::predicate; -use common::{create_bin_with_executables, get_bin, uv_snapshot, TestContext}; +use common::{python_path_with_versions, get_bin, uv_snapshot, TestContext}; mod common; /// Provision python binaries and return a `pip compile` command with options shared across all scenarios. fn command(context: &TestContext, python_versions: &[&str]) -> Command { - let bin = create_bin_with_executables(&context.temp_dir, python_versions) - .expect("Failed to create bin dir"); + let python_path = python_path_with_versions(&context.temp_dir, python_versions) + .expect("Failed to create Python test path"); let mut command = Command::new(get_bin()); command .arg("pip") @@ -34,7 +34,7 @@ fn command(context: &TestContext, python_versions: &[&str]) -> Command { .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) .env("UV_NO_WRAP", "1") - .env("UV_TEST_PYTHON_PATH", bin) + .env("UV_TEST_PYTHON_PATH", python_path) .current_dir(&context.temp_dir); if cfg!(all(windows, debug_assertions)) { @@ -58,6 +58,9 @@ fn command(context: &TestContext, python_versions: &[&str]) -> Command { /// {{.}} {{/tree}} /// ``` +{{#python_patch}} +#[cfg(feature = "python-patch")] +{{/python_patch}} #[test] fn {{module_name}}() -> Result<()> { let context = TestContext::new("{{environment.python}}"); diff --git a/scripts/scenarios/templates/install.mustache b/scripts/scenarios/templates/install.mustache index 06517014f..51e309ea0 100644 --- a/scripts/scenarios/templates/install.mustache +++ b/scripts/scenarios/templates/install.mustache @@ -3,7 +3,7 @@ //! Generated with `{{generated_with}}` //! Scenarios from <{{generated_from}}> //! -#![cfg(all(feature = "python", feature = "pypi"))] +#![cfg(all(feature = "python", feature = "pypi", unix))] use std::path::Path; use std::process::Command; @@ -77,6 +77,9 @@ fn command(context: &TestContext) -> Command { /// {{.}} {{/tree}} /// ``` +{{#python_patch}} +#[cfg(feature = "python-patch")] +{{/python_patch}} #[test] fn {{module_name}}() { let context = TestContext::new("{{environment.python}}");